1 /++
2 Authros: Ilya Yaroshenko
3 +/
4 module mir.ion.ser.bloomberg;
5 
6 import mir.ion.exception: IonException;
7 public import mir.bloomberg.blpapi : BloombergElement = Element;
8 static import blpapi = mir.bloomberg.blpapi;
9 
10 private alias validate = blpapi.validateBloombergErroCode;
11 
12 private static immutable bloombergClobSerializationIsntImplemented = new IonException("Bloomberg CLOB serialization isn't implemented.");
13 private static immutable bloombergBlobSerializationIsntImplemented = new IonException("Bloomberg BLOB serialization isn't implemented.");
14 
15 /++
16 Ion serialization back-end
17 +/
18 struct BloombergSerializer()
19 {
20     import mir.format: stringBuf, getData;
21     import mir.bignum.decimal: Decimal;
22     import mir.bignum.low_level_view: WordEndian;
23     import mir.bignum.integer: BigInt;
24     import mir.ion.type_code;
25     import mir.lob;
26     import mir.timestamp;
27     import mir.serde: SerdeTarget;
28     import std.traits: isNumeric;
29 
30     BloombergElement* nextValue;
31 
32     BloombergElement* aggregateValue;
33 
34     stringBuf currentPartString;
35 
36     /// Mutable value used to choose format specidied or user-defined serialization specializations
37     int serdeTarget = SerdeTarget.bloomberg;
38 
39     private uint valueIndex;
40 
41 @safe pure:
42 
43     private const(char)* toScopeStringz(scope const(char)[] value) @trusted return scope nothrow
44     {
45         currentPartString.reset;
46         currentPartString.put(value);
47         currentPartString.put('\0');
48         return currentPartString.data.ptr;
49     }
50 
51     private void pushState(BloombergElement* state)
52     {
53         aggregateValue = state;
54         nextValue = null;
55     }
56 
57     private BloombergElement* popState()
58     {
59         auto state = aggregateValue;
60         aggregateValue = nextValue;
61         nextValue = null;
62         return state;
63     }
64 
65     BloombergElement* stringBegin()
66     {
67         currentPartString.reset;
68         return null;
69     }
70 
71     /++
72     Puts string part. The implementation allows to split string unicode points.
73     +/
74     void putStringPart(scope const char[] value)
75     {
76         import mir.format: printEscaped, EscapeFormat;
77         currentPartString.put(value);
78     }
79 
80     void stringEnd(BloombergElement*) @trusted
81     {
82         if (currentPartString.length == 1)
83         {
84             blpapi.setValueChar(nextValue, *currentPartString.data.ptr, valueIndex).validate;
85         }
86         else
87         {
88             currentPartString.put('\0');
89             blpapi.setValueString(nextValue, currentPartString.data.ptr, valueIndex).validate;
90         }
91     }
92 
93     private blpapi.Name* getName(scope const char* str)
94     {
95         if (auto name = blpapi.nameFindName(str))
96             return name;
97         return blpapi.nameCreate(str);
98     }
99 
100     private blpapi.Name* getName(scope const char[] str)
101     {
102         return getName(toScopeStringz(str));
103     }
104 
105     void putSymbolPtr(scope const char* value)
106     {
107         auto name = getName(value);
108         blpapi.setValueFromName(nextValue, name, valueIndex).validate;
109         blpapi.nameDestroy(name);
110     }
111 
112     void putSymbol(scope const char[] value)
113     {
114         return putSymbolPtr(toScopeStringz(value));
115     }
116 
117     BloombergElement* structBegin(size_t length = 0)
118     {
119         return popState;
120     }
121 
122     void structEnd(BloombergElement* state)
123     {
124         pushState(state);
125     }
126 
127     BloombergElement* listBegin(size_t length = 0)
128     {
129         valueIndex = uint.max;
130         return null;
131     }
132 
133     void listEnd(BloombergElement* state)
134     {
135         valueIndex = 0;
136         nextValue = null;
137     }
138 
139     alias sexpBegin = listBegin;
140 
141     alias sexpEnd = listEnd;
142 
143     BloombergElement* annotationsBegin()
144     {
145         return aggregateValue;
146     }
147 
148     void putAnnotationPtr(scope const char* value)
149     {
150         aggregateValue = nextValue;
151         auto name = getName(value);
152         blpapi.setChoice(nextValue, nextValue, null, name, 0).validate;
153         blpapi.nameDestroy(name);
154     }
155 
156     void putAnnotation(scope const char[] value) @trusted
157     {
158         putAnnotationPtr(toScopeStringz(value));
159     }
160 
161     void annotationsEnd(BloombergElement* state)
162     {
163         aggregateValue = state;
164     }
165 
166     BloombergElement* annotationWrapperBegin()
167     {
168         return null;
169     }
170 
171     void annotationWrapperEnd(BloombergElement*)
172     {
173     }
174 
175     void nextTopLevelValue()
176     {
177         static immutable exc = new IonException("Can't serialize to multiple Bloomberg Elements at once.");
178         throw exc;
179     }
180 
181     void putKeyPtr(scope const char* key)
182     {
183         nextValue = null;
184         auto name = getName(key);
185         blpapi.getElement(aggregateValue, nextValue, null, name).validate;
186         blpapi.nameDestroy(name);
187         assert(nextValue !is null);
188     }
189 
190     void putKey(scope const char[] key)
191     {
192         putKeyPtr(toScopeStringz(key));
193     }
194 
195     void putValue(Num)(const Num value)
196         if (isNumeric!Num && !is(Num == enum))
197     {
198         import mir.internal.utility: isFloatingPoint;
199 
200         assert(nextValue);
201         static if (isFloatingPoint!Num)
202         {
203             if (float(value) is value)
204             {
205                 blpapi.setValueFloat32(nextValue, value, valueIndex).validate;
206             }
207             else
208             {
209                 blpapi.setValueFloat64(nextValue, value, valueIndex).validate;
210             }
211         }
212         else
213         static if (is(Num == int) || Num.sizeof <= 2)
214         {
215             static if (is(Num == ulong))
216             {
217                 if (value > long.max)
218                 {
219                     static immutable exc = new SerdeException("BloombergSerializer: integer overflow");
220                     throw exc;
221                 }
222             }
223 
224             (cast(int) value == cast(long) value
225                  ? blpapi.setValueInt32(nextValue, value, 0)
226                  : blpapi.setValueInt64(nextValue, value, 0))
227                  .validate;
228         }
229     }
230 
231     void putValue(W, WordEndian endian)(BigIntView!(W, endian) view)
232     {
233         auto i = cast(long) view;
234         if (view != i)
235         {
236             static immutable exc = new SerdeException("BloombergSerializer: integer overflow");
237             throw exc;
238         }
239         putValue(num);
240     }
241 
242     void putValue(size_t size)(auto ref const BigInt!size num)
243     {
244         putValue(num.view);
245     }
246 
247     void putValue(size_t size)(auto ref const Decimal!size num)
248     {
249         putValue(cast(double)num);
250     }
251 
252     void putValue(typeof(null))
253     {
254         assert(nextValue);
255     }
256 
257     /// ditto 
258     void putNull(IonTypeCode code)
259     {
260         putValue(null);
261     }
262 
263     void putValue(bool b)
264     {
265         assert(nextValue);
266         blpapi.setValueBool(nextValue, b, valueIndex).validate;
267     }
268 
269     void putValue(scope const char[] value)
270     {
271         auto state = stringBegin;
272         putStringPart(value);
273         stringEnd(state);
274     }
275 
276     void putValue(Clob value)
277     {
278         throw bloombergClobSerializationIsntImplemented;
279     }
280 
281     void putValue(Blob value)
282     {
283         throw bloombergBlobSerializationIsntImplemented;
284     }
285 
286     void putValue(Timestamp value)
287     {
288         blpapi.HighPrecisionDatetime dt = value;
289         blpapi.setValueHighPrecisionDatetime(nextValue, dt, valueIndex).validate;
290     }
291 
292     void elemBegin()
293     {
294     }
295 
296     alias sexpElemBegin = elemBegin;
297 }
298 
299 private static immutable excBytearray = new Exception("Mir Bloomberg: unexpected data type: bytearray");
300 private static immutable excCorrelationOd = new Exception("Mir Bloomberg: unexpected data type: correlation_id");
301 
302 ///
303 void serializeValue(S)(ref S serializer, const(BloombergElement)* value)
304 {
305     import core.stdc.string: strlen;
306     import mir.ion.type_code;
307     import mir.timestamp: Timestamp;
308     static import blpapi = mir.bloomberg.blpapi;
309     import mir.bloomberg.blpapi: validate = validateBloombergErroCode;
310 
311     if (value is null || blpapi.isNull(value))
312     {
313         serializer.putValue(null);
314         return;
315     }
316 
317     auto isArray = blpapi.isArray(value);
318     auto type = blpapi.datatype(value);
319     size_t arrayLength = 1;
320     typeof(serializer.listBegin(arrayLength)) arrayState;
321     if (isArray)
322     {
323         arrayLength = blpapi.numValues(value);
324         arrayState = serializer.listBegin(arrayLength);
325         if (type == blpapi.DataType.choice || type == blpapi.DataType.sequence)
326         {
327             foreach(index; 0 .. arrayLength)
328             {
329                 BloombergElement* v;
330                 blpapi.getValueAsElement(value, v, index).validate;
331                 serializer.elemBegin;
332                 serializer.serializeValue(v);
333             }
334             serializer.listEnd(arrayState);
335             return;
336         }
337     }
338     foreach(index; 0 .. arrayLength)
339     {
340         if (isArray)
341             serializer.elemBegin;
342         final switch (type)
343         {
344             case blpapi.DataType.null_:
345                 serializer.putValue(null);
346                 continue;
347             case blpapi.DataType.bool_: {
348                 blpapi.Bool v;
349                 blpapi.getValueAsBool(value, v, index).validate;
350                 serializer.putValue(cast(bool)v);
351                 continue;
352             }
353             case blpapi.DataType.char_: {
354                 char[1] v;
355                 blpapi.getValueAsChar(value, v[0], index).validate;
356                 serializer.putValue(v[]);
357                 continue;
358             }
359             case blpapi.DataType.byte_:
360             case blpapi.DataType.int32: {
361                 int v;
362                 blpapi.getValueAsInt32(value, v, index).validate;
363                 serializer.putValue(v);
364                 continue;
365             }
366             case blpapi.DataType.int64: {
367                 long v;
368                 blpapi.getValueAsInt64(value, v, index).validate;
369                 serializer.putValue(v);
370                 continue;
371             }
372             case blpapi.DataType.float32: {
373                 float v;
374                 blpapi.getValueAsFloat32(value, v, index).validate;
375                 serializer.putValue(v);
376                 continue;
377             }
378             case blpapi.DataType.decimal:
379             case blpapi.DataType.float64: {
380                 double v;
381                 blpapi.getValueAsFloat64(value, v, index).validate;
382                 serializer.putValue(v);
383                 continue;
384             }
385             case blpapi.DataType..string: {
386                 const(char)* v;
387                 blpapi.getValueAsString(value, v, index).validate;
388                 serializer.putValue(v[0 .. (()@trusted => v.strlen)()]);
389                 continue;
390             }
391             case blpapi.DataType.date:
392             case blpapi.DataType.time:
393             case blpapi.DataType.datetime: {
394                 blpapi.HighPrecisionDatetime v;
395                 blpapi.getValueAsHighPrecisionDatetime(value, v, index).validate;
396                 if (v.parts)
397                     serializer.putValue(cast(Timestamp)v);
398                 else
399                     serializer.putNull(IonTypeCode.timestamp);
400                 continue;
401             }
402             case blpapi.DataType.enumeration: {
403                 const(char)* v;
404                 blpapi.getValueAsString(value, v, index).validate;
405                 if (v is null)
406                     serializer.putNull(IonTypeCode.symbol);
407                 else
408                     serializer.putSymbol(v[0 .. (()@trusted => v.strlen)()]);
409                 continue;
410             }
411             case blpapi.DataType.sequence: {
412                 auto length = blpapi.numElements(value);
413                 auto state = serializer.structBegin(length);
414                 foreach(i; 0 .. length)
415                 {
416                     BloombergElement* v;
417                     blpapi.getElementAt(value, v, i).validate;
418                     blpapi.Name* name = blpapi.name(v);
419                     const(char)* keyPtr = name ? blpapi.nameString(name) :  null;
420                     auto key = keyPtr ? keyPtr[0 .. (()@trusted => keyPtr.strlen)()] : null;
421                     serializer.putKey(key);
422                     serializer.serializeValue(v);
423                 }
424                 serializer.structEnd(state);
425                 continue;
426             }
427             case blpapi.DataType.choice: {
428                 auto wrapperState = serializer.annotationWrapperBegin;
429                 auto annotationsState = serializer.annotationsBegin;
430                 do
431                 {
432                     BloombergElement* v;
433                     blpapi.getChoice(value, v).validate;
434                     blpapi.Name* name = blpapi.name(v);
435                     const(char)* annotationPtr = name ? blpapi.nameString(name) :  null;
436                     auto annotation = annotationPtr ? annotationPtr[0 .. (()@trusted => annotationPtr.strlen)()] : null;
437                     serializer.putAnnotation(annotation);
438                     value = v;
439                 }
440                 while (blpapi.datatype(value) == blpapi.DataType.choice);
441                 serializer.annotationsEnd(annotationsState);
442                 serializer.serializeValue(value);
443                 serializer.annotationWrapperEnd(wrapperState);
444                 continue;
445             }
446             case blpapi.DataType.bytearray:
447                 throw excBytearray;
448             case blpapi.DataType.correlation_id:
449                 throw excCorrelationOd;
450         }
451     }
452     if (isArray)
453     {
454         serializer.listEnd(arrayState);
455     }
456 }
457 
458 version(mir_ion_test)
459 unittest
460 {
461     import mir.ion.ser.bloomberg;
462     import mir.ion.ser.ion;
463     import mir.ion.ser.json;
464     import mir.ion.ser.text;
465     BloombergSerializer!() ser;
466     BloombergElement* value;
467     serializeValue(ser, value.init);
468     auto text = value.serializeText;
469     auto json = value.serializeJson;
470     auto ion = value.serializeIon;
471 }