1 /** 2 * Authors: Szabo Bogdan <szabobogdan@yahoo.com> 3 * Date: 9 8, 2015 4 * License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file. 5 * Copyright: Public Domain 6 */ 7 module openapi.validation; 8 9 import openapi.definitions; 10 import openapi.exceptions; 11 12 import vibe.http.server; 13 import vibe.data.json; 14 15 import std.conv; 16 import std.datetime; 17 import std.regex; 18 import std.stdio; 19 import std.algorithm.iteration; 20 import std.algorithm.comparison; 21 import std.algorithm.searching; 22 import std.array; 23 import std.traits; 24 import vibe.utils.dictionarylist; 25 26 bool isValid(Json value, string type, string format = "") { 27 if(type == "object") 28 return value.type == Json.Type.Object; 29 30 if(type == "array") 31 return value.type == Json.Type.Array; 32 33 return value.to!string.isValid(type, format); 34 } 35 36 bool isValid(string value, string type, string format = "") { 37 if(format == "undefined") { 38 format = ""; 39 } 40 41 try { 42 if(type == "integer" && format == "int32") { 43 value.to!int; 44 return true; 45 } 46 47 if(type == "integer" && format == "int64") { 48 value.to!long; 49 return true; 50 } 51 52 if(type == "integer" && format == "") { 53 return value.isValid(type, "int32") || value.isValid(type, "int64"); 54 } 55 56 if(type == "number" && format == "float") { 57 value.to!float; 58 return true; 59 } 60 61 if(type == "number" && format == "double") { 62 value.to!double; 63 return true; 64 } 65 66 if(type == "number" && format == "") { 67 return value.isValid(type, "double") || value.isValid(type, "float"); 68 } 69 70 if(type == "boolean") { 71 value.to!bool; 72 return true; 73 } 74 75 if(type == "string" && format == "") { 76 return true; 77 } 78 79 if(type == "string" && format == "password") { 80 return true; 81 } 82 83 if(type == "string" && format == "binary") { 84 return true; 85 } 86 } catch(ConvOverflowException e) { 87 return false; 88 } catch(ConvException e) { 89 return false; 90 } 91 92 try { 93 if(type == "string" && format == "date-time") { 94 SysTime.fromISOExtString(value); 95 return true; 96 } 97 98 if(type == "string" && format == "date") { 99 Date.fromISOExtString(value); 100 return true; 101 } 102 103 if(type == "string" && format == "time") { 104 TimeOfDay.fromISOExtString(value); 105 return true; 106 } 107 } catch(Exception e) { 108 return false; 109 } 110 111 if(type == "string" && format == "byte") { 112 auto ctr = ctRegex!(`^([A-Za-z0-9]*\n{0,1})*`); 113 auto matches = value.matchAll(ctr); 114 115 if(matches.post != "" || matches.pre != "") { 116 return false; 117 } 118 119 return true; 120 } 121 122 return false; 123 } 124 125 @("it should validate integers") 126 unittest { 127 assert("0".isValid("integer", "int32")); 128 assert("2147483647".isValid("integer", "int32")); 129 assert("-2147483648".isValid("integer", "int32")); 130 assert(!"2147483648".isValid("integer", "int32")); 131 assert(!"-2147483649".isValid("integer", "int32")); 132 assert(!"text".isValid("integer", "int32")); 133 134 assert("0".isValid("integer", "int64")); 135 assert("9223372036854775807".isValid("integer", "int64")); 136 assert("-9223372036854775807".isValid("integer", "int64")); 137 assert(!"9223372036854775808".isValid("integer", "int64")); 138 assert(!"9223372036854775808".isValid("integer", "int64")); 139 assert(!"text".isValid("integer", "int64")); 140 } 141 142 @("it should validate numbers") 143 unittest { 144 assert("0".isValid("number", "float")); 145 assert("0.5".isValid("number", "float")); 146 assert("-0.5".isValid("number", "float")); 147 assert(!"0,5".isValid("number", "float")); 148 assert(!"-0,5".isValid("number", "float")); 149 assert(!"text".isValid("number", "float")); 150 151 assert("0".isValid("number", "double")); 152 assert("0.5".isValid("number", "double")); 153 assert("-0.5".isValid("number", "double")); 154 assert(!"0,5".isValid("number", "double")); 155 assert(!"-0,5".isValid("number", "double")); 156 assert(!"text".isValid("number", "double")); 157 } 158 159 @("it should validate boolean") 160 unittest { 161 assert(!"0".isValid("boolean")); 162 assert(!"1".isValid("boolean")); 163 assert(!"2".isValid("boolean")); 164 assert(!"-1".isValid("boolean")); 165 assert("true".isValid("boolean")); 166 assert("false".isValid("boolean")); 167 assert(!"text".isValid("boolean")); 168 } 169 170 @("it should validate strings") 171 unittest { 172 assert("text".isValid("string")); 173 174 assert("text".isValid("string", "password")); 175 176 assert("text".isValid("string", "binary")); 177 178 assert(!"text".isValid("string", "date-time")); 179 assert("2000-01-01T00:00:00Z".isValid("string", "date-time")); 180 181 assert("2000-01-01".isValid("string", "date")); 182 assert(!"text".isValid("string", "date")); 183 184 assert("00:00:00".isValid("string", "time")); 185 assert(!"text".isValid("string", "time")); 186 187 assert("ASDASDasd0123\nadma21".isValid("string", "byte")); 188 assert(!"!@#".isValid("string", "byte")); 189 } 190 191 private string[] keys(T)(T list) { 192 string[] keyList; 193 194 foreach(string key, val; list) 195 keyList ~= key; 196 197 return keyList; 198 } 199 200 Components getOpenApiOperation(HTTPServerRequest, OpenApi) { 201 return Components(); 202 } 203 204 void validateExistence(ParameterIn in_)(HTTPServerRequest request, OpenApi definition) { 205 206 } 207 208 Path matchedPath(OpenApi definition, string) { 209 return Path(); 210 } 211 212 void validateValues(ParameterIn in_)(HTTPServerRequest request, OpenApi definition) { 213 214 } 215 216 void validateAgainstSchema(Json value, Json schema) { 217 if("required" in schema) { 218 foreach(field; schema["required"]) { 219 if(field.to!string !in value) { 220 throw new OpenApiParameterException("Missing `" ~ field.to!string ~ "` parameter."); 221 } 222 } 223 } 224 225 if(value.type == Json.Type.object) { 226 foreach(string key, subValue; value) { 227 if(key !in schema["properties"]) { 228 throw new OpenApiParameterException("Extra `"~key~"` parameter found."); 229 } 230 231 string format = "format" in schema["properties"][key] ? schema["properties"][key]["format"].to!string : ""; 232 233 if(!subValue.isValid(schema["properties"][key]["type"].to!string, format)) { 234 throw new OpenApiValidationException("Invalid `" ~ key ~ "` schema value."); 235 } 236 237 subValue.validateAgainstSchema(schema["properties"][key]); 238 } 239 } 240 } 241 242 void validateBody(HTTPServerRequest request, OpenApi definition) { 243 244 } 245 246 void validate(ParameterIn in_)(HTTPServerRequest request, OpenApi definition) { 247 request.validateExistence!in_(definition); 248 request.validateValues!in_(definition); 249 }