1 /++
2 $(H4 High level YAML deserialization API)
3 
4 Macros:
5 IONREF = $(REF_ALTTEXT $(TT $2), $2, mir, ion, $1)$(NBSP)
6 +/
7 module mir.deser.yaml;
8 
9 import std.traits: isMutable;
10 
11 /++
12 Deserialize YAML document to a scpecified type.
13 Params:
14     T = type of the value
15     text = UTF-8 text (without BOM)
16     fileNam = (optional) file name for better error information
17 Returns:
18     value of type `T`
19 +/
20 T deserializeYaml(T)(scope const(char)[] text, string fileName = "<unknown>")
21     if (isMutable!T)
22 {
23     auto values = deserializeYamlValues!T(text, fileName);
24 
25     if (values.length != 1)
26     {
27         import mir.serde: SerdeMirException;
28         throw new SerdeMirException(
29             "Expected single YAML document in file", fileName,
30             ", got ", values.length, " documents");
31     }
32 
33     import core.lifetime: move;
34     return move(values[0]);
35 }
36 
37 ///
38 @safe
39 unittest
40 {
41     import mir.test: should;
42 
43     static struct S
44     {
45         string foo;
46         uint bar;
47     }
48 
49     `{foo: str, bar: 4}`.deserializeYaml!S.should == S("str", 4);
50 }
51 
52 /// Tags (annotations) support
53 @safe
54 unittest
55 {
56     import mir.test: should;
57     import mir.algebraic: Variant;
58     import mir.serde: serdeAlgebraicAnnotation, serdeAnnotation, serdeOptional;
59 
60     @serdeAlgebraicAnnotation("!S")
61     static struct S
62     {
63         string foo;
64         uint bar;
65     }
66 
67     @serdeAlgebraicAnnotation("rgb")
68     static struct RGB
69     {
70         @serdeAnnotation @serdeOptional
71         string name;
72 
73         ubyte r, g, b;
74     }
75 
76     alias V = Variant!(S, RGB);
77 
78     `!S {foo: str, bar: 4}`.deserializeYaml!V.should == V("str", 4);
79 
80     // Multiple Ion annotations represented in a single tag using `::`.
81     `!<rgb::dark_blue> {r: 23, g: 25, b: 55}`.deserializeYaml!V.should == V(RGB("dark_blue", 23, 25, 55));
82 }
83 
84 /// YAML-specific deserialization
85 @safe pure
86 unittest
87 {
88     import mir.algebraic_alias.yaml: YamlAlgebraic, YamlMap;
89     import mir.test: should;
90 
91     auto yaml = `{foo: str, bar: 4}`;
92 
93     auto value = yaml.deserializeYaml!YamlAlgebraic("test.yml");
94     value.object["bar"].should == 4;
95     value.tag.should == `tag:yaml.org,2002:map`;
96     value.startMark.file.should == "test.yml";
97 
98 
99     // `YamlMap`, `YamlAlgebraic[]`, and `Annotated!YamlAlgebraic`
100     // are YAML specific types as well
101     auto object = yaml.deserializeYaml!YamlMap("test.yml");
102     object["bar"].should == 4;
103     object["bar"].tag.should == `tag:yaml.org,2002:int`;
104 
105     assert(value == object);
106 }
107 
108 /// YAML-user-specific deserialization
109 @safe pure
110 unittest
111 {
112     import mir.test: should;
113     import mir.algebraic_alias.yaml: YamlAlgebraic;
114 
115     static struct MyYamlStruct
116     {
117         YamlAlgebraic node;
118 
119         this(YamlAlgebraic node) @safe pure
120         {
121             this.node = node;
122         }
123     }
124 
125     auto s = `{foo: str, bar: 4}`.deserializeYaml!MyYamlStruct("test.yml");
126     s.node.object["bar"].should == 4;
127     s.node.tag.should == `tag:yaml.org,2002:map`;
128 }
129 
130 /++
131 Deserialize YAML documents to an array of scpecified type.
132 Params:
133     T = type of the value
134     text = UTF-8 text (without BOM)
135     fileNam = (optional) file name for better error information
136 Returns:
137     array of type `T`
138 +/
139 // pure
140 T[] deserializeYamlValues(T)(scope const(char)[] text, string fileName = "<unknown>")
141     if (isMutable!T)
142 {
143     import mir.algebraic_alias.yaml: YamlAlgebraic;
144     import mir.array.allocation: array;
145     import mir.ndslice.topology: map;
146     import std.meta: staticIndexOf;
147 
148     static if (is(T == YamlAlgebraic))
149     {
150         import mir.yaml.internal.loader: Loader;
151         pragma(inline, false);
152         return text.Loader(fileName).loadAll;
153     }
154     else
155     static if (staticIndexOf!(T, YamlAlgebraic.AllowedTypes) >= 0)
156     {
157         return text.deserializeYamlValues!YamlAlgebraic(fileName)
158             .map!((ref value) => value.get!T).array;
159     }
160     else
161     static if (is(typeof(YamlAlgebraic[].init.map!T.array())))
162     {
163         return text.deserializeYamlValues!YamlAlgebraic(fileName)
164             .map!T.array;
165     }
166     else
167     {
168         import mir.ion.conv: serde;
169         import mir.serde: SerdeTarget;
170         return text.deserializeYamlValues!YamlAlgebraic(fileName)
171             .map!((scope ref const value) => serde!T(value, SerdeTarget.yaml)).array;
172     }
173 }