added the game

This commit is contained in:
JHDev2006
2025-09-13 16:30:32 +01:00
parent 5ef689109b
commit 3773bdaf64
3616 changed files with 263702 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Sahedo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,516 @@
class_name JSONSchema
extends RefCounted
# JSON Schema main script
# Inherits from Reference for easy use
const SMALL_FLOAT_THRESHOLD = 0.001
const MAX_DECIMAL_PLACES = 3
const DEF_KEY_NAME = "schema root"
const DEF_ERROR_STRING = "##error##"
const JST_ARRAY = "array"
const JST_BOOLEAN = "boolean"
const JST_INTEGER = "integer"
const JST_NULL = "null"
const JST_NUMBER = "number"
const JST_OBJECT = "object"
const JST_STRING = "string"
const JSKW_TYPE = "type"
const JSKW_PROP = "properties"
const JSKW_REQ = "required"
const JSKW_TITLE = "title"
const JSKW_DESCR = "description"
const JSKW_DEFAULT = "default"
const JSKW_EXAMPLES = "examples"
const JSKW_COMMENT = "$comment"
const JSKW_ENUM = "enum"
const JSKW_CONST = "const"
const JSKW_PREFIX_ITEMS = "prefixItems"
const JSKW_ITEMS = "items"
const JSKW_MIN_ITEMS = "minItems"
const JSKW_MAX_ITEMS = "maxItems"
const JSKW_CONTAINS = "contains"
const JSKW_ADD_ITEMS = "additionalItems"
const JSKW_UNIQUE_ITEMS = "uniqueItems"
const JSKW_MULT_OF = "multipleOf"
const JSKW_MINIMUM = "minimum"
const JSKW_MIN_EX = "exclusiveMinimum"
const JSKW_MAXIMUM = "maximum"
const JSKW_MAX_EX = "exclusiveMaximum"
const JSKW_PROP_ADD = "additionalProperties"
const JSKW_PROP_PATTERN = "patternProperties"
const JSKW_PROP_NAMES = "propertyNames"
const JSKW_PROP_MIN = "minProperties"
const JSKW_PROP_MAX = "maxProperties"
const JSKW_DEPEND = "dependencies"
const JSKW_LENGTH_MIN = "minLength"
const JSKW_LENGTH_MAX = "maxLength"
const JSKW_PATTERN = "pattern"
const JSKW_FORMAT = "format"
const JSKW_COLOR = "color"
const JSM_GREATER = "greater"
const JSM_GREATER_EQ = "greater or equal"
const JSM_LESS = "less"
const JSM_LESS_EQ = "less or equal"
const JSM_OBJ_DICT = "object (dictionary)"
const JSL_AND = "%s and %s"
const JSL_OR = "%s or %s"
const ERR_SCHEMA_FALSE = "Schema declared as deny all"
const ERR_WRONG_SCHEMA_GEN = "Schema error: "
const ERR_WRONG_SCHEMA_TYPE = "Schema error: schema must be empty object or object with 'type' keyword or boolean value"
const ERR_WRONG_SHEMA_NOTA = "Schema error: expected that all elements of '%s.%s' must be '%s'"
const ERR_WRONG_PROP_TYPE = "Schema error: any schema item must be object with 'type' keyword"
const ERR_REQ_PROP_GEN = "Schema error: expected array of required properties for '%s'"
const ERR_REQ_PROP_MISSING = "Missing required property: '%s' for '%s'"
const ERR_NO_PROP_ADD = "Additional properties are not required: found '%s'"
const ERR_FEW_PROP = "%d propertie(s) are not enough properties, at least %d are required"
const ERR_MORE_PROP = "%d propertie(s) are too many properties, at most %d are allowed"
const ERR_FEW_ITEMS = "%s item(s) are not enough items, at least %s are required"
const ERR_MORE_ITEMS = "%s item(s) are too many items, at most %s are allowed"
const ERR_INVALID_JSON_GEN = "Validation fails with message: %s"
const ERR_INVALID_JSON_EXT = "Invalid JSON data passed with message: %s"
const ERR_TYPE_MISMATCH_GEN = "Type mismatch: expected %s for '%s'"
const ERR_INVALID_NUMBER = "The %s key that equals %s should have a maximum of %s decimal places"
const ERR_INVALID_MULT = "Multiplier in key %s that equals %s must be greater or equal to %s"
const ERR_MULT_D = "Key %s that equal %d must be multiple of %d"
const ERR_MULT_F = "Key %s that equal %f must be multiple of %f"
const ERR_RANGE_D = "Key %s that equal %d must be %s than %d"
const ERR_RANGE_F = "Key %s that equal %f must be %s than %f"
const ERR_RANGE_S = "Length of '%s' (%d) %s than declared (%d)"
const ERR_WRONG_PATTERN = "String '%s' does not match its corresponding pattern"
const ERR_FORMAT = "String '%s' does not match its corresponding format '%s'"
# This is one and only function that need you to call outside
# If all validation checks passes, this OK
func validate(json_data : String, schema: String) -> String:
var error: int
var json = JSON.new()
# General validation input data as JSON file
error = json.parse(json_data)
if error: return ERR_INVALID_JSON_EXT % error_string(error)
# General validation input schema as JSONSchema file
error = json.parse(schema)
if not error == OK : return ERR_WRONG_SCHEMA_GEN + error_string(error)
var test_json_conv = JSON.new()
test_json_conv.parse(schema)
var parsed_schema = test_json_conv.get_data()
match typeof(parsed_schema):
TYPE_BOOL:
if !parsed_schema:
return ERR_INVALID_JSON_GEN % ERR_SCHEMA_FALSE
else:
return ""
TYPE_DICTIONARY:
if parsed_schema.is_empty():
return ""
elif parsed_schema.keys().size() > 0 && !parsed_schema.has(JSKW_TYPE):
return ERR_WRONG_SCHEMA_TYPE
_: return ERR_WRONG_SCHEMA_TYPE
# All inputs seems valid. Begin type validation
# Normal return empty string, meaning OK
return _type_selection(json_data, parsed_schema)
func _to_string():
return "[JSONSchema:%d]" % get_instance_id()
# TODO: title, description, default, examples, $comment, enum, const
func _type_selection(json_data: String, schema: Dictionary, key: String = DEF_KEY_NAME) -> String:
# If the schema is an empty object it always passes validation
if schema.is_empty():
return ""
if typeof(schema) == TYPE_BOOL:
# If the schema is true it always passes validation
if schema:
return ""
# If the schema is false it always vales validation
else:
return ERR_INVALID_JSON_GEN + "false is always invalid"
var typearr: Array = _var_to_array(schema.type)
var test_json_conv = JSON.new()
test_json_conv.parse(json_data)
var parsed_data = test_json_conv.get_data()
var error: String = ERR_TYPE_MISMATCH_GEN % [typearr, key]
for type in typearr:
match type:
JST_ARRAY:
if typeof(parsed_data) == TYPE_ARRAY:
error = _validate_array(parsed_data, schema, key)
else:
error = ERR_TYPE_MISMATCH_GEN % [[JST_ARRAY], key]
JST_BOOLEAN:
if typeof(parsed_data) != TYPE_BOOL:
return ERR_TYPE_MISMATCH_GEN % [[JST_BOOLEAN], key]
else:
error = ""
JST_INTEGER:
if typeof(parsed_data) == TYPE_INT:
error = _validate_integer(parsed_data, schema, key)
if typeof(parsed_data) == TYPE_FLOAT && parsed_data == int(parsed_data):
error = _validate_integer(int(parsed_data), schema, key)
JST_NULL:
if typeof(parsed_data) != TYPE_NIL:
return ERR_TYPE_MISMATCH_GEN % [[JST_NULL], key]
else:
error = ""
JST_NUMBER:
if typeof(parsed_data) == TYPE_FLOAT:
error = _validate_number(parsed_data, schema, key)
else:
error = ERR_TYPE_MISMATCH_GEN % [[JST_NUMBER], key]
JST_OBJECT:
if typeof(parsed_data) == TYPE_DICTIONARY:
error = _validate_object(parsed_data, schema, key)
else:
error = ERR_TYPE_MISMATCH_GEN % [[JST_OBJECT], key]
JST_STRING:
if typeof(parsed_data) == TYPE_STRING:
error = _validate_string(parsed_data, schema, key)
else:
error = ERR_TYPE_MISMATCH_GEN % [[JST_STRING], key]
return error
func _var_to_array(variant) -> Array:
var result : Array = []
if typeof(variant) == TYPE_ARRAY:
result = variant
else:
result.append(variant)
return result
func _validate_array(input_data: Array, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# TODO: contains minContains maxContains uniqueItems
# Initialize variables
var error : String = "" # Variable to store any error messages
var items_array : Array # Array of items in the schema
var suberror : Array = [] # Array of suberrors in each item
var additional_items_schema: Dictionary # Schema for additional items in the input data
var is_additional_item_allowed: bool # Flag to check if additional items are allowed
# Check if minItems key exists in the schema
if input_schema.has(JSKW_MIN_ITEMS):
# Check if non negative number
if input_schema.minItems < 0:
return ERR_WRONG_SCHEMA_GEN + "minItems must be a non-negative number."
if input_data.size() < input_schema.minItems:
return ERR_FEW_ITEMS % [input_data.size(), input_schema.minItems]
# Check if maxItems key exists in the schema
if input_schema.has(JSKW_MAX_ITEMS):
# Check if non negative number
if input_schema.maxItems < 0:
return ERR_WRONG_SCHEMA_GEN + "minItems must be a non-negative number."
if input_data.size() > input_schema.maxItems:
return ERR_MORE_ITEMS % [input_data.size(), input_schema.maxItems]
# Check if prefixItems key exists in the schema
if input_schema.has(JSKW_PREFIX_ITEMS):
# Check if items key exists in the schema
if not input_schema.has(JSKW_ITEMS):
return ERR_REQ_PROP_MISSING % [JSKW_ITEMS, JSKW_PREFIX_ITEMS]
# Return error if items key is not a bool or a dictionary
if not typeof(input_schema.items) == TYPE_DICTIONARY and not typeof(input_schema.items) == TYPE_BOOL:
return ERR_WRONG_SCHEMA_TYPE
if typeof(input_schema.items) == TYPE_BOOL:
# Check if additional items in the input data are allowed
if input_schema.items == false:
# Check if there are more items in the input data than specified in prefixItems
if input_data.size() > input_schema.prefixItems.size():
# Create an error message if there are more items than allowed
var substr := "Array '%s' is of size %s but no addition items allowed." % [input_data, input_data.size()]
return ERR_INVALID_JSON_GEN % substr
# If the 'items' key is set to true all types are allowed for addition items.
else:
additional_items_schema = {}
# Check if items key is a dictionary
if typeof(input_schema.items) == TYPE_DICTIONARY:
# Any items after the specified ones in prefixItems have to be validated with this schema
# Set the schema for additional array items
additional_items_schema = input_schema.items
# Check if all entries in prefixItems are a dictionary
for schema in input_schema.prefixItems:
if typeof(schema) != TYPE_DICTIONARY:
return ERR_WRONG_SHEMA_NOTA % [property_name, JSKW_ITEMS, JST_OBJECT]
# Check every item in the input data
for index in input_data.size():
var item = input_data[index]
var current_schema: Dictionary
var key_substr: String
if index <= input_schema.prefixItems.size() - 1:
# As long as there are prefixItems in the array work with those
current_schema = input_schema.prefixItems[index]
key_substr = ".prefixItems"
else:
# After that use the items schema
current_schema = additional_items_schema
key_substr = ".items"
var sub_error_message := _type_selection(JSON.stringify(item), current_schema, property_name + key_substr + "[" + str(index) + "]")
if not sub_error_message == "":
suberror.append(sub_error_message)
if suberror.size() > 0:
return ERR_INVALID_JSON_GEN % str(suberror)
# Return inside this if block, because we don't want to validate the items key twice.
return error
# Check if items key exists in the schema
if input_schema.has(JSKW_ITEMS):
#'items' must be an object
if not typeof(input_schema.items) == TYPE_DICTIONARY:
return ERR_WRONG_SHEMA_NOTA % [property_name, JSKW_ITEMS, JST_OBJECT]
# Check every item of input Array on
for index in input_data.size():
index = index - 1
# Validate the array item with the schema defined by the 'items' key
var sub_error_message := _type_selection(JSON.stringify(input_data[index]), input_schema.items, property_name + "[" + str(index) + "]")
if not sub_error_message == "":
suberror.append(sub_error_message)
if suberror.size() > 0:
return ERR_INVALID_JSON_GEN % str(suberror)
return error
func _validate_boolean(input_data: bool, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# nothing to check
return ""
func _validate_integer(input_data: int, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# all processing is performed in
return _validate_number(input_data, input_schema, property_name)
func _validate_null(input_data, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# nothing to check
return ""
func _validate_number(input_data: float, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
var types: Array = _var_to_array(input_schema.type)
# integer mode turns on only if types has integer and has not number
var integer_mode: bool = types.has(JST_INTEGER) && !types.has(JST_NUMBER)
# Processing multiple check
if input_schema.has(JSKW_MULT_OF):
var mult: float
var mod: float
var is_zero: bool
# Get the multipleOf value from the schema and convert to float
mult = float(input_schema[JSKW_MULT_OF])
# Convert to integer if integer_mode is enabled
mult = int(mult) if integer_mode else mult
# Check if the number has more decimal places then allowed
var decimal_places := str(input_data).get_slice('.', 1)
if not decimal_places.is_empty() and decimal_places.length() > MAX_DECIMAL_PLACES:
return ERR_INVALID_NUMBER % [property_name, input_data, str(MAX_DECIMAL_PLACES)]
# Check if multipleOf is smaller than SMALL_FLOAT_THRESHOLD
if not mult >= SMALL_FLOAT_THRESHOLD:
return ERR_INVALID_MULT % [property_name, mult, str(SMALL_FLOAT_THRESHOLD)]
# Multiply by a big number if input is smaller than 1 to prevent float issues
if input_data < 1.0 or mult < 1.0:
mod = fmod(input_data * 1000, mult * 1000)
else:
mod = fmod(input_data, mult)
# Check if the remainder is close to zero
is_zero = is_zero_approx(mod)
# Return error message if remainder is not close to zero
if not is_zero:
if integer_mode:
return ERR_MULT_D % [property_name, input_data, mult]
else:
return ERR_MULT_F % [property_name, input_data, mult]
# processing minimum check
if input_schema.has(JSKW_MINIMUM):
var minimum = float(input_schema[JSKW_MINIMUM])
minimum = int(minimum) if integer_mode else minimum
if input_data < minimum:
if integer_mode:
return ERR_RANGE_D % [property_name, input_data, JSM_GREATER_EQ, minimum]
else:
return ERR_RANGE_F % [property_name, input_data, JSM_GREATER_EQ, minimum]
# processing exclusive minimum check
if input_schema.has(JSKW_MIN_EX):
var minimum = float(input_schema[JSKW_MIN_EX])
minimum = int(minimum) if integer_mode else minimum
if input_data <= minimum:
if integer_mode:
return ERR_RANGE_D % [property_name, input_data, JSM_GREATER, minimum]
else:
return ERR_RANGE_F % [property_name, input_data, JSM_GREATER, minimum]
# processing maximum check
if input_schema.has(JSKW_MAXIMUM):
var maximum = float(input_schema[JSKW_MAXIMUM])
maximum = int(maximum) if integer_mode else maximum
if input_data > maximum:
if integer_mode:
return ERR_RANGE_D % [property_name, input_data, JSM_LESS_EQ, maximum]
else:
return ERR_RANGE_F % [property_name, input_data, JSM_LESS_EQ, maximum]
# processing exclusive minimum check
if input_schema.has(JSKW_MAX_EX):
var maximum = float(input_schema[JSKW_MAX_EX])
maximum = int(maximum) if integer_mode else maximum
if input_data >= maximum:
if integer_mode:
return ERR_RANGE_D % [property_name, input_data, JSM_LESS, maximum]
else:
return ERR_RANGE_F % [property_name, input_data, JSM_LESS, maximum]
return ""
func _validate_object(input_data: Dictionary, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# TODO: patternProperties
var error : String = ""
# Process dependencies
if input_schema.has(JSKW_DEPEND):
for dependency in input_schema.dependencies.keys():
if input_data.has(dependency):
match typeof(input_schema.dependencies[dependency]):
TYPE_ARRAY:
if input_schema.has(JSKW_REQ):
for property in input_schema.dependencies[dependency]:
input_schema.required.append(property)
else:
input_schema.required = input_schema.dependencies[dependency]
TYPE_DICTIONARY:
for key in input_schema.dependencies[dependency].keys():
if input_schema.has(key):
match typeof(input_schema[key]):
TYPE_ARRAY:
for element in input_schema.dependencies[dependency][key]:
input_schema[key].append(element)
TYPE_DICTIONARY:
for element in input_schema.dependencies[dependency][key].keys():
input_schema[key][element] = input_schema.dependencies[dependency][key][element]
_:
input_schema[key] = input_schema.dependencies[dependency][key]
else:
input_schema[key] = input_schema.dependencies[dependency][key]
_:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSL_OR % [JST_ARRAY, JSM_OBJ_DICT], property_name]
# Process properties
if input_schema.has(JSKW_PROP):
# Process required
if input_schema.has(JSKW_REQ):
if typeof(input_schema.required) != TYPE_ARRAY: return ERR_REQ_PROP_GEN % property_name
for i in input_schema.required:
if !input_data.has(i): return ERR_REQ_PROP_MISSING % [i, property_name]
# Continue validating schema subelements
if typeof(input_schema.properties) != TYPE_DICTIONARY:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSM_OBJ_DICT, property_name]
# Process property items
for key in input_schema.properties:
if !input_schema.properties[key].has(JSKW_TYPE):
return ERR_WRONG_PROP_TYPE
if input_data.has(key):
error = _type_selection(JSON.stringify(input_data[key]), input_schema.properties[key], key)
else:
pass
if error: return error
# Process additional properties
if input_schema.has(JSKW_PROP_ADD):
match typeof(input_schema.additionalProperties):
TYPE_BOOL:
if not input_schema.additionalProperties:
for key in input_data:
if not input_schema.properties.has(key):
return ERR_NO_PROP_ADD % key
TYPE_DICTIONARY:
for key in input_data:
if not input_schema.properties.has(key):
return _type_selection(JSON.stringify(input_data[key]), input_schema.additionalProperties, key)
_:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSL_OR % [JST_BOOLEAN, JSM_OBJ_DICT], property_name]
# Process properties names
if input_schema.has(JSKW_PROP_NAMES):
if typeof(input_schema.propertyNames) != TYPE_DICTIONARY:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JSM_OBJ_DICT, property_name]
for key in input_data:
error = _validate_string(key, input_schema.propertyNames, key)
if error: return error
# Process minProperties maxProperties
if input_schema.has(JSKW_PROP_MIN):
if typeof(input_schema[JSKW_PROP_MIN]) != TYPE_FLOAT:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name]
if input_data.keys().size() < input_schema[JSKW_PROP_MIN]:
return ERR_FEW_PROP % [input_data.keys().size(), input_schema[JSKW_PROP_MIN]]
if input_schema.has(JSKW_PROP_MAX):
if typeof(input_schema[JSKW_PROP_MAX]) != TYPE_FLOAT:
return ERR_WRONG_SCHEMA_GEN + ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name]
if input_data.keys().size() > input_schema[JSKW_PROP_MAX]:
return ERR_MORE_PROP % [input_data.keys().size(), input_schema[JSKW_PROP_MAX]]
return error
func _validate_string(input_data: String, input_schema: Dictionary, property_name: String = DEF_KEY_NAME) -> String:
# TODO: format
var error : String = ""
if input_schema.has(JSKW_LENGTH_MIN):
if not (typeof(input_schema[JSKW_LENGTH_MIN]) == TYPE_INT || typeof(input_schema[JSKW_LENGTH_MIN]) == TYPE_FLOAT):
return ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name+"."+JSKW_LENGTH_MIN]
if input_data.length() < input_schema[JSKW_LENGTH_MIN]:
return ERR_INVALID_JSON_GEN % ERR_RANGE_S % [property_name, input_data.length(), JSM_LESS ,input_schema[JSKW_LENGTH_MIN]]
if input_schema.has(JSKW_LENGTH_MAX):
if not (typeof(input_schema[JSKW_LENGTH_MAX]) == TYPE_INT || typeof(input_schema[JSKW_LENGTH_MAX]) == TYPE_FLOAT):
return ERR_TYPE_MISMATCH_GEN % [JST_INTEGER, property_name+"."+JSKW_LENGTH_MAX]
if input_data.length() > input_schema[JSKW_LENGTH_MAX]:
return ERR_INVALID_JSON_GEN % ERR_RANGE_S % [property_name, input_data.length(), JSM_GREATER, input_schema[JSKW_LENGTH_MAX]]
if input_schema.has(JSKW_PATTERN):
if not (typeof(input_schema[JSKW_PATTERN]) == TYPE_STRING):
return ERR_TYPE_MISMATCH_GEN % [JST_STRING, property_name+"."+JSKW_PATTERN]
var regex = RegEx.new()
regex.compile(input_schema[JSKW_PATTERN])
if regex.search(input_data) == null:
return ERR_INVALID_JSON_GEN % ERR_WRONG_PATTERN % property_name
if input_schema.has(JSKW_FORMAT):
# validate "color" format
if input_schema.format.to_lower() == JSKW_COLOR:
if not input_data.is_valid_html_color():
return ERR_INVALID_JSON_GEN % ERR_FORMAT % [property_name, JSKW_COLOR]
return error

View File

@@ -0,0 +1 @@
uid://b2ogwk8l310et