JsonValidator.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. /*
  2. * JsonValidator.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "JsonValidator.h"
  12. #include "JsonUtils.h"
  13. #include "../VCMI_Lib.h"
  14. #include "../filesystem/Filesystem.h"
  15. #include "../modding/ModScope.h"
  16. #include "../modding/CModHandler.h"
  17. #include "../texts/TextOperations.h"
  18. #include "../ScopeGuard.h"
  19. VCMI_LIB_NAMESPACE_BEGIN
  20. /// Searches for keys similar to 'target' in 'candidates' map
  21. /// Returns closest match or empty string if no suitable candidates are found
  22. static std::string findClosestMatch(const JsonMap & candidates, const std::string & target)
  23. {
  24. // Maximum distance at which we can consider strings to be similar
  25. // If strings have more different symbols than this number then it is not a typo, but a completely different word
  26. static constexpr int maxDistance = 5;
  27. int bestDistance = maxDistance;
  28. std::string bestMatch;
  29. for (auto const & candidate : candidates)
  30. {
  31. int newDistance = TextOperations::getLevenshteinDistance(candidate.first, target);
  32. if (newDistance < bestDistance)
  33. {
  34. bestDistance = newDistance;
  35. bestMatch = candidate.first;
  36. }
  37. }
  38. return bestMatch;
  39. }
  40. static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  41. {
  42. // check is not needed - e.g. incorporated into another check
  43. return "";
  44. }
  45. static std::string notImplementedCheck(JsonValidator & validator,
  46. const JsonNode & baseSchema,
  47. const JsonNode & schema,
  48. const JsonNode & data)
  49. {
  50. return "Not implemented entry in schema";
  51. }
  52. static std::string schemaListCheck(JsonValidator & validator,
  53. const JsonNode & baseSchema,
  54. const JsonNode & schema,
  55. const JsonNode & data,
  56. const std::string & errorMsg,
  57. const std::function<bool(size_t)> & isValid)
  58. {
  59. std::string errors = "<tested schemas>\n";
  60. size_t result = 0;
  61. for(const auto & schemaEntry : schema.Vector())
  62. {
  63. std::string error = validator.check(schemaEntry, data);
  64. if (error.empty())
  65. {
  66. result++;
  67. }
  68. else
  69. {
  70. errors += error;
  71. errors += "<end of schema>\n";
  72. }
  73. }
  74. if (isValid(result))
  75. return "";
  76. else
  77. return validator.makeErrorMessage(errorMsg) + errors;
  78. }
  79. static std::string allOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  80. {
  81. return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count)
  82. {
  83. return count == schema.Vector().size();
  84. });
  85. }
  86. static std::string anyOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  87. {
  88. return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count)
  89. {
  90. return count > 0;
  91. });
  92. }
  93. static std::string oneOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  94. {
  95. return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count)
  96. {
  97. return count == 1;
  98. });
  99. }
  100. static std::string notCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  101. {
  102. if (validator.check(schema, data).empty())
  103. return validator.makeErrorMessage("Successful validation against negative check");
  104. return "";
  105. }
  106. static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  107. {
  108. for(const auto & enumEntry : schema.Vector())
  109. {
  110. if (data == enumEntry)
  111. return "";
  112. }
  113. std::string errorMessage = "Key must have one of predefined values:" + schema.toCompactString();
  114. return validator.makeErrorMessage(errorMessage);
  115. }
  116. static std::string constCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  117. {
  118. if (data == schema)
  119. return "";
  120. return validator.makeErrorMessage("Key must have have constant value");
  121. }
  122. static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  123. {
  124. static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
  125. {
  126. {"null", JsonNode::JsonType::DATA_NULL},
  127. {"boolean", JsonNode::JsonType::DATA_BOOL},
  128. {"number", JsonNode::JsonType::DATA_FLOAT},
  129. {"integer", JsonNode::JsonType::DATA_INTEGER},
  130. {"string", JsonNode::JsonType::DATA_STRING},
  131. {"array", JsonNode::JsonType::DATA_VECTOR},
  132. {"object", JsonNode::JsonType::DATA_STRUCT}
  133. };
  134. const auto & typeName = schema.String();
  135. auto it = stringToType.find(typeName);
  136. if(it == stringToType.end())
  137. {
  138. return validator.makeErrorMessage("Unknown type in schema:" + typeName);
  139. }
  140. JsonNode::JsonType type = it->second;
  141. // for "number" type both float and integer are allowed
  142. if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber())
  143. return "";
  144. if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
  145. return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
  146. return "";
  147. }
  148. static std::string refCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  149. {
  150. std::string URI = schema.String();
  151. //node must be validated using schema pointed by this reference and not by data here
  152. //Local reference. Turn it into more easy to handle remote ref
  153. if (boost::algorithm::starts_with(URI, "#"))
  154. {
  155. const std::string name = validator.usedSchemas.back();
  156. const std::string nameClean = name.substr(0, name.find('#'));
  157. URI = nameClean + URI;
  158. }
  159. return validator.check(URI, data);
  160. }
  161. static std::string formatCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  162. {
  163. auto formats = validator.getKnownFormats();
  164. std::string errors;
  165. auto checker = formats.find(schema.String());
  166. if (checker != formats.end())
  167. {
  168. if (data.isString())
  169. {
  170. std::string result = checker->second(data);
  171. if (!result.empty())
  172. errors += validator.makeErrorMessage(result);
  173. }
  174. else
  175. {
  176. errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
  177. }
  178. }
  179. else
  180. errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
  181. return errors;
  182. }
  183. static std::string maxLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  184. {
  185. if (data.String().size() > schema.Float())
  186. return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
  187. return "";
  188. }
  189. static std::string minLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  190. {
  191. if (data.String().size() < schema.Float())
  192. return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
  193. return "";
  194. }
  195. static std::string maximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  196. {
  197. if (data.Float() > schema.Float())
  198. return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
  199. return "";
  200. }
  201. static std::string minimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  202. {
  203. if (data.Float() < schema.Float())
  204. return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
  205. return "";
  206. }
  207. static std::string exclusiveMaximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  208. {
  209. if (data.Float() >= schema.Float())
  210. return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
  211. return "";
  212. }
  213. static std::string exclusiveMinimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  214. {
  215. if (data.Float() <= schema.Float())
  216. return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
  217. return "";
  218. }
  219. static std::string multipleOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  220. {
  221. double result = data.Integer() / schema.Integer();
  222. if (!vstd::isAlmostEqual(floor(result), result))
  223. return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
  224. return "";
  225. }
  226. static std::string itemEntryCheck(JsonValidator & validator, const JsonVector & items, const JsonNode & schema, size_t index)
  227. {
  228. validator.currentPath.emplace_back();
  229. validator.currentPath.back().Float() = static_cast<double>(index);
  230. auto onExit = vstd::makeScopeGuard([&validator]()
  231. {
  232. validator.currentPath.pop_back();
  233. });
  234. if (!schema.isNull())
  235. return validator.check(schema, items[index]);
  236. return "";
  237. }
  238. static std::string itemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  239. {
  240. std::string errors;
  241. for (size_t i=0; i<data.Vector().size(); i++)
  242. {
  243. if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
  244. {
  245. if (schema.Vector().size() > i)
  246. errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
  247. }
  248. else
  249. {
  250. errors += itemEntryCheck(validator, data.Vector(), schema, i);
  251. }
  252. }
  253. return errors;
  254. }
  255. static std::string additionalItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  256. {
  257. std::string errors;
  258. // "items" is struct or empty (defaults to empty struct) - validation always successful
  259. const JsonNode & items = baseSchema["items"];
  260. if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
  261. return "";
  262. for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
  263. {
  264. if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
  265. errors += itemEntryCheck(validator, data.Vector(), schema, i);
  266. else if(!schema.isNull() && !schema.Bool())
  267. errors += validator.makeErrorMessage("Unknown entry found");
  268. }
  269. return errors;
  270. }
  271. static std::string minItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  272. {
  273. if (data.Vector().size() < schema.Float())
  274. return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
  275. return "";
  276. }
  277. static std::string maxItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  278. {
  279. if (data.Vector().size() > schema.Float())
  280. return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
  281. return "";
  282. }
  283. static std::string uniqueItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  284. {
  285. if (schema.Bool())
  286. {
  287. for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
  288. {
  289. auto itB = itA;
  290. while (++itB != schema.Vector().end())
  291. {
  292. if (*itA == *itB)
  293. return validator.makeErrorMessage("List must consist from unique items");
  294. }
  295. }
  296. }
  297. return "";
  298. }
  299. static std::string maxPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  300. {
  301. if (data.Struct().size() > schema.Float())
  302. return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
  303. return "";
  304. }
  305. static std::string minPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  306. {
  307. if (data.Struct().size() < schema.Float())
  308. return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
  309. return "";
  310. }
  311. static std::string uniquePropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  312. {
  313. for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
  314. {
  315. auto itB = itA;
  316. while (++itB != data.Struct().end())
  317. {
  318. if (itA->second == itB->second)
  319. return validator.makeErrorMessage("List must consist from unique items");
  320. }
  321. }
  322. return "";
  323. }
  324. static std::string requiredCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  325. {
  326. std::string errors;
  327. for(const auto & required : schema.Vector())
  328. {
  329. if (data[required.String()].isNull() && data.getModScope() != "core")
  330. errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
  331. }
  332. return errors;
  333. }
  334. static std::string dependenciesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  335. {
  336. std::string errors;
  337. for(const auto & deps : schema.Struct())
  338. {
  339. if (!data[deps.first].isNull())
  340. {
  341. if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
  342. {
  343. JsonVector depList = deps.second.Vector();
  344. for(auto & depEntry : depList)
  345. {
  346. if (data[depEntry.String()].isNull())
  347. errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
  348. }
  349. }
  350. else
  351. {
  352. if (!validator.check(deps.second, data).empty())
  353. errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
  354. }
  355. }
  356. }
  357. return errors;
  358. }
  359. static std::string propertyEntryCheck(JsonValidator & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
  360. {
  361. validator.currentPath.emplace_back();
  362. validator.currentPath.back().String() = nodeName;
  363. auto onExit = vstd::makeScopeGuard([&validator]()
  364. {
  365. validator.currentPath.pop_back();
  366. });
  367. // there is schema specifically for this item
  368. if (!schema.isNull())
  369. return validator.check(schema, node);
  370. return "";
  371. }
  372. static std::string propertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  373. {
  374. std::string errors;
  375. for(const auto & entry : data.Struct())
  376. errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
  377. return errors;
  378. }
  379. static std::string additionalPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
  380. {
  381. std::string errors;
  382. for(const auto & entry : data.Struct())
  383. {
  384. if (baseSchema["properties"].Struct().count(entry.first) == 0)
  385. {
  386. // try generic additionalItems schema
  387. if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
  388. errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
  389. // or, additionalItems field can be bool which indicates if such items are allowed
  390. else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
  391. {
  392. std::string bestCandidate = findClosestMatch(baseSchema["properties"].Struct(), entry.first);
  393. if (!bestCandidate.empty())
  394. errors += validator.makeErrorMessage("Unknown entry found: '" + entry.first + "'. Perhaps you meant '" + bestCandidate + "'?");
  395. else
  396. errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
  397. }
  398. }
  399. }
  400. return errors;
  401. }
  402. static bool testFilePresence(const std::string & scope, const ResourcePath & resource)
  403. {
  404. #ifndef ENABLE_MINIMAL_LIB
  405. std::set<std::string> allowedScopes;
  406. if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
  407. {
  408. //NOTE: recursive dependencies are not allowed at the moment - update code if this changes
  409. bool found = true;
  410. allowedScopes = VLC->modh->getModDependencies(scope, found);
  411. if(!found)
  412. return false;
  413. allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
  414. }
  415. allowedScopes.insert(scope); // mods can use their own files
  416. for(const auto & entry : allowedScopes)
  417. {
  418. if (CResourceHandler::get(entry)->existsResource(resource))
  419. return true;
  420. }
  421. #endif
  422. return false;
  423. }
  424. #define TEST_FILE(scope, prefix, file, type) \
  425. if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
  426. return ""
  427. static std::string testAnimation(const std::string & path, const std::string & scope)
  428. {
  429. TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
  430. TEST_FILE(scope, "Sprites/", path, EResType::JSON);
  431. return "Animation file \"" + path + "\" was not found";
  432. }
  433. static std::string textFile(const JsonNode & node)
  434. {
  435. TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON);
  436. return "Text file \"" + node.String() + "\" was not found";
  437. }
  438. static std::string musicFile(const JsonNode & node)
  439. {
  440. TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND);
  441. TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND);
  442. return "Music file \"" + node.String() + "\" was not found";
  443. }
  444. static std::string soundFile(const JsonNode & node)
  445. {
  446. TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND);
  447. return "Sound file \"" + node.String() + "\" was not found";
  448. }
  449. static std::string animationFile(const JsonNode & node)
  450. {
  451. return testAnimation(node.String(), node.getModScope());
  452. }
  453. static std::string imageFile(const JsonNode & node)
  454. {
  455. TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE);
  456. TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE);
  457. if (node.String().find(':') != std::string::npos)
  458. return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope());
  459. return "Image file \"" + node.String() + "\" was not found";
  460. }
  461. static std::string videoFile(const JsonNode & node)
  462. {
  463. TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
  464. TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO_LOW_QUALITY);
  465. return "Video file \"" + node.String() + "\" was not found";
  466. }
  467. #undef TEST_FILE
  468. JsonValidator::TValidatorMap createCommonFields()
  469. {
  470. JsonValidator::TValidatorMap ret;
  471. ret["format"] = formatCheck;
  472. ret["allOf"] = allOfCheck;
  473. ret["anyOf"] = anyOfCheck;
  474. ret["oneOf"] = oneOfCheck;
  475. ret["enum"] = enumCheck;
  476. ret["const"] = constCheck;
  477. ret["type"] = typeCheck;
  478. ret["not"] = notCheck;
  479. ret["$ref"] = refCheck;
  480. // fields that don't need implementation
  481. ret["title"] = emptyCheck;
  482. ret["$schema"] = emptyCheck;
  483. ret["default"] = emptyCheck;
  484. ret["defaultIOS"] = emptyCheck;
  485. ret["defaultAndroid"] = emptyCheck;
  486. ret["defaultWindows"] = emptyCheck;
  487. ret["description"] = emptyCheck;
  488. ret["definitions"] = emptyCheck;
  489. // Not implemented
  490. ret["propertyNames"] = notImplementedCheck;
  491. ret["contains"] = notImplementedCheck;
  492. ret["examples"] = notImplementedCheck;
  493. return ret;
  494. }
  495. JsonValidator::TValidatorMap createStringFields()
  496. {
  497. JsonValidator::TValidatorMap ret = createCommonFields();
  498. ret["maxLength"] = maxLengthCheck;
  499. ret["minLength"] = minLengthCheck;
  500. ret["pattern"] = notImplementedCheck;
  501. return ret;
  502. }
  503. JsonValidator::TValidatorMap createNumberFields()
  504. {
  505. JsonValidator::TValidatorMap ret = createCommonFields();
  506. ret["maximum"] = maximumCheck;
  507. ret["minimum"] = minimumCheck;
  508. ret["multipleOf"] = multipleOfCheck;
  509. ret["exclusiveMaximum"] = exclusiveMaximumCheck;
  510. ret["exclusiveMinimum"] = exclusiveMinimumCheck;
  511. return ret;
  512. }
  513. JsonValidator::TValidatorMap createVectorFields()
  514. {
  515. JsonValidator::TValidatorMap ret = createCommonFields();
  516. ret["items"] = itemsCheck;
  517. ret["minItems"] = minItemsCheck;
  518. ret["maxItems"] = maxItemsCheck;
  519. ret["uniqueItems"] = uniqueItemsCheck;
  520. ret["additionalItems"] = additionalItemsCheck;
  521. return ret;
  522. }
  523. JsonValidator::TValidatorMap createStructFields()
  524. {
  525. JsonValidator::TValidatorMap ret = createCommonFields();
  526. ret["additionalProperties"] = additionalPropertiesCheck;
  527. ret["uniqueProperties"] = uniquePropertiesCheck;
  528. ret["maxProperties"] = maxPropertiesCheck;
  529. ret["minProperties"] = minPropertiesCheck;
  530. ret["dependencies"] = dependenciesCheck;
  531. ret["properties"] = propertiesCheck;
  532. ret["required"] = requiredCheck;
  533. ret["patternProperties"] = notImplementedCheck;
  534. return ret;
  535. }
  536. JsonValidator::TFormatMap createFormatMap()
  537. {
  538. JsonValidator::TFormatMap ret;
  539. ret["textFile"] = textFile;
  540. ret["musicFile"] = musicFile;
  541. ret["soundFile"] = soundFile;
  542. ret["animationFile"] = animationFile;
  543. ret["imageFile"] = imageFile;
  544. ret["videoFile"] = videoFile;
  545. //TODO:
  546. // uri-reference
  547. // uri-template
  548. // json-pointer
  549. return ret;
  550. }
  551. std::string JsonValidator::makeErrorMessage(const std::string &message)
  552. {
  553. std::string errors;
  554. errors += "At ";
  555. if (!currentPath.empty())
  556. {
  557. for(const JsonNode &path : currentPath)
  558. {
  559. errors += "/";
  560. if (path.getType() == JsonNode::JsonType::DATA_STRING)
  561. errors += path.String();
  562. else
  563. errors += std::to_string(static_cast<unsigned>(path.Float()));
  564. }
  565. }
  566. else
  567. errors += "<root>";
  568. errors += "\n\t Error: " + message + "\n";
  569. return errors;
  570. }
  571. std::string JsonValidator::check(const std::string & schemaName, const JsonNode & data)
  572. {
  573. usedSchemas.push_back(schemaName);
  574. auto onscopeExit = vstd::makeScopeGuard([this]()
  575. {
  576. usedSchemas.pop_back();
  577. });
  578. return check(JsonUtils::getSchema(schemaName), data);
  579. }
  580. std::string JsonValidator::check(const JsonNode & schema, const JsonNode & data)
  581. {
  582. const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
  583. std::string errors;
  584. for(const auto & entry : schema.Struct())
  585. {
  586. auto checker = knownFields.find(entry.first);
  587. if (checker != knownFields.end())
  588. errors += checker->second(*this, schema, entry.second, data);
  589. }
  590. return errors;
  591. }
  592. const JsonValidator::TValidatorMap & JsonValidator::getKnownFieldsFor(JsonNode::JsonType type)
  593. {
  594. static const TValidatorMap commonFields = createCommonFields();
  595. static const TValidatorMap numberFields = createNumberFields();
  596. static const TValidatorMap stringFields = createStringFields();
  597. static const TValidatorMap vectorFields = createVectorFields();
  598. static const TValidatorMap structFields = createStructFields();
  599. switch (type)
  600. {
  601. case JsonNode::JsonType::DATA_FLOAT:
  602. case JsonNode::JsonType::DATA_INTEGER:
  603. return numberFields;
  604. case JsonNode::JsonType::DATA_STRING: return stringFields;
  605. case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
  606. case JsonNode::JsonType::DATA_STRUCT: return structFields;
  607. default: return commonFields;
  608. }
  609. }
  610. const JsonValidator::TFormatMap & JsonValidator::getKnownFormats()
  611. {
  612. static const TFormatMap knownFormats = createFormatMap();
  613. return knownFormats;
  614. }
  615. VCMI_LIB_NAMESPACE_END