1 module jsonschema.schema;
2 
3 import std, jsonschema;
4 
5 enum JsonSchemaType
6 {
7     failsafe,
8     string_     = 1 << 0,
9     number      = 1 << 1,
10     object      = 1 << 2,
11     array       = 1 << 3,
12     boolean     = 1 << 4,
13     null_       = 1 << 5,
14     integer     = 1 << 6,
15     _enum,
16     _blanket
17 }
18 
19 struct JsonSchemaConstraint
20 {
21 }
22 
23 struct JsonSchemaKeyword
24 {
25 }
26 
27 struct JsonSchemaState
28 {
29     string varName;
30 }
31 
32 struct JsonSchemaAlgebraic(Types...)
33 {
34     private static union Store
35     {
36         static foreach(type; Types)
37             mixin("type "~__traits(identifier, type)~";");
38     }
39 
40     // Create an enum with each type as a member.
41     mixin((){
42         char[] kind;
43         kind ~= "enum Kind { _none, ";
44         static foreach(type; Types)
45             kind ~= __traits(identifier, type)~", ";
46         kind ~= " }";
47         return kind;
48     }());
49 
50     private Kind _kind;
51     private Store _store;
52 
53     static foreach(type; Types)
54     {
55         this(type value)
56         {
57             this._kind = mixin("Kind."~__traits(identifier, type));
58             mixin("this._store."~__traits(identifier, type)~" = value;");
59         }
60 
61         bool contains(T)()
62         if(is(T == type))
63         {
64             return this._kind == mixin("Kind."~__traits(identifier, type));
65         }
66 
67         bool as(T)()
68         if(is(T == type))
69         {
70             assert(this.contains!T, "This algebraic does not contain a "~__traits(identifier, type));
71             return mixin("this._store."~__traits(identifier, type));
72         }
73     }
74 
75     Kind kind()
76     {
77         return this._kind;
78     }
79 }
80 
81 struct JsonSchema(
82     Adapter_,
83     States_,
84     Constraints_,
85     Keywords_,
86     Rules_
87 )
88 {
89     // I'd like to apologise to the compiler for the war crimes I've committed against it.
90     private alias MakeInstance(alias T) = T!(typeof(this));
91     alias Adapter       = Adapter_;
92     alias States        = staticMap!(MakeInstance, States_.Group);
93     alias Constraints   = staticMap!(MakeInstance, Constraints_.Group);
94     alias Keywords      = staticMap!(MakeInstance, Keywords_.Group);
95     alias ConstraintT   = JsonSchemaAlgebraic!Constraints;
96     alias Rules         = Rules_;
97 
98     static foreach(state; States)
99         mixin("state "~getUDAs!(state, JsonSchemaState)[0].varName~";");
100 
101     void parse(Adapter.AggregateType value)
102     {
103         const valueType = Adapter.getType(value);
104         if(valueType == JsonSchemaType.boolean)
105         {
106             this.common.type = JsonSchemaType._blanket;
107             this.common.blanketValue = Adapter.getBoolean(value);
108             return;
109         }
110         else if(valueType != JsonSchemaType.object)
111             throw new Exception("A schema must be an object, true, or false. Not: "~valueType.to!string);
112 
113         Adapter.eachObjectProperty(value, (k,v)
114         {
115             switch(k)
116             {
117                 static foreach(keyword; Keywords)
118                 {
119                     case keyword.KEYWORD: keyword.handle(this, v); return;
120                 }
121 
122                 static foreach(constraint; Constraints)
123                 {
124                     case constraint.KEYWORD: this.common.constraints ~= ConstraintT(constraint(v)); return;
125                 }
126 
127                 default: break;
128             }
129         });
130     }
131 }
132 
133 alias T = JsonSchema!(
134     StdJsonAdapter,
135     AliasGroup!(
136         JsonCommonState,
137         JsonObjectState
138     ),
139     AliasGroup!(
140         JsonSchemaMinLengthConstraint,
141         JsonSchemaMaxLengthConstraint,
142         JsonSchemaPatternConstraint,
143 
144         JsonSchemaMultipleOfConstraint,
145         JsonSchemaMinimumConstraint,
146         JsonSchemaMaximumOfConstraint,
147         
148         JsonSchemaRequiredConstraint,
149         JsonSchemaMinPropertiesConstraint,
150         JsonSchemaMaxPropertiesConstraint
151     ),
152     AliasGroup!(
153         JsonSchemaTypeKeyword,
154         JsonSchemaPropertiesKeyword,
155         JsonSchemaPatternPropertiesKeyword,
156         JsonSchemaAdditionalPropertiesKeyword,
157     ),
158     AliasGroup!()
159 );
160 
161 unittest
162 {
163     T t;
164     t.parse(parseJSON(`{
165         "type": "string",
166         "minLength": 0,
167         "maxLength": 100,
168         "pattern": "^gay$",
169         "multipleOf": 10,
170         "minimum": 200,
171         "maximum": 400,
172         "properties": {
173             "number": { "type": "number" },
174             "street_name": { "type": "string" },
175         },
176         "patternProperties": {
177             "^S_": { "type": "string" },
178             "^I_": { "type": "integer" }
179         },
180         "additionalProperties": false,
181         "required": ["name", "email"]
182     }`));
183 }
184 
185 struct AliasGroup(Group_...)
186 {
187     alias Group = Group_;
188 }
189 
190 void schemaEnforceType(JsonSchemaType expected, JsonSchemaType got)
191 {
192     enforce(
193         got & expected,
194         "Expected value of type '%s' but got '%s'".format(
195             expected, got
196         )
197     );
198 }
199 
200 void schemaEnforceArrayOf(Adapter)(JsonSchemaType type, Adapter.AggregateType value)
201 {
202     const valueType = Adapter.getType(value);
203     schemaEnforceType(JsonSchemaType.array, valueType);
204     Adapter.eachArrayValue(value, (i,v) => schemaEnforceType(type, Adapter.getType(v)));
205 }