diff --git a/api/CMakeLists.txt b/api/CMakeLists.txt index 48c41b1655..824cf80bb5 100644 --- a/api/CMakeLists.txt +++ b/api/CMakeLists.txt @@ -132,3 +132,19 @@ if( APPLE ) "@executable_path/../Frameworks" ) set_target_properties( kiapi PROPERTIES BUILD_WITH_INSTALL_RPATH 1 ) endif() + +if( NOT (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_BINARY_DIR} ) ) + file( GLOB SCHEMA_FILES ${CMAKE_CURRENT_SOURCE_DIR}/schemas/*.json ) + + add_custom_target( api_schema_build_copy ALL + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/schemas ${CMAKE_BINARY_DIR}/schemas + DEPENDS ${SCHEMA_FILES} + COMMENT "Copying API schema files into build directory" + ) +endif() + +INSTALL( DIRECTORY + schemas + DESTINATION ${KICAD_DATA} + COMPONENT Runtime +) diff --git a/api/schemas/api.v1.schema.json b/api/schemas/api.v1.schema.json new file mode 100644 index 0000000000..8c82dfd197 --- /dev/null +++ b/api/schemas/api.v1.schema.json @@ -0,0 +1,108 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://go.kicad.org/api/schemas/v1", + "title": "KiCad API Plugin Schema", + "description": "KiCad IPC API Plugin and Action schema", + "$ref": "#/definitions/Plugin", + "definitions": { + "Plugin": { + "type": "object", + "properties": { + "identifier": { + "type": "string", + "description": "Plugin identifier", + "pattern": "^[a-zA-Z][-_a-zA-Z0-9.]{0,98}[a-zA-Z0-9]$" + }, + "name": { + "type": "string", + "maxLength": 200 + }, + "description": { + "type": "string", + "maxLength": 500 + }, + "runtime": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "python", + "exec" + ], + "description": "How KiCad should launch the plugin" + }, + "min_version": { + "type": "string" + } + }, + "required": [ + "type" + ] + }, + "actions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "identifier": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "show-button": { + "type": "boolean" + }, + "scopes": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "pcb", + "schematic", + "footprint", + "symbol", + "project_manager" + ] + } + }, + "entrypoint": { + "type": "string" + }, + "icons-light": { + "type": "array", + "items": { + "type": "string" + } + }, + "icons-dark": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "identifier", + "name", + "description", + "show-button", + "entrypoint" + ] + } + } + }, + "required": [ + "identifier", + "name", + "description", + "runtime", + "actions" + ] + } + } +} diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index a17a54068b..1d5bf654d7 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -161,6 +161,7 @@ set( KICOMMON_SRCS gestfich.cpp increment.cpp json_conversions.cpp + json_schema_validator.cpp kidialog.cpp kiid.cpp kiway.cpp @@ -226,6 +227,7 @@ target_link_libraries( kicommon kimath kiplatform nlohmann_json + nlohmann_json_schema_validator fmt::fmt CURL::libcurl picosha2 @@ -304,6 +306,7 @@ target_include_directories( kicommon $<TARGET_PROPERTY:expected,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:kiapi,INTERFACE_INCLUDE_DIRECTORIES> $<TARGET_PROPERTY:picosha2,INTERFACE_INCLUDE_DIRECTORIES> + $<TARGET_PROPERTY:nlohmann_json_schema_validator,INTERFACE_INCLUDE_DIRECTORIES> ) add_dependencies( kicommon pegtl version_header ) diff --git a/common/api/api_plugin.cpp b/common/api/api_plugin.cpp index 750f569ee3..5ae559be4b 100644 --- a/common/api/api_plugin.cpp +++ b/common/api/api_plugin.cpp @@ -29,6 +29,28 @@ #include <api/api_plugin_manager.h> #include <api/api_utils.h> #include <json_conversions.h> +#include <json_schema_validator.h> + + +class LOGGING_ERROR_HANDLER : public nlohmann::json_schema::error_handler +{ +public: + LOGGING_ERROR_HANDLER() : m_hasError( false ) {} + + bool HasError() const { return m_hasError; } + + void error( const nlohmann::json::json_pointer& ptr, const nlohmann::json& instance, + const std::string& message ) override + { + m_hasError = true; + wxLogTrace( traceApi, + wxString::Format( wxS( "JSON error: at %s, value:\n%s\n%s" ), + ptr.to_string(), instance.dump(), message ) ); + } + +private: + bool m_hasError; +}; bool PLUGIN_RUNTIME::FromJson( const nlohmann::json& aJson ) @@ -54,7 +76,8 @@ bool PLUGIN_RUNTIME::FromJson( const nlohmann::json& aJson ) struct API_PLUGIN_CONFIG { - API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile ); + API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile, + const JSON_SCHEMA_VALIDATOR& aValidator ); bool valid; wxString identifier; @@ -67,7 +90,8 @@ struct API_PLUGIN_CONFIG }; -API_PLUGIN_CONFIG::API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile ) : +API_PLUGIN_CONFIG::API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aConfigFile, + const JSON_SCHEMA_VALIDATOR& aValidator ) : parent( aParent ) { valid = false; @@ -94,7 +118,11 @@ API_PLUGIN_CONFIG::API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aCo return; } - // TODO add schema and validate + LOGGING_ERROR_HANDLER handler; + aValidator.Validate( js, handler, nlohmann::json_uri( "#/definitions/Plugin" ) ); + + if( !handler.HasError() ) + wxLogTrace( traceApi, "Plugin: schema validation successful" ); // All of these are required; any exceptions here leave us with valid == false try @@ -151,9 +179,9 @@ API_PLUGIN_CONFIG::API_PLUGIN_CONFIG( API_PLUGIN& aParent, const wxFileName& aCo } -API_PLUGIN::API_PLUGIN( const wxFileName& aConfigFile ) : +API_PLUGIN::API_PLUGIN( const wxFileName& aConfigFile, const JSON_SCHEMA_VALIDATOR& aValidator ) : m_configFile( aConfigFile ), - m_config( std::make_unique<API_PLUGIN_CONFIG>( *this, aConfigFile ) ) + m_config( std::make_unique<API_PLUGIN_CONFIG>( *this, aConfigFile, aValidator ) ) { } diff --git a/common/api/api_plugin_manager.cpp b/common/api/api_plugin_manager.cpp index f4c430f581..1556b6c30a 100644 --- a/common/api/api_plugin_manager.cpp +++ b/common/api/api_plugin_manager.cpp @@ -18,6 +18,8 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ +#include <fstream> + #include <env_vars.h> #include <fmt/format.h> #include <wx/dir.h> @@ -42,6 +44,13 @@ API_PLUGIN_MANAGER::API_PLUGIN_MANAGER( wxEvtHandler* aEvtHandler ) : wxEvtHandler(), m_parent( aEvtHandler ) { + // Read and store pcm schema + wxFileName schemaFile( PATHS::GetStockDataPath( true ), wxS( "api.v1.schema.json" ) ); + schemaFile.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS ); + schemaFile.AppendDir( wxS( "schemas" ) ); + + m_schema_validator = std::make_unique<JSON_SCHEMA_VALIDATOR>( schemaFile ); + Bind( EDA_EVT_PLUGIN_MANAGER_JOB_FINISHED, &API_PLUGIN_MANAGER::processNextJob, this ); } @@ -90,7 +99,7 @@ void API_PLUGIN_MANAGER::ReloadPlugins() wxLogTrace( traceApi, wxString::Format( "Manager: loading plugin from %s", aFile.GetFullPath() ) ); - auto plugin = std::make_unique<API_PLUGIN>( aFile ); + auto plugin = std::make_unique<API_PLUGIN>( aFile, *m_schema_validator ); if( plugin->IsOk() ) { diff --git a/common/json_schema_validator.cpp b/common/json_schema_validator.cpp new file mode 100644 index 0000000000..122f640e0d --- /dev/null +++ b/common/json_schema_validator.cpp @@ -0,0 +1,64 @@ +/* +* This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <fstream> +#include <wx/filename.h> +#include <wx/log.h> + +#include <json_schema_validator.h> +#include <locale_io.h> + + +JSON_SCHEMA_VALIDATOR::JSON_SCHEMA_VALIDATOR( const wxFileName& aSchemaFile ) +{ + std::ifstream schema_stream( aSchemaFile.GetFullPath().fn_str() ); + nlohmann::json schema; + + try + { + // For some obscure reason on MINGW, using UCRT option, + // m_schema_validator.set_root_schema() hangs without switching to locale "C" +#if defined(__MINGW32__) && defined(_UCRT) + LOCALE_IO dummy; +#endif + + schema_stream >> schema; + m_validator.set_root_schema( schema ); + } + catch( std::exception& e ) + { + if( !aSchemaFile.FileExists() ) + { + wxLogError( wxString::Format( _( "schema file '%s' not found" ), + aSchemaFile.GetFullPath() ) ); + } + else + { + wxLogError( wxString::Format( _( "Error loading schema: %s" ), e.what() ) ); + } + } +} + + +nlohmann::json JSON_SCHEMA_VALIDATOR::Validate( const nlohmann::json& aJson, + nlohmann::json_schema::error_handler& aErrorHandler, + const nlohmann::json_uri& aInitialUri ) const +{ + return m_validator.validate( aJson, aErrorHandler, aInitialUri ); +} diff --git a/include/api/api_plugin.h b/include/api/api_plugin.h index 1eab95c921..6f7281e8c1 100644 --- a/include/api/api_plugin.h +++ b/include/api/api_plugin.h @@ -34,6 +34,7 @@ struct API_PLUGIN_CONFIG; class API_PLUGIN; +class JSON_SCHEMA_VALIDATOR; struct PLUGIN_DEPENDENCY @@ -104,7 +105,7 @@ struct PLUGIN_ACTION class KICOMMON_API API_PLUGIN { public: - API_PLUGIN( const wxFileName& aConfigFile ); + API_PLUGIN( const wxFileName& aConfigFile, const JSON_SCHEMA_VALIDATOR& aValidator ); ~API_PLUGIN(); diff --git a/include/api/api_plugin_manager.h b/include/api/api_plugin_manager.h index 6c8db54e07..40c6513156 100644 --- a/include/api/api_plugin_manager.h +++ b/include/api/api_plugin_manager.h @@ -25,6 +25,7 @@ #include <wx/event.h> #include <api/api_plugin.h> +#include <json_schema_validator.h> #include <kicommon.h> /// Internal event used for handling async tasks @@ -98,4 +99,6 @@ private: }; std::deque<JOB> m_jobs; + + std::unique_ptr<JSON_SCHEMA_VALIDATOR> m_schema_validator; }; diff --git a/include/json_schema_validator.h b/include/json_schema_validator.h new file mode 100644 index 0000000000..648fd2c963 --- /dev/null +++ b/include/json_schema_validator.h @@ -0,0 +1,43 @@ +/* +* This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef JSON_SCHEMA_VALIDATOR_H +#define JSON_SCHEMA_VALIDATOR_H + +#include <wx/filename.h> +#include <kicommon.h> +#include <json_common.h> +#include <nlohmann/json-schema.hpp> + +class KICOMMON_API JSON_SCHEMA_VALIDATOR +{ +public: + JSON_SCHEMA_VALIDATOR( const wxFileName& aSchemaFile ); + + ~JSON_SCHEMA_VALIDATOR() = default; + + nlohmann::json Validate( const nlohmann::json& aJson, + nlohmann::json_schema::error_handler& aErrorHandler, + const nlohmann::json_uri& aInitialUri = nlohmann::json_uri("#") ) const; + +private: + nlohmann::json_schema::json_validator m_validator; +}; + +#endif //JSON_SCHEMA_VALIDATOR_H diff --git a/kicad/pcm/CMakeLists.txt b/kicad/pcm/CMakeLists.txt index 4d56751046..fe1d6f5449 100644 --- a/kicad/pcm/CMakeLists.txt +++ b/kicad/pcm/CMakeLists.txt @@ -40,7 +40,6 @@ target_link_libraries( pcm ${wxWidgets_LIBRARIES} common picosha2 - nlohmann_json_schema_validator ) # Copy the schema to the build directory when building outside the source tree diff --git a/kicad/pcm/pcm.cpp b/kicad/pcm/pcm.cpp index b56ac86828..d4ca2cc4df 100644 --- a/kicad/pcm/pcm.cpp +++ b/kicad/pcm/pcm.cpp @@ -26,6 +26,7 @@ #include "core/wx_stl_compat.h" #include <env_vars.h> #include <background_jobs_monitor.h> +#include <json_schema_validator.h> #include "build_version.h" #include "paths.h" #include "pcm.h" @@ -81,32 +82,7 @@ PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( schema_file.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS ); schema_file.AppendDir( wxS( "schemas" ) ); - std::ifstream schema_stream( schema_file.GetFullPath().fn_str() ); - nlohmann::json schema; - - try - { - // For some obscure reason on MINGW, using UCRT option, - // m_schema_validator.set_root_schema() hangs without switching to locale "C" - #if defined(__MINGW32__) && defined(_UCRT) - LOCALE_IO dummy; - #endif - - schema_stream >> schema; - m_schema_validator.set_root_schema( schema ); - } - catch( std::exception& e ) - { - if( !schema_file.FileExists() ) - { - wxLogError( wxString::Format( _( "schema file '%s' not found" ), - schema_file.GetFullPath() ) ); - } - else - { - wxLogError( wxString::Format( _( "Error loading schema: %s" ), e.what() ) ); - } - } + m_schema_validator = std::make_unique<JSON_SCHEMA_VALIDATOR>( schema_file ); // Load currently installed packages wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) ); @@ -314,7 +290,7 @@ void PLUGIN_CONTENT_MANAGER::ValidateJson( const nlohmann::json& aJson, const nlohmann::json_uri& aUri ) const { THROWING_ERROR_HANDLER error_handler; - m_schema_validator.validate( aJson, error_handler, aUri ); + m_schema_validator->Validate( aJson, error_handler, aUri ); } diff --git a/kicad/pcm/pcm.h b/kicad/pcm/pcm.h index cc8a021ddc..50a29cfedd 100644 --- a/kicad/pcm/pcm.h +++ b/kicad/pcm/pcm.h @@ -23,12 +23,12 @@ #include "core/wx_stl_compat.h" #include "pcm_data.h" +#include <json_schema_validator.h> #include "widgets/wx_progress_reporters.h" #include <functional> #include <iostream> #include <map> -#include <json_common.h> -#include <nlohmann/json-schema.hpp> + #include <thread> #include <tuple> #include <unordered_map> @@ -393,7 +393,7 @@ private: time_t getCurrentTimestamp() const; wxWindow* m_dialog; - nlohmann::json_schema::json_validator m_schema_validator; + std::unique_ptr<JSON_SCHEMA_VALIDATOR> m_schema_validator; wxString m_3rdparty_path; wxString m_cache_path; std::unordered_map<wxString, PCM_REPOSITORY> m_repository_cache; diff --git a/thirdparty/json_schema_validator/CMakeLists.txt b/thirdparty/json_schema_validator/CMakeLists.txt index 32e50c83f4..f33c0aeea7 100644 --- a/thirdparty/json_schema_validator/CMakeLists.txt +++ b/thirdparty/json_schema_validator/CMakeLists.txt @@ -6,15 +6,14 @@ add_library( nlohmann_json_schema_validator STATIC string-format-check.cpp ) -add_dependencies( nlohmann_json_schema_validator kicommon ) - target_include_directories( nlohmann_json_schema_validator PUBLIC $<INSTALL_INTERFACE:include> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> ) +target_include_directories( nlohmann_json_schema_validator INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} ) + target_link_libraries( nlohmann_json_schema_validator PUBLIC nlohmann_json - PRIVATE kicommon ) diff --git a/thirdparty/json_schema_validator/json-schema-draft7.json.cpp b/thirdparty/json_schema_validator/json-schema-draft7.json.cpp index bc07d8afca..b680e2c2d2 100644 --- a/thirdparty/json_schema_validator/json-schema-draft7.json.cpp +++ b/thirdparty/json_schema_validator/json-schema-draft7.json.cpp @@ -6,7 +6,6 @@ * SPDX-License-Identifier: MIT * */ -#include <json_common.h> #include <nlohmann/json.hpp> namespace nlohmann diff --git a/thirdparty/json_schema_validator/nlohmann/json-schema.hpp b/thirdparty/json_schema_validator/nlohmann/json-schema.hpp index 582c113c1c..07befd3472 100644 --- a/thirdparty/json_schema_validator/nlohmann/json-schema.hpp +++ b/thirdparty/json_schema_validator/nlohmann/json-schema.hpp @@ -21,7 +21,6 @@ # define JSON_SCHEMA_VALIDATOR_API #endif -#include <json_common.h> #include <nlohmann/json.hpp> #ifdef NLOHMANN_JSON_VERSION_MAJOR