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