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 }