1 /*
2  * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design.
3  *
4  * Copyright (C) 2015-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.framework.util.uninode.Core;
13 
14 private
15 {
16     import std.array : appender;
17     import std.conv : to;
18     import std.format : fmt = format;
19     import std.traits;
20     import std.traits : isTraitsArray = isArray;
21     import std.variant : maxSize;
22 }
23 
24 
25 import hunt.logging;
26 
27 /**
28  * UniNode implementation
29  */
30 struct UniNodeImpl(This)
31 {
32 @safe:
33     private nothrow
34     {
35         alias Bytes = immutable(ubyte)[];
36 
37         union U
38         {
39             typeof(null) nil;
40             bool boolean;
41             ulong uinteger;
42             long integer;
43             real floating;
44             string text;
45             Bytes raw;
46             This[] array;
47             This[string] object;
48         }
49 
50         struct SizeChecker
51         {
52             int function() fptr;
53             ubyte[maxSize!U] data;
54         }
55 
56         enum size = SizeChecker.sizeof - (int function()).sizeof;
57 
58         union
59         {
60             ubyte[size] _store;
61             // conservatively mark the region as pointers
62             static if (size >= (void*).sizeof)
63                 void*[size / (void*).sizeof] p;
64         }
65 
66         Kind _kind;
67 
68         ref inout(T) getDataAs(T)() inout @trusted
69         {
70             static assert(T.sizeof <= _store.sizeof, "Size errro");
71             return (cast(inout(T)[1])_store[0 .. T.sizeof])[0];
72         }
73 
74         @property ref inout(This[string]) _object() inout
75         {
76             return getDataAs!(This[string])();
77         }
78 
79         @property ref inout(This[]) _array() inout
80         {
81             return getDataAs!(This[])();
82         }
83 
84         @property ref inout(bool) _bool() inout
85         {
86             return getDataAs!bool();
87         }
88 
89         @property ref inout(long) _int() inout
90         {
91             return getDataAs!long();
92         }
93 
94         @property ref inout(ulong) _uint() inout
95         {
96             return getDataAs!ulong();
97         }
98 
99         @property ref inout(real) _float() inout
100         {
101             return getDataAs!real();
102         }
103 
104         @property ref inout(string) _string() inout
105         {
106             return getDataAs!string();
107         }
108 
109         @property ref inout(Bytes) _raw() inout
110         {
111             return getDataAs!(Bytes)();
112         }
113     }
114 
115 
116     alias Kind = TypeEnum!U;
117 
118 
119     Kind kind() @property inout nothrow pure
120     {
121         return _kind;
122     }
123 
124     /**
125      * Construct UniNode null value
126      */
127     this(typeof(null)) inout nothrow
128     {
129         _kind = Kind.nil;
130     }
131 
132     /**
133      * Check node is null
134      */
135     bool isNull() inout nothrow pure
136     {
137         return _kind == Kind.nil;
138     }
139 
140 
141     @safe unittest
142     {
143         auto node = immutable(UniNode)(null);
144         assert (node.isNull);
145         auto node2 = immutable(UniNode)();
146         assert (node2.isNull);
147     }
148 
149     /**
150      * Construct UniNode from unsigned number value
151      */
152     this(T)(T val) inout nothrow if (isUnsignedNumeric!T)
153     {
154         _kind = Kind.uinteger;
155         (cast(ulong)_uint) = val;
156     }
157 
158 
159     @safe unittest
160     {
161         import std.meta : AliasSeq;
162         foreach (TT; AliasSeq!(ubyte, ushort, uint, ulong))
163         {
164             TT v = cast(TT)11U;
165             auto node = immutable(UniNode)(v);
166             assert (node.kind == UniNode.Kind.uinteger);
167             assert (node.get!TT == cast(TT)11U);
168         }
169     }
170 
171     /**
172      * Construct UniNode from signed number value
173      */
174     this(T)(T val) inout nothrow if (isSignedNumeric!T)
175     {
176         _kind = Kind.integer;
177         (cast(long)_int) = val;
178     }
179 
180 
181     @safe unittest
182     {
183         import std.meta : AliasSeq;
184         foreach (TT; AliasSeq!(byte, short, int, long))
185         {
186             TT v = -11;
187             auto node = UniNode(v);
188             assert (node.kind == UniNode.Kind.integer);
189             assert (node.get!TT == cast(TT)-11);
190         }
191     }
192 
193     /**
194      * Construct UniNode from boolean value
195      */
196     this(T)(T val) inout nothrow if (isBoolean!T)
197     {
198         _kind = Kind.boolean;
199         (cast(bool)_bool) = val;
200     }
201 
202 
203     @safe unittest
204     {
205         auto node = UniNode(true);
206         assert (node.kind == UniNode.Kind.boolean);
207         assert (node.get!bool == true);
208 
209         auto nodei = UniNode(0);
210         assert (nodei.kind == UniNode.Kind.integer);
211     }
212 
213     /**
214      * Construct UniNode from floating value
215      */
216     this(T)(T val) inout nothrow if (isFloatingPoint!T)
217     {
218         _kind = Kind.floating;
219         (cast(real)_float) = val;
220     }
221 
222 
223     @safe unittest
224     {
225         import std.meta : AliasSeq;
226         foreach (TT; AliasSeq!(float, double))
227         {
228             TT v = 11.11;
229             auto node = UniNode(v);
230             assert (node.kind == UniNode.Kind.floating);
231             assert (node.get!TT == cast(TT)11.11);
232         }
233     }
234 
235     /**
236      * Construct UniNode from string
237      */
238     this(string val) inout nothrow
239     {
240         _kind = Kind.text;
241         (cast(string)_string) = val;
242     }
243 
244 
245     @safe unittest
246     {
247         string str = "hello";
248         auto node = UniNode(str);
249         assert(node.kind == UniNode.Kind.text);
250         assert (node.get!(string) == "hello");
251     }
252 
253     /**
254      * Construct UniNode from byte array
255      */
256     this(T)(T val) inout nothrow if (isRawData!T)
257     {
258         _kind = Kind.raw;
259         static if (isStaticArray!T || isMutable!T)
260             (cast(Bytes)_raw) = val.idup;
261         else
262             (cast(Bytes)_raw) = val;
263     }
264 
265 
266     @safe unittest
267     {
268         ubyte[] dynArr = [1, 2, 3];
269         auto node = UniNode(dynArr);
270         assert (node.kind == UniNode.Kind.raw);
271         assert (node.get!(ubyte[]) == [1, 2, 3]);
272 
273         ubyte[3] stArr = [1, 2, 3];
274         node = UniNode(stArr);
275         assert (node.kind == UniNode.Kind.raw);
276         assert (node.get!(ubyte[3]) == [1, 2, 3]);
277 
278         Bytes bb = [1, 2, 3];
279         node = UniNode(bb);
280         assert (node.kind == UniNode.Kind.raw);
281         assert (node.get!(ubyte[]) == [1, 2, 3]);
282     }
283 
284     /**
285      * Construct array UniNode
286      */
287     this(This[] val) nothrow
288     {
289         _kind = Kind.array;
290         _array = val;
291     }
292 
293     /**
294      * Construct empty UniNode array
295      */
296     static This emptyArray() nothrow
297     {
298         return This(cast(This[])null);
299     }
300 
301     /**
302      * Check node is array
303      */
304     bool isArray() inout pure nothrow
305     {
306         return _kind == Kind.array;
307     }
308 
309 
310     @safe unittest
311     {
312         auto node = UniNode.emptyArray;
313         assert(node.isArray);
314         assert(node.length == 0);
315     }
316 
317     /**
318      * Construct object UnoNode
319      */
320     this(This[string] val) nothrow
321     {
322         _kind = Kind.object;
323         _object = val;
324     }
325 
326     /**
327      * Construct empty UniNode object
328      */
329     static This emptyObject() nothrow
330     {
331         return This(cast(This[string])null);
332     }
333 
334     /**
335      * Check node is object
336      */
337     bool isObject() inout nothrow pure
338     {
339         return _kind == Kind.object;
340     }
341 
342 
343     @safe unittest
344     {
345         auto node = UniNode.emptyObject;
346         assert(node.isObject);
347     }
348 
349 
350     size_t length() const @property
351     {
352         switch (_kind) with (Kind)
353         {
354             case text:
355                 return _string.length;
356             case raw:
357                 return _raw.length;
358             case array:
359                 return _array.length;
360             case object:
361                 return _object.length;
362             default:
363                 enforceUniNode(false, "Expected " ~ This.stringof ~ " not length");
364                 assert(false, "Nothing");
365         }
366     }
367 
368 
369     alias opDollar = length;
370 
371     /**
372      * Return value from UnoNode
373      */
374     inout(T) get(T)() inout @trusted if (isUniNodeType!(T, This))
375     {
376         static if (isSignedNumeric!T)
377         {
378             if (_kind == Kind.uinteger)
379             {
380                 auto val = _uint;
381                 enforceUniNode(val < T.max, "Unsigned value great max");
382                 return cast(T)(val);
383             }
384             checkType!T(Kind.integer);
385             return cast(T)(_int);
386         }
387         else static if (isUnsignedNumeric!T)
388         {
389             if (_kind == Kind.integer)
390             {
391                 auto val = _int;
392                 enforceUniNode(val >= 0, "Signed value less zero");
393                 return cast(T)(val);
394             }
395             checkType!T(Kind.uinteger);
396             return cast(T)(_uint);
397         }
398         else static if (isBoolean!T)
399         {
400             checkType!T(Kind.boolean);
401             return _bool;
402         }
403         else static if (isFloatingPoint!T)
404         {
405             if (_kind == Kind.integer)
406                 return cast(T)(_int);
407             if (_kind == Kind.uinteger)
408                 return cast(T)(_uint);
409 
410             checkType!T(Kind.floating);
411             return cast(T)(_float);
412         }
413         else static if (isSomeString!T)
414         {
415             if (_kind == Kind.raw)
416                 return cast(T)_raw;
417             checkType!T(Kind.text);
418             return _string;
419         }
420         else static if (isRawData!T)
421         {
422             checkType!T(Kind.raw);
423             static if (isStaticArray!T)
424                 return cast(inout(T))_raw[0..T.length];
425             else
426                 return cast(inout(T))_raw;
427         }
428         else static if (isUniNodeArray!(T, This))
429         {
430             checkType!T(Kind.array);
431             return _array;
432         }
433         else static if (isUniNodeObject!(T, This))
434         {
435             checkType!T(Kind.object);
436             return _object;
437         }
438         else
439             enforceUniNode(false, fmt!"Not support type '%s'"(T.stringof));
440     }
441 
442 
443     private int _opApply(F)(scope F dg)
444     {
445         auto fun = assumeSafe!F(dg);
446         alias Params = Parameters!F;
447 
448         static if (Params.length == 1 && is(Unqual!(Params[0]) : This))
449         {
450             enforceUniNode(_kind == Kind.array,
451                     "Expected " ~ This.stringof ~ " array");
452             foreach (ref node; _array)
453             {
454                 if (auto ret = fun(node))
455                     return ret;
456             }
457         }
458         else static if (Params.length == 2 && is(Unqual!(Params[1]) : This))
459         {
460             static if (isSomeString!(Params[0]))
461             {
462                 enforceUniNode(_kind == Kind.object,
463                         "Expected " ~ This.stringof ~ " object");
464                 foreach (string key, ref node; _object)
465                 {
466                     if (auto ret = fun(key, node))
467                         return ret;
468                 }
469             }
470             else
471             {
472                 enforceUniNode(_kind == Kind.array,
473                         "Expected " ~ This.stringof ~ " array");
474 
475                 foreach (size_t key, ref node; _array)
476                 {
477                     if (auto ret = fun(key, node))
478                         return ret;
479                 }
480             }
481         }
482 
483         return 0;
484     }
485 
486     /**
487      * Iteration by UnoNode array or object
488      */
489     int opApply(scope int delegate(ref size_t idx, ref This node) dg)
490     {
491         return _opApply!(int delegate(ref size_t idx, ref This node))(dg);
492     }
493 
494     /**
495      * Iteration by UnoNode object
496      */
497     int opApply(scope int delegate(ref string idx, ref This node) dg)
498     {
499         return _opApply!(int delegate(ref string idx, ref This node))(dg);
500     }
501 
502     /**
503      * Iteration by UnoNode array
504      */
505     int opApply(scope int delegate(ref This node) dg)
506     {
507         return _opApply!(int delegate(ref This node))(dg);
508     }
509 
510 
511     size_t toHash() const nothrow @safe
512     {
513         final switch (_kind) with (Kind)
514         {
515             case nil:
516                 return 0;
517             case boolean:
518                 return _bool.hashOf();
519             case uinteger:
520                 return _uint.hashOf();
521             case integer:
522                 return _int.hashOf();
523             case floating:
524                 return _float.hashOf();
525             case text:
526                 return _string.hashOf();
527             case raw:
528                 return _raw.hashOf();
529             case array:
530                 return _array.hashOf();
531             case object:
532                 return _object.hashOf();
533         }
534     }
535 
536 
537     @safe unittest
538     {
539         UniNode node;
540         assert(node.toHash() == 0);
541 
542         node = UniNode(true);
543         assert(node.toHash() == 1);
544         node = UniNode(false);
545         assert(node.toHash() == 0);
546         node = UniNode(22u);
547         assert(node.toHash() == 22);
548         node = UniNode(-22);
549         assert(node.toHash() == -22);
550         node = UniNode(22.22);
551         assert(node.toHash() == 3683678524);
552         node = UniNode("1");
553         assert(node.toHash() == 2484513939);
554         ubyte[] data = [1, 2, 3];
555         node = UniNode(data);
556         assert(node.toHash() == 2161234436);
557         node = UniNode([UniNode(1), UniNode(2)]);
558         assert(node.toHash() == 9774061950961268414U);
559         node = UniNode(["1": UniNode(1), "2": UniNode(2)]);
560         assert(node.toHash() == 4159018407);
561 
562         auto node2 = UniNode(["2": UniNode(2), "1": UniNode(1)]);
563         assert(node.toHash() == node2.toHash());
564     }
565 
566 
567     bool opEquals(const This other) const
568     {
569         return opEquals(other);
570     }
571 
572 
573     bool opEquals(ref const This other) const @trusted
574     {
575         version (HUNT_VIEW_DEBUG) {
576             tracef("this: %s, other: %s", _kind, other.kind);
577         }
578 
579         if (_kind != other.kind) {
580             version(HUNT_FM_DEBUG) {
581                 warningf("Different type for comparation, this: %s, other: %s", toString(), other.toString());
582             }
583             
584             if(_kind == Kind.integer && other.kind == Kind.uinteger) {
585                 return _int == other._int;
586             }
587             if(_kind == Kind.uinteger && other.kind == Kind.integer) {
588                 return _uint == other._uint;
589             }
590             return false;
591         }
592 
593         final switch (_kind) with (Kind)
594         {
595             case nil:
596                 return true;
597             case boolean:
598                 return _bool == other._bool;
599             case uinteger:
600                 return _uint == other._uint;
601             case integer:
602                 return _int == other._int;
603             case floating:
604                 return _float == other._float;
605             case text:
606                 return _string == other._string;
607             case raw:
608                 return _raw == other._raw;
609             case array:
610                 return _array == other._array;
611             case object:
612                 return _object == other._object;
613         }
614     }
615 
616 
617     @safe unittest
618     {
619         auto n1 = UniNode(1);
620         auto n2 = UniNode("1");
621         auto n3 = UniNode(1);
622 
623         assert(n1 == n3);
624         assert(n1 != n2);
625         assert(n1 != UniNode(3));
626 
627         assert(UniNode([n1, n2, n3]) != UniNode([n2, n1, n3]));
628         assert(UniNode([n1, n2, n3]) == UniNode([n1, n2, n3]));
629 
630         assert(UniNode(["one": n1, "two": n2]) == UniNode(["one": n1, "two": n2]));
631     }
632 
633     /**
634      * Implement operator in for object
635      */
636     inout(This)* opBinaryRight(string op)(string key) inout if (op == "in")
637     {
638         enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object");
639         return key in _object;
640     }
641 
642 
643     @safe unittest
644     {
645         auto node = UniNode(1);
646         auto mnode = UniNode(["one": node, "two": node]);
647         assert (mnode.isObject);
648         assert("one" in mnode);
649     }
650 
651 
652     string toString() const
653     {
654         auto buff = appender!string;
655 
656         void fun(UniNodeImpl!This node) @safe const
657         {
658             switch (node.kind)
659             {
660                 case Kind.nil:
661                     buff.put("nil");
662                     break;
663                 case Kind.boolean:
664                     buff.put("bool("~node.get!bool.to!string~")");
665                     break;
666                 case Kind.uinteger:
667                     buff.put("uint("~node.get!ulong.to!string~")");
668                     break;
669                 case Kind.integer:
670                     buff.put("int("~node.get!long.to!string~")");
671                     break;
672                 case Kind.floating:
673                     buff.put("float("~node.get!double.to!string~")");
674                     break;
675                 case Kind.text:
676                     buff.put("text("~node.get!string.to!string~")");
677                     break;
678                 case Kind.raw:
679                     buff.put("raw("~node.get!(ubyte[]).to!string~")");
680                     break;
681                 case Kind.object:
682                 {
683                     buff.put("{");
684                     immutable len = node.length;
685                     size_t count;
686                     foreach (ref string k, ref This v; node)
687                     {
688                         count++;
689                         buff.put(k ~ ":");
690                         fun(v);
691                         if (count < len)
692                             buff.put(", ");
693                     }
694                     buff.put("}");
695                     break;
696                 }
697                 case Kind.array:
698                 {
699                     buff.put("[");
700                     immutable len = node.length;
701                     size_t count;
702                     foreach (size_t i, ref This v; node)
703                     {
704                         count++;
705                         fun(v);
706                         if (count < len)
707                             buff.put(", ");
708                     }
709                     buff.put("]");
710                     break;
711                 }
712                 default:
713                     buff.put("undefined");
714                     break;
715             }
716         }
717 
718         fun(this);
719         return buff.data;
720     }
721 
722 
723     @safe unittest
724     {
725         auto obj = UniNode.emptyObject;
726 
727         auto intNode = UniNode(int.max);
728         auto uintNode = UniNode(uint.max);
729         auto fNode = UniNode(float.nan);
730         auto textNode = UniNode("node");
731         auto boolNode = UniNode(true);
732         ubyte[] bytes = [1, 2, 3];
733         auto binNode = UniNode(bytes);
734         auto nilNode = UniNode();
735 
736         auto arrNode = UniNode([intNode, fNode, textNode, nilNode]);
737         auto objNode = UniNode([
738                 "i": intNode,
739                 "ui": uintNode,
740                 "f": fNode,
741                 "text": textNode,
742                 "bool": boolNode,
743                 "bin": binNode,
744                 "nil": nilNode,
745                 "arr": arrNode]);
746 
747         assert(objNode.toString.length);
748     }
749 
750 
751     @safe unittest
752     {
753         auto node = UniNode();
754         assert (node.isNull);
755 
756         auto anode = UniNode([node, node]);
757         assert (anode.isArray);
758 
759         auto mnode = UniNode(["one": node, "two": node]);
760         assert (mnode.isObject);
761     }
762 
763     /**
764      * Implement index operator by UniNode array
765      */
766     ref inout(This) opIndex(size_t idx) inout
767     {
768         enforceUniNode(_kind == Kind.array, "Expected " ~ This.stringof ~ " array");
769         return _array[idx];
770     }
771 
772 
773     @safe unittest
774     {
775         auto arr = UniNode.emptyArray;
776         foreach(i; 1..10)
777             arr ~= UniNode(i);
778         assert(arr[1] == UniNode(2));
779     }
780 
781     /**
782      * Implement index operator by UniNode object
783      */
784     ref inout(This) opIndex(string key) inout
785     {
786         enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object");
787         return _object[key];
788     }
789 
790 
791     @safe unittest
792     {
793         UniNode[string] obj;
794         foreach(i; 1..10)
795             obj[i.to!string] = UniNode(i*i);
796 
797         UniNode node = UniNode(obj);
798         assert(node["2"] == UniNode(4));
799     }
800 
801     /**
802      * Implement index assign operator by UniNode object
803      */
804     ref This opIndexAssign(This val, string key)
805     {
806         return opIndexAssign(val, key);
807     }
808 
809     /**
810      * Implement index assign operator by UniNode object
811      */
812     ref This opIndexAssign(ref This val, string key)
813     {
814         enforceUniNode(_kind == Kind.object, "Expected " ~ This.stringof ~ " object");
815         return _object[key] = val;
816     }
817 
818 
819     @safe unittest
820     {
821         UniNode node = UniNode.emptyObject;
822         UniNode[string] obj;
823         foreach(i; 1..10)
824             node[i.to!string] = UniNode(i*i);
825 
826         assert(node["2"] == UniNode(4));
827     }
828 
829     /**
830      * Implement operator ~= by UniNode array
831      */
832     void opOpAssign(string op)(This[] elem) if (op == "~")
833     {
834         opOpAssign!op(UniNode(elem));
835     }
836 
837     /**
838      * Implement operator ~= by UniNode array
839      */
840     void opOpAssign(string op)(This elem) if (op == "~")
841     {
842         enforceUniNode(_kind == Kind.array, "Expected " ~ This.stringof ~ " array");
843         _array ~= elem;
844     }
845 
846 
847     @safe unittest
848     {
849         auto node = UniNode(1);
850         auto anode = UniNode([node, node]);
851         assert(anode.length == 2);
852         anode ~= node;
853         anode ~= anode;
854         assert(anode.length == 4);
855         assert(anode[$-2] == node);
856     }
857 
858 
859 private:
860 
861 
862     void checkType(T)(Kind target, string file = __FILE__, size_t line = __LINE__) inout
863     {
864         enforceUniNode(_kind == target,
865                 fmt!("Trying to get %s but have %s.")(T.stringof, _kind),
866                 file, line);
867     }
868 }
869 
870 /**
871  * Universal structure for data storage of different types
872  */
873 struct UniNode
874 {
875 @safe:
876     UniNodeImpl!UniNode node;
877     alias node this;
878 
879 
880     this(V)(V val) inout
881     {
882         node = UniNodeImpl!UniNode(val);
883     }
884 
885     size_t toHash() const nothrow @safe
886     {
887         return node.toHash();
888     }
889 
890     bool opEquals(const UniNode other) const
891     {
892         return node.opEquals(other);
893     }
894 }
895 
896 /**
897  * UniNode error class
898  */
899 class UniNodeException : Exception
900 {
901     this(string msg, string file = __FILE__, size_t line = __LINE__,
902             Throwable next = null) @safe pure
903     {
904         super(msg, file, line, next);
905     }
906 }
907 
908 /**
909  * Enforce UniNodeException
910  */
911 void enforceUniNode(T)(T value, lazy string msg = "UniNode exception",
912         string file = __FILE__, size_t line = __LINE__) @safe pure
913 {
914     if (!value)
915         throw new UniNodeException(msg, file, line);
916 }
917 
918 template isUniNodeType(T, This)
919 {
920     enum isUniNodeType = isUniNodeInnerType!T
921         || isUniNodeArray!(T, This) || isUniNodeObject!(T, This);
922 }
923 
924 template isUniNodeInnerType(T)
925 {
926     enum isUniNodeInnerType = isNumeric!T || isBoolean!T || isSomeString!T
927         || is(T == typeof(null)) || isRawData!T;
928 }
929 
930 private:
931 
932 template TypeEnum(U)
933 {
934     import std.array : join;
935     enum msg = "enum TypeEnum : ubyte { " ~ [FieldNameTuple!U].join(", ") ~ " }";
936     // pragma(msg, msg);
937     mixin(msg);
938 }
939 
940 /**
941  * Check for an integer signed number
942  */
943 template isSignedNumeric(T)
944 {
945     enum isSignedNumeric = isNumeric!T && isSigned!T && !isFloatingPoint!T;
946 }
947 
948 /**
949  * Check for an integer unsigned number
950  */
951 template isUnsignedNumeric(T)
952 {
953     enum isUnsignedNumeric = isNumeric!T && isUnsigned!T && !isFloatingPoint!T;
954 }
955 
956 /**
957  * Checking for binary data
958  */
959 template isRawData(T)
960 {
961     enum isRawData = isTraitsArray!T && is(Unqual!(ForeachType!T) == ubyte);
962 }
963 
964 
965 template isUniNodeArray(T, This)
966 {
967     enum isUniNodeArray = isTraitsArray!T && is(Unqual!(ForeachType!T) == This);
968 }
969 
970 template isUniNodeObject(T, This)
971 {
972     enum isUniNodeObject = isAssociativeArray!T
973         && is(Unqual!(ForeachType!T) == This) && is(KeyType!T == string);
974 }
975 
976 auto assumeSafe(F)(F fun) @safe
977 if (isFunctionPointer!F || isDelegate!F)
978 {
979     static if (hasFunctionAttributes!(F, "@safe"))
980         return fun;
981     else
982         return (ParameterTypeTuple!F args) @trusted
983         {
984             return fun(args);
985         };
986 }