1 module dash.utility.data.serialization;
2 import dash.utility.data.yaml;
3 import dash.utility.resources, dash.utility.math, dash.utility.output;
4 
5 import vibe.data.json, vibe.data.bson;
6 import std.typecons: Tuple, tuple;
7 import std.typetuple;
8 import std.math;
9 
10 // Serialization attributes
11 public import vibe.data.serialization: rename = name, asArray, byName, ignore, optional, isCustomSerializable;
12 
13 /**
14  * Modes of serialization.
15  */
16 enum SerializationMode
17 {
18     Default,
19     Json,
20     Bson,
21     Yaml,
22 }
23 
24 /**
25  * Deserializes a file.
26  *
27  * Params:
28  *  fileName =          The name of the file to deserialize.
29  *
30  * Returns: The deserialized object.
31  */
32 Tuple!( T, Resource ) deserializeFileByName( T )( string fileName, SerializationMode mode = SerializationMode.Default )
33 {
34     import std.path: dirName, baseName;
35     import std.array: empty, front;
36 
37     auto files = fileName.dirName.scanDirectory( fileName.baseName ~ ".*" );
38     return files.empty
39         ? tuple( T.init, internalResource )
40         : tuple( deserializeFile!T( files.front ), files.front );
41 }
42 
43 /**
44  * Deserializes a file.
45  *
46  * Params:
47  *  file =              The name of the file to deserialize.
48  *
49  * Returns: The deserialized object.
50  */
51 T deserializeFile( T )( Resource file, SerializationMode mode = SerializationMode.Default )
52 {
53     import std.path: extension;
54     import std..string: toLower;
55 
56     T handleJson()
57     {
58         return deserializeJson!T( file.readText().parseJsonString() );
59     }
60 
61     T handleBson()
62     {
63         return deserializeBson!T( Bson( Bson.Type.object, file.read().idup ) );
64     }
65 
66     T handleYaml()
67     {
68         return deserializeYaml!T( Loader( file.fullPath ).load() );
69     }
70 
71     try
72     {
73         final switch( mode ) with( SerializationMode )
74         {
75             case Json: return handleJson();
76             case Bson: return handleBson();
77             case Yaml: return handleYaml();
78             case Default:
79                 switch( file.extension.toLower )
80                 {
81                     case ".json": return handleJson();
82                     case ".bson": return handleBson();
83                     case ".yaml":
84                     case ".yml":  return handleYaml();
85                     default: throw new Exception( "File extension " ~ file.extension.toLower ~ " not supported." );
86                 }
87         }
88     }
89     catch( Exception e )
90     {
91         errorf( "Error deserializing file %s to type %s: %s", file.fileName, T.stringof, e.msg );
92         return T.init;
93     }
94 }
95 
96 /**
97  * Deserializes a file with multiple documents.
98  *
99  * Params:
100  *  file =              The name of the file to deserialize.
101  *
102  * Returns: The deserialized object.
103  */
104 T[] deserializeMultiFile( T )( Resource file, SerializationMode mode = SerializationMode.Default )
105 {
106     import std.path: extension;
107     import std..string: toLower;
108 
109     T[] handleJson()
110     {
111         return [deserializeFile!T( file, SerializationMode.Json )];
112     }
113 
114     T[] handleBson()
115     {
116         return [deserializeFile!T( file, SerializationMode.Bson )];
117     }
118 
119     T[] handleYaml()
120     {
121         import std.algorithm: map;
122         import std.array: array;
123         return Loader( file.fullPath )
124             .loadAll()
125             .map!( node => node.deserializeYaml!T() )
126             .array();
127     }
128 
129     try
130     {
131         final switch( mode ) with( SerializationMode )
132         {
133             case Json: return handleJson();
134             case Bson: return handleBson();
135             case Yaml: return handleYaml();
136             case Default:
137                 switch( file.extension.toLower )
138                 {
139                     case ".json": return handleJson();
140                     case ".bson": return handleBson();
141                     case ".yaml":
142                     case ".yml":  return handleYaml();
143                     default: throw new Exception( "File extension " ~ file.extension.toLower ~ " not supported." );
144                 }
145         }
146     }
147     catch( Exception e )
148     {
149         errorf( "Error deserializing file %s to type %s: %s", file.fileName, T.stringof, e.msg );
150         return [];
151     }
152 }
153 
154 /**
155  * Serializes an object to a file.
156  */
157 template serializeToFile( bool prettyPrint = true )
158 {
159     void serializeToFile( T )( T t, string outPath, SerializationMode mode = SerializationMode.Default )
160     {
161         import std.path: extension;
162         import std..string: toLower;
163         import std.file: write;
164         import std.array: appender;
165 
166         void handleJson()
167         {
168             auto json = appender!string;
169             writeJsonString!( typeof(json), prettyPrint )( json, serializeToJson( t ) );
170             write( outPath, json.data );
171         }
172 
173         void handleBson()
174         {
175             write( outPath, serializeToBson( t ).data );
176         }
177 
178         void handleYaml()
179         {
180             Dumper( outPath ).dump( serializeToYaml( t ) );
181         }
182 
183         try
184         {
185             final switch( mode ) with( SerializationMode )
186             {
187                 case Json: handleJson(); break;
188                 case Bson: handleBson(); break;
189                 case Yaml: handleYaml(); break;
190                 case Default:
191                     switch( outPath.extension.toLower )
192                     {
193                         case ".json": handleJson(); break;
194                         case ".bson": handleBson(); break;
195                         case ".yaml":
196                         case ".yml":  handleYaml(); break;
197                         default: throw new Exception( "File extension " ~ outPath.extension.toLower ~ " not supported." );
198                     }
199                     break;
200             }
201         }
202         catch( Exception e )
203         {
204             errorf( "Error serializing %s to file %s: %s", T.stringof, file.fileName, e.msg );
205         }
206     }
207 }
208 
209 /// Supported serialization formats.
210 enum serializationFormats = tuple( "Json", "Bson", "Yaml" );
211 
212 /// Type to use when defining custom
213 struct CustomSerializer( _T, _Rep, alias _ser, alias _deser, alias _check )
214     if( is( typeof( _ser( _T.init ) ) == _Rep ) &&
215         is( typeof( _deser( _Rep.init ) ) == _T ) &&
216         is( typeof( _check( _Rep.init ) ) == bool ) )
217 {
218     /// The type being serialized
219     alias T = _T;
220     /// The serialized representation
221     alias Rep = _Rep;
222     /// Function to convert the type to its rep
223     alias serialize = _ser;
224     /// Function to convert the rep to the type
225     alias deserialize = _deser;
226     /// Function called to ensure the representation is valid
227     alias check = _check;
228 }
229 
230 /// For calling templated templates.
231 template PApply( alias Target, T... )
232 {
233     alias PApply( U... ) = Target!( T, U );
234 }
235 
236 /// Checks if a serializer is for a type.
237 enum serializerTypesMatch( Type, alias CS ) = is( CS.T == Type );
238 
239 /// Predicate for std.typetupple
240 alias isSerializerFor( T ) = PApply!( serializerTypesMatch, T );
241 
242 /// Does a given type have a serializer
243 enum hasSerializer( T ) = anySatisfy!( isSerializerFor!T, customSerializers );
244 
245 /// Get the serializer for a type
246 template serializerFor( T )
247 {
248     static if( hasSerializer!T )
249         alias serializerFor = Filter!( isSerializerFor!T, customSerializers )[ 0 ];
250     else
251         alias serializerFor = defaultSerializer!T;
252 }
253 
254 /// A tuple of all supported serializers
255 alias customSerializers = TypeTuple!(
256     CustomSerializer!( vec2f, float[], vec => vec.vector[], arr => vec2f( arr ), arr => arr.length == 2 ),
257     CustomSerializer!( vec3f, float[], vec => vec.vector[], arr => vec3f( arr ), arr => arr.length == 3 ),
258     CustomSerializer!( quatf, float[], vec => vec.toEulerAngles.vector[], arr => fromEulerAngles( arr ), arr => arr.length == 3 ),
259 );
260 static assert( hasSerializer!vec2f );
261 static assert( hasSerializer!vec3f );
262 static assert( hasSerializer!quatf );
263 
264 /// Serializer for all other types
265 alias defaultSerializer( T ) = CustomSerializer!( T, T, t => t, t => t, t => true );