From 2c61b49b57ec8fa731c6fee8db564f6b8d3cf374 Mon Sep 17 00:00:00 2001 From: redPanther Date: Thu, 29 Dec 2016 23:27:33 +0100 Subject: [PATCH] add json check before compile (#354) * add json check before compile * do more checking on effects * add effects checking * better effects checking. * integrate schema check for default config reduce size of default configs --- .gitignore | 1 + CMakeLists.txt | 35 + HyperionConfig.h.in | 5 +- config/hyperion.config.json.commented | 252 ++---- config/hyperion.config.json.default | 256 ++---- .../{trails.json => trails.schema.json} | 0 test/jsonchecks/COPYING | 19 + test/jsonchecks/checkeffects.py | 50 ++ test/jsonchecks/checkjson.py | 23 + test/jsonchecks/checkschema.py | 21 + test/jsonchecks/jsonschema.py | 734 ++++++++++++++++++ 11 files changed, 1001 insertions(+), 395 deletions(-) rename effects/schema/{trails.json => trails.schema.json} (100%) create mode 100644 test/jsonchecks/COPYING create mode 100644 test/jsonchecks/checkeffects.py create mode 100644 test/jsonchecks/checkjson.py create mode 100644 test/jsonchecks/checkschema.py create mode 100644 test/jsonchecks/jsonschema.py diff --git a/.gitignore b/.gitignore index 272d4c32..3cffde1c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ CMakeCache.txt /HyperionConfig.h /lib .directory +*.pyc diff --git a/CMakeLists.txt b/CMakeLists.txt index fadeda3d..061b46c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ SET ( HYPERION_VERSION_STABLE OFF ) SET ( HYPERION_VERSION_MAJOR 2 ) SET ( HYPERION_VERSION_MINOR 0 ) SET ( HYPERION_VERSION_PATCH 0 ) +SET ( CURRENT_CONFIG_VERSION 2 ) SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_DISPMANX OFF ) @@ -140,6 +141,40 @@ message(STATUS "ENABLE_PROFILER = ${ENABLE_PROFILER}") SET ( PROTOBUF_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/proto ) SET ( PROTOBUF_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/proto ) +# check all json files +FILE ( GLOB_RECURSE HYPERION_SCHEMAS RELATIVE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/libsrc/*schema*.json ) +SET( JSON_FILES + config/hyperion.config.json.default + ${HYPERION_SCHEMAS} +) +EXECUTE_PROCESS ( + COMMAND python test/jsonchecks/checkjson.py ${JSON_FILES} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE CHECK_JSON_FAILED +) +IF ( ${CHECK_JSON_FAILED} ) + MESSAGE (FATAL_ERROR "check of json files failed" ) +ENDIF () + +EXECUTE_PROCESS ( + COMMAND python test/jsonchecks/checkeffects.py effects effects/schema + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE CHECK_EFFECTS_FAILED +) +IF ( ${CHECK_EFFECTS_FAILED} ) + MESSAGE (FATAL_ERROR "check of json effect files failed" ) +ENDIF () + +EXECUTE_PROCESS ( + COMMAND python test/jsonchecks/checkschema.py config/hyperion.config.json.default libsrc/hyperion/schemas/hyperion.schema-${CURRENT_CONFIG_VERSION}.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE CHECK_CONFIG_FAILED +) +IF ( ${CHECK_CONFIG_FAILED} ) + MESSAGE (FATAL_ERROR "check of json default config failed" ) +ENDIF () + + # Createt the configuration file # Add project specific cmake modules (find, etc) diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 5197643b..483d4ab7 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -30,6 +30,9 @@ // Define to enable profiler for development purpose #cmakedefine ENABLE_PROFILER +// Define version id of current config +#define CURRENT_CONFIG_VERSION ${CURRENT_CONFIG_VERSION} + // the hyperion build id string #define HYPERION_BUILD_ID "${HYPERION_BUILD_ID}" @@ -40,4 +43,4 @@ #define HYPERION_JSON_VERSION "1.0.0" -#define CURRENT_CONFIG_VERSION 2 + diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 238389fe..246639b2 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -70,7 +70,7 @@ { "imageToLedMappingType" : "multicolor_mean", "channelAdjustment_enable" : true, - "channelAdjustment_v4l_only" : true, + "channelAdjustment_v4l_only" : false, "channelAdjustment" : [ { @@ -87,7 +87,7 @@ } ], "transform_enable" : true, - "transform_v4l_only" : true, + "transform_v4l_only" : false, "transform" : [ { @@ -381,234 +381,94 @@ "leds" : [ { - "index" : 0, - "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 0, + "hscan": { "maximum": 0.1667, "minimum": 0 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 1, - "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 1, + "hscan": { "maximum": 0.3332, "minimum": 0.1667 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 2, - "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 2, + "hscan": { "maximum": 0.5, "minimum": 0.3333 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 3, - "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 3, + "hscan": { "maximum": 0.6667, "minimum": 0.5 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 4, - "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 4, + "hscan": { "maximum": 0.8333, "minimum": 0.6667 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 5, - "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 5, + "hscan": { "maximum": 1, "minimum": 0.8333 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 6, - "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 6, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 0.3333, "minimum": 0 } }, { - "index" : 7, - "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 7, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 0.6667, "minimum": 0.3333 } }, { - "index" : 8, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 8, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 1, "minimum": 0.6667 } }, { - "index" : 9, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } + "index": 9, + "hscan": { "maximum": 1, "minimum": 0.8333 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 10, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } + "index": 10, + "hscan": { "maximum": 0.8333, "minimum": 0.6667 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 11, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } + "index": 11, + "hscan": { "maximum": 0.6667, "minimum": 0.5 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 12, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } + "index": 12, + "hscan": { "maximum": 0.5, "minimum": 0.3333 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 13, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } + "index": 13, + "hscan": { "maximum": 0.3333, "minimum": 0.1667 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 14, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } + "index": 14, + "hscan": { "maximum": 0.1667, "minimum": 0 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 15, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } + "index": 15, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 1, "minimum": 0.6667 } }, { - "index" : 16, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + "index": 16, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 0.6667, "minimum": 0.3333 } }, { - "index" : 17, - "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 18, - "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 19, - "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 20, - "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 21, - "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 22, - "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 23, - "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 24, - "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 25, - "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 26, - "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 27, - "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 28, - "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 29, - "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 30, - "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 31, - "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 32, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } - }, - { - "index" : 33, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } - }, - { - "index" : 34, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } - }, - { - "index" : 35, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } - }, - { - "index" : 36, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } - }, - { - "index" : 37, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } - }, - { - "index" : 38, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } - }, - { - "index" : 39, - "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 40, - "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 41, - "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 42, - "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 43, - "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 44, - "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 45, - "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 17, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 0.3333, "minimum": 0 } } ] } diff --git a/config/hyperion.config.json.default b/config/hyperion.config.json.default index 22752b22..3c68cd36 100644 --- a/config/hyperion.config.json.default +++ b/config/hyperion.config.json.default @@ -22,7 +22,7 @@ { "imageToLedMappingType" : "multicolor_mean", "channelAdjustment_enable" : true, - "channelAdjustment_v4l_only" : true, + "channelAdjustment_v4l_only" : false, "channelAdjustment" : [ { @@ -39,7 +39,7 @@ } ], "transform_enable" : true, - "transform_v4l_only" : true, + "transform_v4l_only" : false, "transform" : [ { @@ -114,7 +114,7 @@ "type" : "auto", "width" : 80, "height" : 45, - "frequency_Hz" : 10.0, + "frequency_Hz" : 10, "priority" : 900 }, @@ -190,237 +190,97 @@ "paths" : ["../custom-effects"] }, - "leds" : + "leds": [ { - "index" : 0, - "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 0, + "hscan": { "maximum": 0.1667, "minimum": 0 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 1, - "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 1, + "hscan": { "maximum": 0.3332, "minimum": 0.1667 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 2, - "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 2, + "hscan": { "maximum": 0.5, "minimum": 0.3333 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 3, - "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 3, + "hscan": { "maximum": 0.6667, "minimum": 0.5 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 4, - "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 4, + "hscan": { "maximum": 0.8333, "minimum": 0.6667 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 5, - "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 5, + "hscan": { "maximum": 1, "minimum": 0.8333 }, + "vscan": { "maximum": 0.0800, "minimum": 0 } }, { - "index" : 6, - "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 6, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 0.3333, "minimum": 0 } }, { - "index" : 7, - "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 7, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 0.6667, "minimum": 0.3333 } }, { - "index" : 8, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 8, + "hscan": { "maximum": 1, "minimum": 0.95 }, + "vscan": { "maximum": 1, "minimum": 0.6667 } }, { - "index" : 9, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } + "index": 9, + "hscan": { "maximum": 1, "minimum": 0.8333 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 10, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } + "index": 10, + "hscan": { "maximum": 0.8333, "minimum": 0.6667 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 11, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } + "index": 11, + "hscan": { "maximum": 0.6667, "minimum": 0.5 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 12, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } + "index": 12, + "hscan": { "maximum": 0.5, "minimum": 0.3333 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 13, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } + "index": 13, + "hscan": { "maximum": 0.3333, "minimum": 0.1667 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 14, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } + "index": 14, + "hscan": { "maximum": 0.1667, "minimum": 0 }, + "vscan": { "maximum": 1, "minimum": 0.92 } }, { - "index" : 15, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } + "index": 15, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 1, "minimum": 0.6667 } }, { - "index" : 16, - "hscan" : { "minimum" : 0.0000, "maximum" : 0.0625 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } + "index": 16, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 0.6667, "minimum": 0.3333 } }, { - "index" : 17, - "hscan" : { "minimum" : 0.0625, "maximum" : 0.1250 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 18, - "hscan" : { "minimum" : 0.1250, "maximum" : 0.1875 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 19, - "hscan" : { "minimum" : 0.1875, "maximum" : 0.2500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 20, - "hscan" : { "minimum" : 0.2500, "maximum" : 0.3125 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 21, - "hscan" : { "minimum" : 0.3125, "maximum" : 0.3750 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 22, - "hscan" : { "minimum" : 0.3750, "maximum" : 0.4375 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 23, - "hscan" : { "minimum" : 0.4375, "maximum" : 0.5000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 24, - "hscan" : { "minimum" : 0.5000, "maximum" : 0.5625 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 25, - "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 26, - "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 27, - "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 28, - "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 29, - "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 30, - "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 31, - "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.0800 } - }, - { - "index" : 32, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.0000, "maximum" : 0.1429 } - }, - { - "index" : 33, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.1429, "maximum" : 0.2857 } - }, - { - "index" : 34, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.2857, "maximum" : 0.4286 } - }, - { - "index" : 35, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.4286, "maximum" : 0.5714 } - }, - { - "index" : 36, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.5714, "maximum" : 0.7143 } - }, - { - "index" : 37, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.7143, "maximum" : 0.8571 } - }, - { - "index" : 38, - "hscan" : { "minimum" : 0.9500, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.8571, "maximum" : 1.0000 } - }, - { - "index" : 39, - "hscan" : { "minimum" : 0.9375, "maximum" : 1.0000 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 40, - "hscan" : { "minimum" : 0.8750, "maximum" : 0.9375 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 41, - "hscan" : { "minimum" : 0.8125, "maximum" : 0.8750 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 42, - "hscan" : { "minimum" : 0.7500, "maximum" : 0.8125 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 43, - "hscan" : { "minimum" : 0.6875, "maximum" : 0.7500 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 44, - "hscan" : { "minimum" : 0.6250, "maximum" : 0.6875 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } - }, - { - "index" : 45, - "hscan" : { "minimum" : 0.5625, "maximum" : 0.6250 }, - "vscan" : { "minimum" : 0.9200, "maximum" : 1.0000 } + "index": 17, + "hscan": { "maximum": 0.05, "minimum": 0 }, + "vscan": { "maximum": 0.3333, "minimum": 0 } } ] } diff --git a/effects/schema/trails.json b/effects/schema/trails.schema.json similarity index 100% rename from effects/schema/trails.json rename to effects/schema/trails.schema.json diff --git a/test/jsonchecks/COPYING b/test/jsonchecks/COPYING new file mode 100644 index 00000000..d8338a32 --- /dev/null +++ b/test/jsonchecks/COPYING @@ -0,0 +1,19 @@ +Copyright (c) 2011 Julian Berman + +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. diff --git a/test/jsonchecks/checkeffects.py b/test/jsonchecks/checkeffects.py new file mode 100644 index 00000000..45f308be --- /dev/null +++ b/test/jsonchecks/checkeffects.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +import json, sys, glob +from os import path +from jsonschema import Draft3Validator + +print("-- validate json effect files") + +jsonFiles = sys.argv[1] +schemaFiles = sys.argv[2] + +retval = 0 +total = 0 +errors = 0 +with open("libsrc/effectengine/EffectDefinition.schema.json") as baseSchemaFile: + baseSchema = json.load(baseSchemaFile) + baseValidator = Draft3Validator(baseSchema) + for filename in glob.glob(jsonFiles+'/*.json'): + with open(filename) as f: + total += 1 + msg = " check effect %s ... " % filename + try: + effect = json.load(f) + script = path.basename(effect['script']) + if not path.exists(jsonFiles+'/'+script): + raise ValueError('script file: '+script+' not found.') + + schema = path.splitext(script)[0]+'.schema.json' + if not path.exists(jsonFiles+'/schema/'+schema): + raise ValueError('schema file: '+schema+' not found.') + schema = jsonFiles+'/schema/'+schema + + # validate against schema + with open(schema) as s: + effectSchema = json.load(s) + Draft3Validator.check_schema(effectSchema) + validator = Draft3Validator(effectSchema) + baseValidator.validate(effect) + validator.validate(effect['args']) + + #print(msg + "ok") + + except Exception as e: + print(msg + 'error ('+str(e)+')') + errors += 1 + retval = 1 + + +print(" checked effect files: %s success: %s errors: %s" % (total,(total-errors),errors)) + +sys.exit(retval) diff --git a/test/jsonchecks/checkjson.py b/test/jsonchecks/checkjson.py new file mode 100644 index 00000000..d1f8e477 --- /dev/null +++ b/test/jsonchecks/checkjson.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +import json, sys + +print("-- validate json files") + +retval = 0 +total = 0 +errors = 0 +for filename in sys.argv[1:]: + with open(filename) as f: + total += 1 + msg = " check json %s ... " % filename + try: + json.load(f) + #print(msg + "ok") + except ValueError as e: + print(msg + 'invalid') + retval = 1 + errors += 1 + +print(" checked files: %s success: %s errors: %s" % (total,(total-errors),errors)) + +sys.exit(retval) diff --git a/test/jsonchecks/checkschema.py b/test/jsonchecks/checkschema.py new file mode 100644 index 00000000..c7269149 --- /dev/null +++ b/test/jsonchecks/checkschema.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python +import json, sys, glob +from os import path +from jsonschema import Draft3Validator + +print('-- validate json file') + +jsonFileName = sys.argv[1] +schemaFileName = sys.argv[2] + +try: + with open(schemaFileName) as schemaFile: + with open(jsonFileName) as jsonFile: + j = json.load(jsonFile) + validator = Draft3Validator(json.load(schemaFile)) + validator.validate(j) +except Exception as e: + print('validation error: '+jsonFileName + ' '+schemaFileName+' ('+str(e)+')') + sys.exit(1) + +sys.exit(0) diff --git a/test/jsonchecks/jsonschema.py b/test/jsonchecks/jsonschema.py new file mode 100644 index 00000000..bb5a97c3 --- /dev/null +++ b/test/jsonchecks/jsonschema.py @@ -0,0 +1,734 @@ +""" +An implementation of JSON Schema for Python + +The main functionality is provided by the validator classes for each of the +supported JSON Schema versions. + +Most commonly, the :function:`validate` function is the quickest way to simply +validate a given instance under a schema, and will create a validator for you. + +""" + +from __future__ import division, unicode_literals + +import collections +import itertools +import operator +import re +import sys +import warnings + + +__version__ = "0.7" + +FLOAT_TOLERANCE = 10 ** -15 +PY3 = sys.version_info[0] >= 3 + +if PY3: + basestring = unicode = str + iteritems = operator.methodcaller("items") + from urllib.parse import unquote +else: + from itertools import izip as zip + iteritems = operator.methodcaller("iteritems") + from urllib import unquote + + +class UnknownType(Exception): + """ + An unknown type was given. + + """ + + +class InvalidRef(Exception): + """ + An invalid reference was given. + + """ + + +class SchemaError(Exception): + """ + The provided schema is malformed. + + The same attributes exist for ``SchemaError``s as for ``ValidationError``s. + + """ + + validator = None + + def __init__(self, message): + super(SchemaError, self).__init__(message) + self.message = message + self.path = [] + + +class ValidationError(Exception): + """ + The instance didn't properly validate with the provided schema. + + Relevant attributes are: + * ``message`` : a human readable message explaining the error + * ``path`` : a list containing the path to the offending element (or [] + if the error happened globally) in *reverse* order (i.e. + deepest index first). + + """ + + # the failing validator will be set externally at whatever recursion level + # is immediately above the validation failure + validator = None + + def __init__(self, message): + super(ValidationError, self).__init__(message) + self.message = message + + # Any validator that recurses must append to the ValidationError's + # path (e.g., properties and items) + self.path = [] + + +class Draft3Validator(object): + """ + A validator for JSON Schema draft 3. + + """ + + DEFAULT_TYPES = { + "array" : list, "boolean" : bool, "integer" : int, "null" : type(None), + "number" : (int, float), "object" : dict, "string" : basestring, + } + + def __init__(self, schema, types=()): + """ + Initialize a validator. + + ``schema`` should be a *valid* JSON Schema object already converted to + a native Python object (typically a dict via ``json.load``). + + ``types`` is a mapping (or iterable of 2-tuples) containing additional + types or alternate types to verify via the 'type' property. For + instance, the default types for the 'number' JSON Schema type are + ``int`` and ``float``. To override this behavior (e.g. for also + allowing ``decimal.Decimal``), pass ``types={"number" : (int, float, + decimal.Decimal)} *including* the default types if so desired, which + are fairly obvious but can be accessed via the ``DEFAULT_TYPES`` + attribute on this class if necessary. + + """ + + self._types = dict(self.DEFAULT_TYPES) + self._types.update(types) + self._types["any"] = tuple(self._types.values()) + + self.schema = schema + + def is_type(self, instance, type): + """ + Check if an ``instance`` is of the provided (JSON Schema) ``type``. + + """ + + if type not in self._types: + raise UnknownType(type) + type = self._types[type] + + # bool inherits from int, so ensure bools aren't reported as integers + if isinstance(instance, bool): + type = _flatten(type) + if int in type and bool not in type: + return False + return isinstance(instance, type) + + def is_valid(self, instance, _schema=None): + """ + Check if the ``instance`` is valid under the current schema. + + Returns a bool indicating whether validation succeeded. + + """ + + error = next(self.iter_errors(instance, _schema), None) + return error is None + + @classmethod + def check_schema(cls, schema): + """ + Validate a ``schema`` against the meta-schema to see if it is valid. + + """ + + for error in cls(cls.META_SCHEMA).iter_errors(schema): + s = SchemaError(error.message) + s.path = error.path + s.validator = error.validator + # I think we're safer raising these always, not yielding them + raise s + + def iter_errors(self, instance, _schema=None): + """ + Lazily yield each of the errors in the given ``instance``. + + """ + + if _schema is None: + _schema = self.schema + + for k, v in iteritems(_schema): + validator = getattr(self, "validate_%s" % (k.lstrip("$"),), None) + + if validator is None: + continue + + errors = validator(v, instance, _schema) or () + for error in errors: + # if the validator hasn't already been set (due to recursion) + # make sure to set it + error.validator = error.validator or k + yield error + + def validate(self, *args, **kwargs): + """ + Validate an ``instance`` under the given ``schema``. + + """ + + for error in self.iter_errors(*args, **kwargs): + raise error + + def validate_type(self, types, instance, schema): + types = _list(types) + + for type in types: + # Ouch. Brain hurts. Two paths here, either we have a schema, then + # check if the instance is valid under it + if (( + self.is_type(type, "object") and + self.is_valid(instance, type) + + # Or we have a type as a string, just check if the instance is that + # type. Also, HACK: we can reach the `or` here if skip_types is + # something other than error. If so, bail out. + + ) or ( + self.is_type(type, "string") and + (self.is_type(instance, type) or type not in self._types) + )): + return + else: + yield ValidationError(_types_msg(instance, types)) + + def validate_properties(self, properties, instance, schema): + if not self.is_type(instance, "object"): + return + + for property, subschema in iteritems(properties): + if property in instance: + for error in self.iter_errors(instance[property], subschema): + error.path.append(property) + yield error + elif subschema.get("required", False): + error = ValidationError( + "%r is a required property" % (property,) + ) + error.path.append(property) + error.validator = "required" + yield error + + def validate_patternProperties(self, patternProperties, instance, schema): + for pattern, subschema in iteritems(patternProperties): + for k, v in iteritems(instance): + if re.match(pattern, k): + for error in self.iter_errors(v, subschema): + yield error + + def validate_additionalProperties(self, aP, instance, schema): + if not self.is_type(instance, "object"): + return + + extras = set(_find_additional_properties(instance, schema)) + + if self.is_type(aP, "object"): + for extra in extras: + for error in self.iter_errors(instance[extra], aP): + yield error + elif not aP and extras: + error = "Additional properties are not allowed (%s %s unexpected)" + yield ValidationError(error % _extras_msg(extras)) + + def validate_dependencies(self, dependencies, instance, schema): + if not self.is_type(instance, "object"): + return + + for property, dependency in iteritems(dependencies): + if property not in instance: + continue + + if self.is_type(dependency, "object"): + for error in self.iter_errors(instance, dependency): + yield error + else: + dependencies = _list(dependency) + for dependency in dependencies: + if dependency not in instance: + yield ValidationError( + "%r is a dependency of %r" % (dependency, property) + ) + + def validate_items(self, items, instance, schema): + if not self.is_type(instance, "array"): + return + + if self.is_type(items, "object"): + for index, item in enumerate(instance): + for error in self.iter_errors(item, items): + error.path.append(index) + yield error + else: + for (index, item), subschema in zip(enumerate(instance), items): + for error in self.iter_errors(item, subschema): + error.path.append(index) + yield error + + def validate_additionalItems(self, aI, instance, schema): + if not self.is_type(instance, "array"): + return + if not self.is_type(schema.get("items"), "array"): + return + + if self.is_type(aI, "object"): + for item in instance[len(schema):]: + for error in self.iter_errors(item, aI): + yield error + elif not aI and len(instance) > len(schema.get("items", [])): + error = "Additional items are not allowed (%s %s unexpected)" + yield ValidationError( + error % _extras_msg(instance[len(schema.get("items", [])):]) + ) + + def validate_minimum(self, minimum, instance, schema): + if not self.is_type(instance, "number"): + return + + instance = float(instance) + if schema.get("exclusiveMinimum", False): + failed = instance <= minimum + cmp = "less than or equal to" + else: + failed = instance < minimum + cmp = "less than" + + if failed: + yield ValidationError( + "%r is %s the minimum of %r" % (instance, cmp, minimum) + ) + + def validate_maximum(self, maximum, instance, schema): + if not self.is_type(instance, "number"): + return + + instance = float(instance) + if schema.get("exclusiveMaximum", False): + failed = instance >= maximum + cmp = "greater than or equal to" + else: + failed = instance > maximum + cmp = "greater than" + + if failed: + yield ValidationError( + "%r is %s the maximum of %r" % (instance, cmp, maximum) + ) + + def validate_minItems(self, mI, instance, schema): + if self.is_type(instance, "array") and len(instance) < mI: + yield ValidationError("%r is too short" % (instance,)) + + def validate_maxItems(self, mI, instance, schema): + if self.is_type(instance, "array") and len(instance) > mI: + yield ValidationError("%r is too long" % (instance,)) + + def validate_uniqueItems(self, uI, instance, schema): + if uI and self.is_type(instance, "array") and not _uniq(instance): + yield ValidationError("%r has non-unique elements" % instance) + + def validate_pattern(self, patrn, instance, schema): + if self.is_type(instance, "string") and not re.match(patrn, instance): + yield ValidationError("%r does not match %r" % (instance, patrn)) + + def validate_minLength(self, mL, instance, schema): + if self.is_type(instance, "string") and len(instance) < mL: + yield ValidationError("%r is too short" % (instance,)) + + def validate_maxLength(self, mL, instance, schema): + if self.is_type(instance, "string") and len(instance) > mL: + yield ValidationError("%r is too long" % (instance,)) + + def validate_enum(self, enums, instance, schema): + if instance not in enums: + yield ValidationError("%r is not one of %r" % (instance, enums)) + + def validate_divisibleBy(self, dB, instance, schema): + if not self.is_type(instance, "number"): + return + + if isinstance(dB, float): + mod = instance % dB + failed = (mod > FLOAT_TOLERANCE) and (dB - mod) > FLOAT_TOLERANCE + else: + failed = instance % dB + + if failed: + yield ValidationError("%r is not divisible by %r" % (instance, dB)) + + def validate_disallow(self, disallow, instance, schema): + for disallowed in _list(disallow): + if self.is_valid(instance, {"type" : [disallowed]}): + yield ValidationError( + "%r is disallowed for %r" % (disallowed, instance) + ) + + def validate_extends(self, extends, instance, schema): + if self.is_type(extends, "object"): + extends = [extends] + for subschema in extends: + for error in self.iter_errors(instance, subschema): + yield error + + def validate_ref(self, ref, instance, schema): + if ref != "#" and not ref.startswith("#/"): + warnings.warn("jsonschema only supports json-pointer $refs") + return + + resolved = resolve_json_pointer(self.schema, ref) + for error in self.iter_errors(instance, resolved): + yield error + + +Draft3Validator.META_SCHEMA = { + "$schema" : "http://json-schema.org/draft-03/schema#", + "id" : "http://json-schema.org/draft-03/schema#", + "type" : "object", + + "properties" : { + "type" : { + "type" : ["string", "array"], + "items" : {"type" : ["string", {"$ref" : "#"}]}, + "uniqueItems" : True, + "default" : "any" + }, + "properties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#", "type": "object"}, + "default" : {} + }, + "patternProperties" : { + "type" : "object", + "additionalProperties" : {"$ref" : "#"}, + "default" : {} + }, + "additionalProperties" : { + "type" : [{"$ref" : "#"}, "boolean"], "default" : {} + }, + "items" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + "additionalItems" : { + "type" : [{"$ref" : "#"}, "boolean"], "default" : {} + }, + "required" : {"type" : "boolean", "default" : False}, + "dependencies" : { + "type" : ["string", "array", "object"], + "additionalProperties" : { + "type" : ["string", "array", {"$ref" : "#"}], + "items" : {"type" : "string"} + }, + "default" : {} + }, + "minimum" : {"type" : "number"}, + "maximum" : {"type" : "number"}, + "exclusiveMinimum" : {"type" : "boolean", "default" : False}, + "exclusiveMaximum" : {"type" : "boolean", "default" : False}, + "minItems" : {"type" : "integer", "minimum" : 0, "default" : 0}, + "maxItems" : {"type" : "integer", "minimum" : 0}, + "uniqueItems" : {"type" : "boolean", "default" : False}, + "pattern" : {"type" : "string", "format" : "regex"}, + "minLength" : {"type" : "integer", "minimum" : 0, "default" : 0}, + "maxLength" : {"type" : "integer"}, + "enum" : {"type" : "array", "minItems" : 1, "uniqueItems" : True}, + "default" : {"type" : "any"}, + "title" : {"type" : "string"}, + "description" : {"type" : "string"}, + "format" : {"type" : "string"}, + "maxDecimal" : {"type" : "number", "minimum" : 0}, + "divisibleBy" : { + "type" : "number", + "minimum" : 0, + "exclusiveMinimum" : True, + "default" : 1 + }, + "disallow" : { + "type" : ["string", "array"], + "items" : {"type" : ["string", {"$ref" : "#"}]}, + "uniqueItems" : True + }, + "extends" : { + "type" : [{"$ref" : "#"}, "array"], + "items" : {"$ref" : "#"}, + "default" : {} + }, + "id" : {"type" : "string", "format" : "uri"}, + "$ref" : {"type" : "string", "format" : "uri"}, + "$schema" : {"type" : "string", "format" : "uri"}, + }, + "dependencies" : { + "exclusiveMinimum" : "minimum", "exclusiveMaximum" : "maximum" + }, +} + + +class Validator(Draft3Validator): + """ + Deprecated: Use :class:`Draft3Validator` instead. + + """ + + def __init__( + self, version=None, unknown_type="skip", unknown_property="skip", + *args, **kwargs + ): + super(Validator, self).__init__({}, *args, **kwargs) + warnings.warn( + "Validator is deprecated and will be removed. " + "Use Draft3Validator instead.", + DeprecationWarning, stacklevel=2, + ) + + +class ErrorTree(object): + """ + ErrorTrees make it easier to check which validations failed. + + """ + + def __init__(self, errors=()): + self.errors = {} + self._contents = collections.defaultdict(self.__class__) + + for error in errors: + container = self + for element in reversed(error.path): + container = container[element] + container.errors[error.validator] = error + + def __contains__(self, k): + return k in self._contents + + def __getitem__(self, k): + return self._contents[k] + + def __setitem__(self, k, v): + self._contents[k] = v + + def __iter__(self): + return iter(self._contents) + + def __len__(self): + child_errors = sum(len(tree) for _, tree in iteritems(self._contents)) + return len(self.errors) + child_errors + + def __repr__(self): + return "<%s (%s errors)>" % (self.__class__.__name__, len(self)) + + +def resolve_json_pointer(schema, ref): + """ + Resolve a local reference ``ref`` within the given root ``schema``. + + ``ref`` should be a local ref whose ``#`` is still present. + + """ + + if ref == "#": + return schema + + parts = ref.lstrip("#/").split("/") + + parts = map(unquote, parts) + parts = [part.replace('~1', '/').replace('~0', '~') for part in parts] + + try: + for part in parts: + schema = schema[part] + except KeyError: + raise InvalidRef("Unresolvable json-pointer %r" % ref) + else: + return schema + + +def _find_additional_properties(instance, schema): + """ + Return the set of additional properties for the given ``instance``. + + Weeds out properties that should have been validated by ``properties`` and + / or ``patternProperties``. + + Assumes ``instance`` is dict-like already. + + """ + + properties = schema.get("properties", {}) + patterns = "|".join(schema.get("patternProperties", {})) + for property in instance: + if property not in properties: + if patterns and re.search(patterns, property): + continue + yield property + + +def _extras_msg(extras): + """ + Create an error message for extra items or properties. + + """ + + if len(extras) == 1: + verb = "was" + else: + verb = "were" + return ", ".join(repr(extra) for extra in extras), verb + + +def _types_msg(instance, types): + """ + Create an error message for a failure to match the given types. + + If the ``instance`` is an object and contains a ``name`` property, it will + be considered to be a description of that object and used as its type. + + Otherwise the message is simply the reprs of the given ``types``. + + """ + + reprs = [] + for type in types: + try: + reprs.append(repr(type["name"])) + except Exception: + reprs.append(repr(type)) + return "%r is not of type %s" % (instance, ", ".join(reprs)) + + +def _flatten(suitable_for_isinstance): + """ + isinstance() can accept a bunch of really annoying different types: + * a single type + * a tuple of types + * an arbitrary nested tree of tuples + + Return a flattened tuple of the given argument. + + """ + + types = set() + + if not isinstance(suitable_for_isinstance, tuple): + suitable_for_isinstance = (suitable_for_isinstance,) + for thing in suitable_for_isinstance: + if isinstance(thing, tuple): + types.update(_flatten(thing)) + else: + types.add(thing) + return tuple(types) + + +def _list(thing): + """ + Wrap ``thing`` in a list if it's a single str. + + Otherwise, return it unchanged. + + """ + + if isinstance(thing, basestring): + return [thing] + return thing + + +def _delist(thing): + """ + Unwrap ``thing`` to a single element if its a single str in a list. + + Otherwise, return it unchanged. + + """ + + if ( + isinstance(thing, list) and + len(thing) == 1 + and isinstance(thing[0], basestring) + ): + return thing[0] + return thing + + +def _uniq(container): + """ + Check if all of a container's elements are unique. + + Successively tries first to rely that the elements are hashable, then + falls back on them being sortable, and finally falls back on brute + force. + + """ + + try: + return len(set(container)) == len(container) + except TypeError: + try: + sort = sorted(container) + sliced = itertools.islice(container, 1, None) + for i, j in zip(container, sliced): + if i == j: + return False + except (NotImplementedError, TypeError): + seen = [] + for e in container: + if e in seen: + return False + seen.append(e) + return True + + +def validate(instance, schema, cls=Draft3Validator, *args, **kwargs): + """ + Validate an ``instance`` under the given ``schema``. + + First verifies that the provided schema is itself valid, since not doing so + can lead to less obvious failures when validating. If you know it is or + don't care, use ``YourValidator(schema).validate(instance)`` directly + instead (e.g. ``Draft3Validator``). + + ``cls`` is a validator class that will be used to validate the instance. + By default this is a draft 3 validator. Any other provided positional and + keyword arguments will be provided to this class when constructing a + validator. + + """ + + + meta_validate = kwargs.pop("meta_validate", None) + + if meta_validate is not None: + warnings.warn( + "meta_validate is deprecated and will be removed. If you do not " + "want to validate a schema, use Draft3Validator.validate instead.", + DeprecationWarning, stacklevel=2, + ) + + if meta_validate is not False: # yes this is needed since True was default + cls.check_schema(schema) + cls(schema, *args, **kwargs).validate(instance)