1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-06-19 00:17:56 +02:00
Files
.github
AI
CI
Mods
android
client
clientapp
cmake_modules
config
debian
docker
docs
include
ios
launcher
lib
battle
bonuses
campaign
constants
entities
events
filesystem
gameState
json
JsonBonus.cpp
JsonBonus.h
JsonFormatException.h
JsonNode.cpp
JsonNode.h
JsonParser.cpp
JsonParser.h
JsonRandom.cpp
JsonRandom.h
JsonUtils.cpp
JsonUtils.h
JsonValidator.cpp
JsonValidator.h
JsonWriter.cpp
JsonWriter.h
logging
mapObjectConstructors
mapObjects
mapping
minizip
modding
network
networkPacks
pathfinder
rewardable
rmg
serializer
spells
texts
vstd
AI_Base.h
ArtifactUtils.cpp
ArtifactUtils.h
AsyncRunner.h
BasicTypes.cpp
BattleFieldHandler.cpp
BattleFieldHandler.h
CAndroidVMHelper.cpp
CAndroidVMHelper.h
CArtHandler.cpp
CArtHandler.h
CArtifactInstance.cpp
CArtifactInstance.h
CArtifactSet.h
CBonusTypeHandler.cpp
CBonusTypeHandler.h
CConfigHandler.cpp
CConfigHandler.h
CConsoleHandler.cpp
CConsoleHandler.h
CCreatureHandler.cpp
CCreatureHandler.h
CCreatureSet.cpp
CCreatureSet.h
CGameInfoCallback.cpp
CGameInfoCallback.h
CGameInterface.cpp
CGameInterface.h
CMakeLists.txt
CPlayerState.cpp
CPlayerState.h
CRandomGenerator.cpp
CRandomGenerator.h
CScriptingModule.cpp
CScriptingModule.h
CSkillHandler.cpp
CSkillHandler.h
CSoundBase.h
CStack.cpp
CStack.h
CStopWatch.h
CThreadHelper.cpp
CThreadHelper.h
Color.h
ConditionalWait.h
ExceptionsCommon.h
ExtraOptionsInfo.cpp
ExtraOptionsInfo.h
FunctionList.h
GameCallbackHolder.h
GameConstants.h
GameLibrary.cpp
GameLibrary.h
GameSettings.cpp
GameSettings.h
IBonusTypeHandler.h
IGameCallback.cpp
IGameCallback.h
IGameEventsReceiver.h
IGameSettings.h
IHandlerBase.cpp
IHandlerBase.h
LoadProgress.cpp
LoadProgress.h
LogicalExpression.cpp
LogicalExpression.h
ObstacleHandler.cpp
ObstacleHandler.h
Point.h
Rect.cpp
Rect.h
ResourceSet.cpp
ResourceSet.h
RiverHandler.cpp
RiverHandler.h
RoadHandler.cpp
RoadHandler.h
ScopeGuard.h
ScriptHandler.cpp
ScriptHandler.h
StartInfo.cpp
StartInfo.h
StdInc.cpp
StdInc.h
TerrainHandler.cpp
TerrainHandler.h
TurnTimerInfo.cpp
TurnTimerInfo.h
UnlockGuard.h
VCMIDirs.cpp
VCMIDirs.h
int3.h
vcmi_endian.h
lobby
mapeditor
osx
rpm
scripting
scripts
server
serverapp
test
vcmiqt
win
xcode
.editorconfig
.gitattributes
.gitignore
.gitmodules
.travis.yml
AUTHORS.h
CCallback.cpp
CCallback.h
CMakeLists.txt
CMakePresets.json
ChangeLog.md
Global.h
Version.cpp.in
Version.h
conanfile.py
fuzzylite.pc.in
license.txt
vcmibuilder
vcmi/lib/json/JsonValidator.cpp

703 lines
22 KiB
C++
Raw Normal View History

/*
* JsonValidator.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#include "StdInc.h"
#include "JsonValidator.h"
#include "JsonUtils.h"
#include "../GameLibrary.h"
#include "../filesystem/Filesystem.h"
#include "../modding/ModScope.h"
#include "../modding/CModHandler.h"
2025-01-23 23:50:04 +01:00
#include "../texts/TextOperations.h"
#include "../ScopeGuard.h"
VCMI_LIB_NAMESPACE_BEGIN
/// Searches for keys similar to 'target' in 'candidates' map
/// Returns closest match or empty string if no suitable candidates are found
static std::string findClosestMatch(const JsonMap & candidates, const std::string & target)
{
// Maximum distance at which we can consider strings to be similar
// If strings have more different symbols than this number then it is not a typo, but a completely different word
static constexpr int maxDistance = 5;
int bestDistance = maxDistance;
std::string bestMatch;
for (auto const & candidate : candidates)
{
2025-01-23 23:50:04 +01:00
int newDistance = TextOperations::getLevenshteinDistance(candidate.first, target);
if (newDistance < bestDistance)
{
bestDistance = newDistance;
bestMatch = candidate.first;
}
}
return bestMatch;
}
2024-02-19 17:46:26 +02:00
static std::string emptyCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
2024-02-19 17:46:26 +02:00
// check is not needed - e.g. incorporated into another check
return "";
}
2024-02-19 17:46:26 +02:00
static std::string notImplementedCheck(JsonValidator & validator,
const JsonNode & baseSchema,
const JsonNode & schema,
const JsonNode & data)
{
2024-02-19 17:46:26 +02:00
return "Not implemented entry in schema";
}
2024-02-19 17:46:26 +02:00
static std::string schemaListCheck(JsonValidator & validator,
const JsonNode & baseSchema,
const JsonNode & schema,
const JsonNode & data,
const std::string & errorMsg,
const std::function<bool(size_t)> & isValid)
{
std::string errors = "<tested schemas>\n";
size_t result = 0;
2024-02-19 17:46:26 +02:00
for(const auto & schemaEntry : schema.Vector())
{
std::string error = validator.check(schemaEntry, data);
if (error.empty())
{
2024-02-19 17:46:26 +02:00
result++;
}
2024-02-19 17:46:26 +02:00
else
{
2024-02-19 17:46:26 +02:00
errors += error;
errors += "<end of schema>\n";
}
2024-02-19 17:46:26 +02:00
}
if (isValid(result))
return "";
else
return validator.makeErrorMessage(errorMsg) + errors;
}
2024-02-19 17:46:26 +02:00
static std::string allOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass all schemas", [&schema](size_t count)
{
return count == schema.Vector().size();
});
}
2024-02-19 17:46:26 +02:00
static std::string anyOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass any schema", [](size_t count)
{
return count > 0;
});
}
2024-02-19 17:46:26 +02:00
static std::string oneOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
return schemaListCheck(validator, baseSchema, schema, data, "Failed to pass exactly one schema", [](size_t count)
{
return count == 1;
});
}
2024-02-19 17:46:26 +02:00
static std::string notCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (validator.check(schema, data).empty())
return validator.makeErrorMessage("Successful validation against negative check");
return "";
}
2024-02-19 17:46:26 +02:00
static std::string enumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
for(const auto & enumEntry : schema.Vector())
{
if (data == enumEntry)
return "";
}
std::string errorMessage = "Key must have one of predefined values:" + schema.toCompactString();
return validator.makeErrorMessage(errorMessage);
2024-02-19 17:46:26 +02:00
}
static std::string constCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data == schema)
return "";
return validator.makeErrorMessage("Key must have have constant value");
}
2024-02-19 17:46:26 +02:00
static std::string typeCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
static const std::unordered_map<std::string, JsonNode::JsonType> stringToType =
{
{"null", JsonNode::JsonType::DATA_NULL},
{"boolean", JsonNode::JsonType::DATA_BOOL},
{"number", JsonNode::JsonType::DATA_FLOAT},
{"integer", JsonNode::JsonType::DATA_INTEGER},
{"string", JsonNode::JsonType::DATA_STRING},
{"array", JsonNode::JsonType::DATA_VECTOR},
{"object", JsonNode::JsonType::DATA_STRUCT}
};
const auto & typeName = schema.String();
auto it = stringToType.find(typeName);
if(it == stringToType.end())
{
return validator.makeErrorMessage("Unknown type in schema:" + typeName);
}
2024-02-19 17:46:26 +02:00
JsonNode::JsonType type = it->second;
2024-02-19 17:46:26 +02:00
// for "number" type both float and integer are allowed
if(type == JsonNode::JsonType::DATA_FLOAT && data.isNumber())
return "";
2024-02-19 17:46:26 +02:00
if(type != data.getType() && data.getType() != JsonNode::JsonType::DATA_NULL)
return validator.makeErrorMessage("Type mismatch! Expected " + schema.String());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string refCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string URI = schema.String();
//node must be validated using schema pointed by this reference and not by data here
//Local reference. Turn it into more easy to handle remote ref
if (boost::algorithm::starts_with(URI, "#"))
{
const std::string name = validator.usedSchemas.back();
const std::string nameClean = name.substr(0, name.find('#'));
URI = nameClean + URI;
}
2024-02-19 17:46:26 +02:00
return validator.check(URI, data);
}
2024-02-19 17:46:26 +02:00
static std::string formatCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
auto formats = validator.getKnownFormats();
std::string errors;
auto checker = formats.find(schema.String());
if (checker != formats.end())
{
2024-02-19 17:46:26 +02:00
if (data.isString())
{
2024-02-19 17:46:26 +02:00
std::string result = checker->second(data);
if (!result.empty())
errors += validator.makeErrorMessage(result);
}
2024-02-19 17:46:26 +02:00
else
{
2024-02-19 17:46:26 +02:00
errors += validator.makeErrorMessage("Format value must be string: " + schema.String());
}
}
2024-02-19 17:46:26 +02:00
else
errors += validator.makeErrorMessage("Unsupported format type: " + schema.String());
return errors;
}
2024-02-19 17:46:26 +02:00
static std::string maxLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.String().size() > schema.Float())
return validator.makeErrorMessage((boost::format("String is longer than %d symbols") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string minLengthCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.String().size() < schema.Float())
return validator.makeErrorMessage((boost::format("String is shorter than %d symbols") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string maximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Float() > schema.Float())
return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string minimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Float() < schema.Float())
return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string exclusiveMaximumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Float() >= schema.Float())
return validator.makeErrorMessage((boost::format("Value is bigger than %d") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string exclusiveMinimumCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Float() <= schema.Float())
return validator.makeErrorMessage((boost::format("Value is smaller than %d") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string multipleOfCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
double result = data.Integer() / schema.Integer();
if (!vstd::isAlmostEqual(floor(result), result))
return validator.makeErrorMessage((boost::format("Value is not divisible by %d") % schema.Float()).str());
return "";
}
static std::string itemEntryCheck(JsonValidator & validator, const JsonVector & items, const JsonNode & schema, size_t index)
{
validator.currentPath.emplace_back();
validator.currentPath.back().Float() = static_cast<double>(index);
auto onExit = vstd::makeScopeGuard([&validator]()
{
2024-02-19 17:46:26 +02:00
validator.currentPath.pop_back();
});
2024-02-19 17:46:26 +02:00
if (!schema.isNull())
return validator.check(schema, items[index]);
return "";
}
2024-02-19 17:46:26 +02:00
static std::string itemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
for (size_t i=0; i<data.Vector().size(); i++)
{
if (schema.getType() == JsonNode::JsonType::DATA_VECTOR)
{
2024-02-19 17:46:26 +02:00
if (schema.Vector().size() > i)
errors += itemEntryCheck(validator, data.Vector(), schema.Vector()[i], i);
}
2024-02-19 17:46:26 +02:00
else
{
2024-02-19 17:46:26 +02:00
errors += itemEntryCheck(validator, data.Vector(), schema, i);
}
2024-02-19 17:46:26 +02:00
}
return errors;
}
2024-02-19 17:46:26 +02:00
static std::string additionalItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
// "items" is struct or empty (defaults to empty struct) - validation always successful
const JsonNode & items = baseSchema["items"];
if (items.getType() != JsonNode::JsonType::DATA_VECTOR)
return "";
2024-02-19 17:46:26 +02:00
for (size_t i=items.Vector().size(); i<data.Vector().size(); i++)
{
if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
errors += itemEntryCheck(validator, data.Vector(), schema, i);
else if(!schema.isNull() && !schema.Bool())
errors += validator.makeErrorMessage("Unknown entry found");
}
return errors;
}
static std::string minItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Vector().size() < schema.Float())
return validator.makeErrorMessage((boost::format("Length is smaller than %d") % schema.Float()).str());
return "";
}
static std::string maxItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Vector().size() > schema.Float())
return validator.makeErrorMessage((boost::format("Length is bigger than %d") % schema.Float()).str());
return "";
}
2024-02-19 17:46:26 +02:00
static std::string uniqueItemsCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (schema.Bool())
{
for (auto itA = schema.Vector().begin(); itA != schema.Vector().end(); itA++)
{
2024-02-19 17:46:26 +02:00
auto itB = itA;
while (++itB != schema.Vector().end())
{
2024-02-19 17:46:26 +02:00
if (*itA == *itB)
return validator.makeErrorMessage("List must consist from unique items");
}
}
}
2024-02-19 17:46:26 +02:00
return "";
}
2024-02-19 17:46:26 +02:00
static std::string maxPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Struct().size() > schema.Float())
return validator.makeErrorMessage((boost::format("Number of entries is bigger than %d") % schema.Float()).str());
return "";
}
static std::string minPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
if (data.Struct().size() < schema.Float())
return validator.makeErrorMessage((boost::format("Number of entries is less than %d") % schema.Float()).str());
return "";
}
static std::string uniquePropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
for (auto itA = data.Struct().begin(); itA != data.Struct().end(); itA++)
{
2024-02-19 17:46:26 +02:00
auto itB = itA;
while (++itB != data.Struct().end())
{
2024-02-19 17:46:26 +02:00
if (itA->second == itB->second)
return validator.makeErrorMessage("List must consist from unique items");
}
2024-02-19 17:46:26 +02:00
}
return "";
}
2024-02-19 17:46:26 +02:00
static std::string requiredCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
for(const auto & required : schema.Vector())
{
2024-11-30 17:20:39 +01:00
if (data[required.String()].isNull() && data.getModScope() != "core")
2024-02-19 17:46:26 +02:00
errors += validator.makeErrorMessage("Required entry " + required.String() + " is missing");
}
return errors;
}
2024-02-19 17:46:26 +02:00
static std::string dependenciesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
for(const auto & deps : schema.Struct())
{
if (!data[deps.first].isNull())
{
2024-02-19 17:46:26 +02:00
if (deps.second.getType() == JsonNode::JsonType::DATA_VECTOR)
{
2024-02-19 17:46:26 +02:00
JsonVector depList = deps.second.Vector();
for(auto & depEntry : depList)
{
2024-02-19 17:46:26 +02:00
if (data[depEntry.String()].isNull())
errors += validator.makeErrorMessage("Property " + depEntry.String() + " required for " + deps.first + " is missing");
}
}
2024-02-19 17:46:26 +02:00
else
{
2024-02-19 17:46:26 +02:00
if (!validator.check(deps.second, data).empty())
errors += validator.makeErrorMessage("Requirements for " + deps.first + " are not fulfilled");
}
}
2024-02-19 17:46:26 +02:00
}
return errors;
}
2024-02-19 17:46:26 +02:00
static std::string propertyEntryCheck(JsonValidator & validator, const JsonNode &node, const JsonNode & schema, const std::string & nodeName)
{
validator.currentPath.emplace_back();
validator.currentPath.back().String() = nodeName;
auto onExit = vstd::makeScopeGuard([&validator]()
{
validator.currentPath.pop_back();
});
2024-02-19 17:46:26 +02:00
// there is schema specifically for this item
if (!schema.isNull())
return validator.check(schema, node);
return "";
}
2024-02-19 17:46:26 +02:00
static std::string propertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
2024-02-19 17:46:26 +02:00
for(const auto & entry : data.Struct())
errors += propertyEntryCheck(validator, entry.second, schema[entry.first], entry.first);
return errors;
}
2024-02-19 17:46:26 +02:00
static std::string additionalPropertiesCheck(JsonValidator & validator, const JsonNode & baseSchema, const JsonNode & schema, const JsonNode & data)
{
std::string errors;
for(const auto & entry : data.Struct())
{
if (baseSchema["properties"].Struct().count(entry.first) == 0)
{
2024-02-19 17:46:26 +02:00
// try generic additionalItems schema
if (schema.getType() == JsonNode::JsonType::DATA_STRUCT)
errors += propertyEntryCheck(validator, entry.second, schema, entry.first);
2024-02-19 17:46:26 +02:00
// or, additionalItems field can be bool which indicates if such items are allowed
else if(!schema.isNull() && !schema.Bool()) // present and set to false - error
{
std::string bestCandidate = findClosestMatch(baseSchema["properties"].Struct(), entry.first);
if (!bestCandidate.empty())
errors += validator.makeErrorMessage("Unknown entry found: '" + entry.first + "'. Perhaps you meant '" + bestCandidate + "'?");
else
errors += validator.makeErrorMessage("Unknown entry found: " + entry.first);
}
}
}
2024-02-19 17:46:26 +02:00
return errors;
}
2024-02-19 17:46:26 +02:00
static bool testFilePresence(const std::string & scope, const ResourcePath & resource)
{
#ifndef ENABLE_MINIMAL_LIB
2024-02-19 17:46:26 +02:00
std::set<std::string> allowedScopes;
if(scope != ModScope::scopeBuiltin() && !scope.empty()) // all real mods may have dependencies
{
2024-02-19 17:46:26 +02:00
//NOTE: recursive dependencies are not allowed at the moment - update code if this changes
bool found = true;
allowedScopes = LIBRARY->modh->getModDependencies(scope, found);
2024-02-19 17:46:26 +02:00
if(!found)
return false;
2024-02-19 17:46:26 +02:00
allowedScopes.insert(ModScope::scopeBuiltin()); // all mods can use H3 files
}
allowedScopes.insert(scope); // mods can use their own files
2024-02-19 17:46:26 +02:00
for(const auto & entry : allowedScopes)
{
if (CResourceHandler::get(entry)->existsResource(resource))
return true;
}
#endif
2024-02-19 17:46:26 +02:00
return false;
}
2024-02-19 17:46:26 +02:00
#define TEST_FILE(scope, prefix, file, type) \
if (testFilePresence(scope, ResourcePath(prefix + file, type))) \
return ""
2024-02-19 17:46:26 +02:00
static std::string testAnimation(const std::string & path, const std::string & scope)
{
TEST_FILE(scope, "Sprites/", path, EResType::ANIMATION);
TEST_FILE(scope, "Sprites/", path, EResType::JSON);
return "Animation file \"" + path + "\" was not found";
}
2024-02-19 17:46:26 +02:00
static std::string textFile(const JsonNode & node)
{
TEST_FILE(node.getModScope(), "", node.String(), EResType::JSON);
return "Text file \"" + node.String() + "\" was not found";
}
2024-02-19 17:46:26 +02:00
static std::string musicFile(const JsonNode & node)
{
TEST_FILE(node.getModScope(), "Music/", node.String(), EResType::SOUND);
TEST_FILE(node.getModScope(), "", node.String(), EResType::SOUND);
return "Music file \"" + node.String() + "\" was not found";
}
2024-02-19 17:46:26 +02:00
static std::string soundFile(const JsonNode & node)
{
TEST_FILE(node.getModScope(), "Sounds/", node.String(), EResType::SOUND);
return "Sound file \"" + node.String() + "\" was not found";
}
2024-02-19 17:46:26 +02:00
static std::string animationFile(const JsonNode & node)
{
return testAnimation(node.String(), node.getModScope());
}
2024-02-19 17:46:26 +02:00
static std::string imageFile(const JsonNode & node)
{
TEST_FILE(node.getModScope(), "Data/", node.String(), EResType::IMAGE);
TEST_FILE(node.getModScope(), "Sprites/", node.String(), EResType::IMAGE);
if (node.String().find(':') != std::string::npos)
return testAnimation(node.String().substr(0, node.String().find(':')), node.getModScope());
return "Image file \"" + node.String() + "\" was not found";
}
2024-02-19 17:46:26 +02:00
static std::string videoFile(const JsonNode & node)
{
TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO);
2024-05-03 20:08:29 +03:00
TEST_FILE(node.getModScope(), "Video/", node.String(), EResType::VIDEO_LOW_QUALITY);
2024-02-19 17:46:26 +02:00
return "Video file \"" + node.String() + "\" was not found";
}
#undef TEST_FILE
2024-02-19 17:46:26 +02:00
JsonValidator::TValidatorMap createCommonFields()
{
JsonValidator::TValidatorMap ret;
ret["format"] = formatCheck;
ret["allOf"] = allOfCheck;
ret["anyOf"] = anyOfCheck;
ret["oneOf"] = oneOfCheck;
ret["enum"] = enumCheck;
ret["const"] = constCheck;
2024-02-19 17:46:26 +02:00
ret["type"] = typeCheck;
ret["not"] = notCheck;
ret["$ref"] = refCheck;
// fields that don't need implementation
ret["title"] = emptyCheck;
ret["$schema"] = emptyCheck;
ret["default"] = emptyCheck;
ret["defaultIOS"] = emptyCheck;
ret["defaultAndroid"] = emptyCheck;
ret["defaultWindows"] = emptyCheck;
ret["description"] = emptyCheck;
ret["definitions"] = emptyCheck;
// Not implemented
ret["propertyNames"] = notImplementedCheck;
ret["contains"] = notImplementedCheck;
ret["examples"] = notImplementedCheck;
return ret;
}
2024-02-19 17:46:26 +02:00
JsonValidator::TValidatorMap createStringFields()
{
JsonValidator::TValidatorMap ret = createCommonFields();
ret["maxLength"] = maxLengthCheck;
ret["minLength"] = minLengthCheck;
2024-02-19 17:46:26 +02:00
ret["pattern"] = notImplementedCheck;
return ret;
}
2024-02-19 17:46:26 +02:00
JsonValidator::TValidatorMap createNumberFields()
{
JsonValidator::TValidatorMap ret = createCommonFields();
ret["maximum"] = maximumCheck;
ret["minimum"] = minimumCheck;
ret["multipleOf"] = multipleOfCheck;
ret["exclusiveMaximum"] = exclusiveMaximumCheck;
ret["exclusiveMinimum"] = exclusiveMinimumCheck;
return ret;
}
2024-02-19 17:46:26 +02:00
JsonValidator::TValidatorMap createVectorFields()
{
JsonValidator::TValidatorMap ret = createCommonFields();
ret["items"] = itemsCheck;
ret["minItems"] = minItemsCheck;
ret["maxItems"] = maxItemsCheck;
ret["uniqueItems"] = uniqueItemsCheck;
ret["additionalItems"] = additionalItemsCheck;
return ret;
}
2024-02-19 17:46:26 +02:00
JsonValidator::TValidatorMap createStructFields()
{
JsonValidator::TValidatorMap ret = createCommonFields();
ret["additionalProperties"] = additionalPropertiesCheck;
ret["uniqueProperties"] = uniquePropertiesCheck;
ret["maxProperties"] = maxPropertiesCheck;
ret["minProperties"] = minPropertiesCheck;
ret["dependencies"] = dependenciesCheck;
ret["properties"] = propertiesCheck;
ret["required"] = requiredCheck;
ret["patternProperties"] = notImplementedCheck;
return ret;
}
2024-02-19 17:46:26 +02:00
JsonValidator::TFormatMap createFormatMap()
{
JsonValidator::TFormatMap ret;
ret["textFile"] = textFile;
ret["musicFile"] = musicFile;
ret["soundFile"] = soundFile;
ret["animationFile"] = animationFile;
ret["imageFile"] = imageFile;
ret["videoFile"] = videoFile;
//TODO:
// uri-reference
// uri-template
// json-pointer
return ret;
}
2024-02-19 17:46:26 +02:00
std::string JsonValidator::makeErrorMessage(const std::string &message)
{
2024-02-19 17:46:26 +02:00
std::string errors;
errors += "At ";
if (!currentPath.empty())
{
2024-02-19 17:46:26 +02:00
for(const JsonNode &path : currentPath)
{
2024-02-19 17:46:26 +02:00
errors += "/";
if (path.getType() == JsonNode::JsonType::DATA_STRING)
errors += path.String();
else
errors += std::to_string(static_cast<unsigned>(path.Float()));
}
}
2024-02-19 17:46:26 +02:00
else
errors += "<root>";
errors += "\n\t Error: " + message + "\n";
return errors;
}
2024-02-19 17:46:26 +02:00
std::string JsonValidator::check(const std::string & schemaName, const JsonNode & data)
{
usedSchemas.push_back(schemaName);
auto onscopeExit = vstd::makeScopeGuard([this]()
{
2024-02-19 17:46:26 +02:00
usedSchemas.pop_back();
});
return check(JsonUtils::getSchema(schemaName), data);
}
2024-02-19 17:46:26 +02:00
std::string JsonValidator::check(const JsonNode & schema, const JsonNode & data)
{
const TValidatorMap & knownFields = getKnownFieldsFor(data.getType());
std::string errors;
for(const auto & entry : schema.Struct())
{
2024-02-19 17:46:26 +02:00
auto checker = knownFields.find(entry.first);
if (checker != knownFields.end())
errors += checker->second(*this, schema, entry.second, data);
}
2024-02-19 17:46:26 +02:00
return errors;
}
2024-02-19 17:46:26 +02:00
const JsonValidator::TValidatorMap & JsonValidator::getKnownFieldsFor(JsonNode::JsonType type)
{
static const TValidatorMap commonFields = createCommonFields();
static const TValidatorMap numberFields = createNumberFields();
static const TValidatorMap stringFields = createStringFields();
static const TValidatorMap vectorFields = createVectorFields();
static const TValidatorMap structFields = createStructFields();
2024-02-19 17:46:26 +02:00
switch (type)
{
2024-02-19 17:46:26 +02:00
case JsonNode::JsonType::DATA_FLOAT:
case JsonNode::JsonType::DATA_INTEGER:
return numberFields;
case JsonNode::JsonType::DATA_STRING: return stringFields;
case JsonNode::JsonType::DATA_VECTOR: return vectorFields;
case JsonNode::JsonType::DATA_STRUCT: return structFields;
default: return commonFields;
}
2024-02-19 17:46:26 +02:00
}
2024-02-19 17:46:26 +02:00
const JsonValidator::TFormatMap & JsonValidator::getKnownFormats()
{
static const TFormatMap knownFormats = createFormatMap();
return knownFormats;
}
VCMI_LIB_NAMESPACE_END