1 module SchemaLoader; 2 3 import boilerplate; 4 import dyaml; 5 import route; 6 import std.algorithm; 7 import std.array; 8 import std.format; 9 import std.json; 10 import std.path; 11 import std.string; 12 import std.typecons; 13 import text.json.Decode; 14 import ToJson; 15 import types; 16 17 class SchemaLoader 18 { 19 OpenApiFile[string] files; 20 21 OpenApiFile load(string path) 22 { 23 path = path.asNormalizedPath.array; 24 if (path !in this.files) 25 { 26 const root = Loader.fromFile(path).load; 27 const JSONValue jsonValue = root.toJson(Yes.ordered); 28 Route[] routes = null; 29 Type[string] schemas = null; 30 Parameter[string] parameters = null; 31 32 if (jsonValue.hasKey("paths")) 33 { 34 foreach (JSONValue pair; jsonValue.getEntry("paths").array) 35 { 36 const url_ = pair["key"].str; 37 const routeJson = pair["value"].toObject.object; 38 39 auto routeParametersJson = routeJson.get("parameters", JSONValue((JSONValue[]).init)); 40 auto routeParameters_ = routeParametersJson.decodeJson!(Parameter[], route.decode); 41 42 foreach (string method_, JSONValue endpoint; routeJson) 43 { 44 if (method_ == "parameters") 45 continue; 46 47 auto routeDto = endpoint.toObject.decodeJson!(RouteDto, route.decode); 48 49 Response[] responses_; 50 foreach (string code, JSONValue response; endpoint.getEntry("responses").toObject.object) 51 { 52 Type schema = null; 53 if (response.hasKey("content", "application/json")) 54 { 55 schema = response.getEntry("content") 56 .getEntry("application/json") 57 .getEntry("schema") 58 .decodeJson!(Type, types.decode); 59 } 60 else if (response.hasKey("content")) 61 { 62 // unknown content 63 schema = new ArrayType(new IntegerType("int8".nullable)); 64 } 65 responses_ ~= Response(code, schema); 66 } 67 68 Type schema_ = null; 69 if (endpoint.hasKey("requestBody", "content", "application/json")) 70 { 71 schema_ = endpoint.getEntry("requestBody") 72 .getEntry("content") 73 .getEntry("application/json") 74 .getEntry("schema") 75 .decodeJson!(Type, types.decode); 76 } 77 else if (endpoint.hasKey("requestBody", "content")) 78 { 79 // unknown content 80 schema_ = new ArrayType(new IntegerType("int8".nullable)); 81 } 82 83 with (Route.Builder()) 84 { 85 url = url_; 86 method = method_; 87 description = mergeComments(routeDto.summary, routeDto.description); 88 operationId = routeDto.operationId; 89 schema = schema_; 90 parameters = routeParameters_ ~ routeDto.parameters; 91 responses = responses_; 92 routes ~= builderValue; 93 } 94 } 95 } 96 } 97 98 string description = null; 99 if (jsonValue.hasKey("info") && jsonValue.getEntry("info").hasKey("description")) 100 { 101 description = jsonValue.getEntry("info").getEntry("description").str; 102 } 103 104 foreach (JSONValue pair; jsonValue.getEntry("components").getEntry("schemas").array) 105 { 106 const key = pair["key"].str; 107 108 schemas[format!"components/schemas/%s"(key)] = pair["value"] 109 .decodeJson!(Type, types.decode); 110 } 111 if (jsonValue.getEntry("components").hasKey("parameters")) 112 { 113 foreach (JSONValue pair; jsonValue.getEntry("components").getEntry("parameters").array) 114 { 115 const key = pair["key"].str; 116 117 parameters[format!"components/parameters/%s"(key)] = pair["value"] 118 .decodeJson!(Parameter, route.decode); 119 } 120 } 121 this.files[path] = new OpenApiFile(path, description, routes, schemas, parameters); 122 } 123 return this.files[path]; 124 } 125 126 mixin(GenerateToString); 127 } 128 129 class OpenApiFile 130 { 131 string path; 132 133 string description; 134 135 Route[] routes; 136 137 Type[string] schemas; 138 139 Parameter[string] parameters; 140 141 mixin(GenerateAll); 142 } 143 144 struct RouteDto 145 { 146 @(This.Default) 147 Nullable!string description; 148 149 @(This.Default) 150 Nullable!string summary; 151 152 string operationId; 153 154 @(This.Default) 155 Parameter[] parameters; 156 157 mixin(GenerateAll); 158 } 159 160 private string mergeComments(const Nullable!string first, const Nullable!string second) 161 { 162 const string firstString = first.get(null).strip; 163 const string secondString = second.get(null).strip; 164 if (secondString.empty) 165 { 166 return firstString; 167 } 168 if (firstString.empty) 169 { 170 return secondString; 171 } 172 return format!"%s\n\n%s"(firstString, secondString); 173 }