1 module dpq2.conv.from_bson;
2 
3 import dpq2;
4 import dpq2.exception;
5 import vibe.data.bson;
6 import std.bitmanip: nativeToBigEndian;
7 import std.conv: to;
8 
9 /// Default type will be used for NULL value and for array without detected type
10 @property Value bsonToValue(Bson v, OidType defaultType = OidType.Undefined)
11 {
12     if(v.type == Bson.Type.array)
13         return bsonArrayToValue(v, defaultType);
14     else
15         return bsonValueToValue(v, defaultType);
16 }
17 
18 private:
19 
20 Value bsonValueToValue(Bson v, OidType defaultType)
21 {
22     Value ret;
23 
24     with(Bson.Type)
25     switch(v.type)
26     {
27         case null_:
28             ret = Value(ValueFormat.BINARY, defaultType);
29             break;
30 
31         case Bson.Type.object:
32             ret = v.toJson.toString.toValue;
33             ret.oidType = OidType.Json;
34             break;
35 
36         case bool_:
37             ret = v.get!bool.toValue;
38             break;
39 
40         case int_:
41             ret = v.get!int.toValue;
42             break;
43 
44         case long_:
45             ret = v.get!long.toValue;
46             break;
47 
48         case double_:
49             ret = v.get!double.toValue;
50             break;
51 
52         case Bson.Type..string:
53             ret = v.get!(immutable(char)[]).toValue;
54             break;
55 
56         default:
57             throw new AnswerConvException(
58                     ConvExceptionType.NOT_IMPLEMENTED,
59                     "Format "~v.type.to!(immutable(char)[])~" doesn't supported by Bson to Value converter",
60                     __FILE__, __LINE__
61                 );
62     }
63 
64     return ret;
65 }
66 
67 unittest
68 {
69     {
70         Value v1 = bsonToValue(Bson(123));
71         Value v2 = (123).toValue;
72 
73         assert(v1.as!int == v2.as!int);
74     }
75 
76     {
77         Value v1 = bsonToValue(Bson("Test string"));
78         Value v2 = ("Test string").toValue;
79 
80         assert(v1.as!string == v2.as!string);
81     }
82 
83     {
84         Value t = bsonToValue(Bson(true));
85         Value f = bsonToValue(Bson(false));
86 
87         assert(t.as!bool == true);
88         assert(f.as!bool == false);
89     }
90 }
91 
92 Value bsonArrayToValue(ref Bson bsonArr, OidType defaultType)
93 {
94     ubyte[] nullValue() pure
95     {
96         ubyte[] ret = [0xff, 0xff, 0xff, 0xff]; //NULL magic number
97         return ret;
98     }
99 
100     ubyte[] rawValue(Value v) pure
101     {
102         if(v.isNull)
103         {
104             return nullValue();
105         }
106         else
107         {
108             return v._data.length.to!uint.nativeToBigEndian ~ v._data;
109         }
110     }
111 
112     ArrayProperties ap;
113     ubyte[] rawValues;
114 
115     void recursive(ref Bson bsonArr, int dimension)
116     {
117         if(dimension == ap.dimsSize.length)
118         {
119             ap.dimsSize ~= bsonArr.length.to!int;
120         }
121         else
122         {
123             if(ap.dimsSize[dimension] != bsonArr.length)
124                 throw new AnswerConvException(ConvExceptionType.NOT_ARRAY, "Jagged arrays are unsupported", __FILE__, __LINE__);
125         }
126 
127         foreach(bElem; bsonArr)
128         {
129             ap.nElems++;
130 
131             switch(bElem.type)
132             {
133                 case Bson.Type.array:
134                     recursive(bElem, dimension + 1);
135                     break;
136 
137                 case Bson.Type.null_:
138                     rawValues ~= nullValue();
139                     break;
140 
141                 default:
142                     Value v = bsonValueToValue(bElem, OidType.Undefined);
143 
144                     if(ap.OID == OidType.Undefined)
145                     {
146                         ap.OID = v.oidType;
147                     }
148                     else
149                     {
150                         if(ap.OID != v.oidType)
151                             throw new AnswerConvException(
152                                     ConvExceptionType.NOT_ARRAY,
153                                     "Bson (which used for creating "~ap.OID.to!string~" array) also contains value of type "~v.oidType.to!string,
154                                     __FILE__, __LINE__
155                                 );                    
156                     }
157 
158                     rawValues ~= rawValue(v);
159             }
160         }
161     }
162 
163     recursive(bsonArr, 0);
164 
165     if(ap.OID == OidType.Undefined) ap.OID = defaultType.oidConvTo!"element";
166 
167     ArrayHeader_net h;
168     h.ndims = nativeToBigEndian(ap.dimsSize.length.to!int);
169     h.OID = nativeToBigEndian(ap.OID.to!Oid);
170 
171     ubyte[] ret;
172     ret ~= (cast(ubyte*) &h)[0 .. h.sizeof];
173 
174     foreach(i; 0 .. ap.dimsSize.length)
175     {
176         Dim_net dim;
177         dim.dim_size = nativeToBigEndian(ap.dimsSize[i]);
178         dim.lbound = nativeToBigEndian!int(1);
179 
180         ret ~= (cast(ubyte*) &dim)[0 .. dim.sizeof];
181     }
182 
183     ret ~= rawValues;
184 
185     return Value(ret, ap.OID.oidConvTo!"array", false, ValueFormat.BINARY);
186 }
187 
188 unittest
189 {
190     {
191         Bson bsonArray = Bson(
192             [Bson(123), Bson(155), Bson(null), Bson(0), Bson(null)]
193         );
194 
195         Value v = bsonToValue(bsonArray);
196 
197         assert(v.isSupportedArray);
198         assert(v.as!Bson == bsonArray);
199     }
200 
201     {
202         Bson bsonArray = Bson([
203             Bson([Bson(123), Bson(155), Bson(null)]),
204             Bson([Bson(0), Bson(null), Bson(155)])
205         ]);
206 
207         Value v = bsonToValue(bsonArray);
208 
209         assert(v.isSupportedArray);
210         assert(v.as!Bson == bsonArray);
211     }
212 
213     {
214         Bson bsonArray = Bson([
215             Bson([Bson(123), Bson(155)]),
216             Bson([Bson(0)])
217         ]);
218 
219         bool exceptionFlag = false;
220 
221         try
222             bsonToValue(bsonArray);
223         catch(AnswerConvException e)
224         {
225             if(e.type == ConvExceptionType.NOT_ARRAY)
226                 exceptionFlag = true;
227         }
228 
229         assert(exceptionFlag);
230     }
231 }