winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit 9e104ebe699617ef003676eb2ab9837136d799b9
parent 4cd1f5246c47cc676480591f081a86f623821bf0
Author: Luis Chacón <lechacon@users.noreply.github.com>
Date:   Fri,  5 Mar 2021 13:15:05 -0800

Enable JSON schema validation for import (#779)


Diffstat:
M.github/actions/spelling/allow.txt | 1+
Msrc/AppInstallerCLI.sln | 10++++++++--
Msrc/AppInstallerCLI/AppInstallerCLI.vcxproj | 1+
Msrc/AppInstallerCLICore/AppInstallerCLICore.vcxproj | 21++++++++++++---------
Msrc/AppInstallerCLICore/PackageCollection.cpp | 41++++++++++++++++++++++++++++++++++++-----
Msrc/AppInstallerCLICore/PackageCollection.h | 22+++++++++++++++++++++-
Msrc/AppInstallerCLICore/Resources.h | 1+
Msrc/AppInstallerCLICore/Workflows/ImportExportFlow.cpp | 21++++++++++++++++-----
Msrc/AppInstallerCLICore/pch.h | 8++++++++
Msrc/AppInstallerCLIE2ETests/ImportCommand.cs | 2+-
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 3+++
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/PackageCollection.cpp | 114++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/AppInstallerCLITests/WorkFlow.cpp | 3---
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 6++++++
Asrc/AppInstallerCommonCore/JsonSchemaValidation.cpp | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp | 79+++++++++++--------------------------------------------------------------------
Asrc/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h | 27+++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h | 3---
Asrc/WinGetSchemas/PackagesSchema.h | 7+++++++
Asrc/WinGetSchemas/WinGetSchemas.rc | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/WinGetSchemas/WinGetSchemas.vcxitems | 28++++++++++++++++++++++++++++
Asrc/WinGetSchemas/WinGetSchemas.vcxitems.filters | 27+++++++++++++++++++++++++++
Asrc/WinGetSchemas/resource.h | 14++++++++++++++
25 files changed, 492 insertions(+), 106 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -261,6 +261,7 @@ OSVERSIONINFOEXW outfile OUTOFMEMORY OWC +PACKAGESSCHEMA Params parentidx pathpart diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln @@ -17,10 +17,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53 ..\azure-pipelines.loc.yml = ..\azure-pipelines.loc.yml ..\azure-pipelines.yml = ..\azure-pipelines.yml ..\cgmanifest.json = ..\cgmanifest.json - ..\doc\packages.schema.json = ..\doc\packages.schema.json ..\README.md = ..\README.md ..\doc\Settings.md = ..\doc\Settings.md - ..\doc\settings.schema.json = ..\doc\settings.schema.json EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "catch2", "catch2\catch2.vcxitems", "{5295E21E-9868-4DE2-A177-FBB97B36579B}" @@ -68,6 +66,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Valijson", "Valijson\Valijs EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestSchema\ManifestSchema.vcxitems", "{7D05F64D-CE5A-42AA-A2C1-E91458F061CF}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetSchemas", "WinGetSchemas\WinGetSchemas.vcxitems", "{952B513F-8A00-4D74-9271-925AFB3C6252}" +EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spelling", "spelling", "{2ACDE176-F13F-42FA-8159-C34FA3D37837}" ProjectSection(SolutionItems) = preProject ..\.github\actions\spelling\allow.txt = ..\.github\actions\spelling\allow.txt @@ -79,16 +79,21 @@ EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution ManifestSchema\ManifestSchema.vcxitems*{1622da16-914f-4f57-a259-d5169003cc8c}*SharedItemsImports = 4 + Valijson\Valijson.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4 Valijson\Valijson.vcxitems*{358bc478-0624-4ad1-a933-0422b5292af8}*SharedItemsImports = 9 catch2\catch2.vcxitems*{5295e21e-9868-4de2-a177-fbb97b36579b}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 Valijson\Valijson.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 ManifestSchema\ManifestSchema.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 catch2\catch2.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 ManifestSchema\ManifestSchema.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{952b513f-8a00-4d74-9271-925afb3c6252}*SharedItemsImports = 9 binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 ManifestSchema\ManifestSchema.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 EndGlobalSection @@ -437,6 +442,7 @@ Global {3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE} {358BC478-0624-4AD1-A933-0422B5292AF8} = {60618CAC-2995-4DF9-9914-45C6FC02C995} {7D05F64D-CE5A-42AA-A2C1-E91458F061CF} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {952B513F-8A00-4D74-9271-925AFB3C6252} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} {2ACDE176-F13F-42FA-8159-C34FA3D37837} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -71,6 +71,7 @@ <ImportGroup Label="Shared"> <Import Project="..\binver\binver.vcxitems" Label="Shared" /> <Import Project="..\ManifestSchema\ManifestSchema.vcxitems" Label="Shared" /> + <Import Project="..\WinGetSchemas\WinGetSchemas.vcxitems" Label="Shared" /> </ImportGroup> <ImportGroup Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -68,7 +68,10 @@ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> <ImportGroup Label="ExtensionSettings"> </ImportGroup> - <ImportGroup Label="Shared" /> + <ImportGroup Label="Shared"> + <Import Project="..\Valijson\Valijson.vcxitems" Label="Shared" /> + <Import Project="..\WinGetSchemas\WinGetSchemas.vcxitems" Label="Shared" /> + </ImportGroup> <ImportGroup Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> </ImportGroup> @@ -122,9 +125,9 @@ <ClCompile> <Optimization>Disabled</Optimization> <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</TreatWarningAsError> @@ -139,7 +142,7 @@ <ItemDefinitionGroup Condition="'$(Platform)'=='Win32'"> <ClCompile> <PreprocessorDefinitions>WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</TreatWarningAsError> </ClCompile> <Link> @@ -152,10 +155,10 @@ <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD</PreprocessorDefinitions> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\JsonCppLib\json;$(ProjectDir)..\JsonCppLib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">true</TreatWarningAsError> <TreatWarningAsError Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</TreatWarningAsError> diff --git a/src/AppInstallerCLICore/PackageCollection.cpp b/src/AppInstallerCLICore/PackageCollection.cpp @@ -3,7 +3,11 @@ #include "pch.h" #include "PackageCollection.h" + #include "AppInstallerRuntime.h" +#include "winget/JsonSchemaValidation.h" + +#include "PackagesSchema.h" #include <algorithm> #include <ostream> @@ -106,7 +110,7 @@ namespace AppInstaller::CLI if (!version.empty()) { packageNode[s_PackagesJson_Package_Version] = version; - } + } const std::string& channel = package.VersionAndChannel.GetChannel().ToString(); if (!channel.empty()) @@ -122,7 +126,6 @@ namespace AppInstaller::CLI { Json::Value sourceNode{ Json::ValueType::objectValue }; - Json::Value sourceDetailsNode{ Json::ValueType::objectValue }; sourceDetailsNode[s_PackagesJson_Source_Name] = source.Details.Name; sourceDetailsNode[s_PackagesJson_Source_Argument] = source.Details.Arg; @@ -155,10 +158,38 @@ namespace AppInstaller::CLI return root; } - std::optional<PackageCollection> TryParseJson(const Json::Value& root) + ParseResult TryParseJson(const Json::Value& root) { - // TODO: Embed schema in binaries & validate file. This will return nullopt on failure. + // Find the schema used for the JSON + if (!(root.isObject() && root.isMember(s_PackagesJson_Schema) && root[s_PackagesJson_Schema].isString())) + { + AICLI_LOG(CLI, Error, << "Import file is missing \"" << s_PackagesJson_Schema << "\" property"); + return ParseResult{ ParseResult::Type::MissingSchema }; + } + + const auto& schemaUri = root[s_PackagesJson_Schema].asString(); + Json::Value schemaJson; + if (schemaUri == s_PackagesJson_SchemaUri_v1_0) + { + schemaJson = JsonSchema::LoadResourceAsSchemaDoc(MAKEINTRESOURCE(IDX_PACKAGES_SCHEMA_V1), MAKEINTRESOURCE(PACKAGESSCHEMA_RESOURCE_TYPE)); + } + else + { + AICLI_LOG(CLI, Error, << "Unrecognized schema for import file: " << schemaUri); + return ParseResult{ ParseResult::Type::UnrecognizedSchema }; + } + + // Validate the JSON against the schema. + valijson::Schema schema; + JsonSchema::PopulateSchema(schemaJson, schema); + + valijson::ValidationResults results; + if (!JsonSchema::Validate(schema, root, results)) + { + return ParseResult{ ParseResult::Type::SchemaValidationFailed, JsonSchema::GetErrorStringFromResults(results) }; + } + // Extract the data from the JSON. PackageCollection packages; packages.ClientVersion = root[s_PackagesJson_WinGetVersion].asString(); for (const auto& sourceNode : root[s_PackagesJson_Sources]) @@ -175,7 +206,7 @@ namespace AppInstaller::CLI } } - return packages; + return ParseResult{ std::move(packages) }; } } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/PackageCollection.h b/src/AppInstallerCLICore/PackageCollection.h @@ -53,9 +53,29 @@ namespace AppInstaller::CLI namespace PackagesJson { + struct ParseResult + { + enum class Type + { + MissingSchema, + UnrecognizedSchema, + SchemaValidationFailed, + Success, + }; + + ParseResult(Type result) : Result(result) {} + ParseResult(Type result, std::string_view errors) : Result(result), Errors(errors) {} + ParseResult(PackageCollection&& packages) : Result(Type::Success), Packages(std::move(packages)) {} + + Type Result; + PackageCollection Packages; + std::string Errors; + }; + // Converts a collection of packages to its JSON representation for exporting. Json::Value CreateJson(const PackageCollection& packages); - std::optional<PackageCollection> TryParseJson(const Json::Value& root); + // Tries to parse a JSON into a collection of packages. + ParseResult TryParseJson(const Json::Value& root); } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -75,6 +75,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); diff --git a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -169,14 +169,25 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); } - auto packages = PackagesJson::TryParseJson(jsonRoot); - if (!packages.has_value()) + PackagesJson::ParseResult parseResult = PackagesJson::TryParseJson(jsonRoot); + if (parseResult.Result != PackagesJson::ParseResult::Type::Success) { context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl; + if (parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema || + parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema) + { + context.Reporter.Error() << Resource::String::ImportFileHasInvalidSchema << std::endl; + } + else if (parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed) + { + context.Reporter.Error() << parseResult.Errors << std::endl; + } + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); } - if (packages->Sources.empty()) + PackageCollection& packages = parseResult.Packages; + if (packages.Sources.empty()) { AICLI_LOG(CLI, Warning, << "No packages to install"); context.Reporter.Info() << Resource::String::NoPackagesFoundInImportFile << std::endl; @@ -186,7 +197,7 @@ namespace AppInstaller::CLI::Workflow if (context.Args.Contains(Execution::Args::Type::IgnoreVersions)) { // Strip out all the version information as we don't need it. - for (auto& source : packages->Sources) + for (auto& source : packages.Sources) { for (auto& package : source.Packages) { @@ -195,7 +206,7 @@ namespace AppInstaller::CLI::Workflow } } - context.Add<Execution::Data::PackageCollection>(packages.value()); + context.Add<Execution::Data::PackageCollection>(std::move(packages)); } void OpenSourcesForImport(Execution::Context& context) diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h @@ -7,6 +7,14 @@ #include <shellapi.h> #include <WinInet.h> +#pragma warning( push ) +#pragma warning ( disable : 4458 4100 4702 ) +#include <valijson/schema.hpp> +#include <valijson/schema_parser.hpp> +#include <valijson/validator.hpp> +#include <valijson/adapters/jsoncpp_adapter.hpp> +#pragma warning( pop ) + #include <array> #include <iostream> #include <fstream> diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs @@ -33,7 +33,7 @@ namespace AppInstallerCLIE2ETests } // Ignore while we don't have schema validation - // [Test] + [Test] public void ImportInvalidFile() { // Verify failure when trying to import with an invalid file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -814,4 +814,7 @@ They can be configured through the settings file 'winget settings'.</value> <data name="VerifyPathFailedNotExist" xml:space="preserve"> <value>Path does not exist:</value> </data> + <data name="ImportFileHasInvalidSchema" xml:space="preserve"> + <value>The JSON file does not specify a recognized schema.</value> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -55,6 +55,7 @@ <ImportGroup Label="Shared"> <Import Project="..\catch2\catch2.vcxitems" Label="Shared" /> <Import Project="..\ManifestSchema\ManifestSchema.vcxitems" Label="Shared" /> + <Import Project="..\WinGetSchemas\WinGetSchemas.vcxitems" Label="Shared" /> </ImportGroup> <ImportGroup Label="PropertySheets"> <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> diff --git a/src/AppInstallerCLITests/PackageCollection.cpp b/src/AppInstallerCLITests/PackageCollection.cpp @@ -195,8 +195,9 @@ TEST_CASE("PackageCollection_Read_SingleSource", "[PackageCollection]") "WinGetVersion": "1.0.0" })"); - auto parsed = PackagesJson::TryParseJson(json); - REQUIRE(parsed.has_value()); + auto parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success); + REQUIRE(parseResult.Errors.empty()); PackageCollection::Source source; source.Details.Name = "TestSource"; @@ -213,7 +214,7 @@ TEST_CASE("PackageCollection_Read_SingleSource", "[PackageCollection]") std::vector<PackageCollection::Source>{ source } }; - ValidateEqualCollections(parsed.value(), expected); + ValidateEqualCollections(parseResult.Packages, expected); } TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]") @@ -254,8 +255,10 @@ TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]") ] })"); - auto parsed = PackagesJson::TryParseJson(json); - REQUIRE(parsed.has_value()); + + auto parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success); + REQUIRE(parseResult.Errors.empty()); PackageCollection::Source source1; source1.Details.Name = "First"; @@ -277,7 +280,7 @@ TEST_CASE("PackageCollection_Read_MultipleSources", "[PackageCollection]") std::vector<PackageCollection::Source>{ source1, source2 } }; - ValidateEqualCollections(parsed.value(), expected); + ValidateEqualCollections(parseResult.Packages, expected); } TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]") @@ -332,8 +335,10 @@ TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]") ] })"); - auto parsed = PackagesJson::TryParseJson(json); - REQUIRE(parsed.has_value()); + + auto parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::Success); + REQUIRE(parseResult.Errors.empty()); PackageCollection::Source source1; source1.Details.Name = "First"; @@ -356,5 +361,96 @@ TEST_CASE("PackageCollection_Read_RepeatedSource", "[PackageCollection]") std::vector<PackageCollection::Source>{ source1, source2 } }; - ValidateEqualCollections(parsed.value(), expected); + ValidateEqualCollections(parseResult.Packages, expected); +} + +TEST_CASE("PackageCollection_Read_MissingSchema", "[PackageCollection]") +{ + auto json = ParseJsonString(R"( + { + "CreationDate": "2021-01-01T12:00:00.000", + "Sources": [ + { + "Packages": [ + { + "Id": "test.test" + } + ], + "SourceDetails": { + "Argument": "https://aka.ms/winget", + "Identifier": "TestSourceId", + "Name": "TestSource", + "Type": "Microsoft.PreIndexed.Package" + } + } + ], + "WinGetVersion": "1.0.0" + })"); + + auto parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema); + + json = ParseJsonString("\"Not even a JSON object\""); + + parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema); +} + +TEST_CASE("PackageCollection_Read_WrongSchema", "[PackageCollection]") +{ + auto json = ParseJsonString(R"( + { + "$schema": "https://aka.ms/winget-settings.schema.json", + "CreationDate": "2021-01-01T12:00:00.000", + "Sources": [ + { + "Packages": [ + { + "Id": "test.test" + } + ], + "SourceDetails": { + "Argument": "https://aka.ms/winget", + "Identifier": "TestSourceId", + "Name": "TestSource", + "Type": "Microsoft.PreIndexed.Package" + } + } + ], + "WinGetVersion": "1.0.0" + })"); + + auto parseResult = PackagesJson::TryParseJson(json); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema); +} + +TEST_CASE("PackageCollection_Read_SchemaValidationFail", "[PackageCollection]") +{ + auto json = ParseJsonString(R"( + { + "$schema": "https://aka.ms/winget-packages.schema.1.0.json", + "CreationDate": "2021-01-01T12:00:00.000", + "NotSources": [ + { + "Packages": [ + { + "Id": "test.test" + } + ], + "SourceDetails": { + "Argument": "https://aka.ms/winget", + "Identifier": "TestSourceId", + "Name": "TestSource", + "Type": "Microsoft.PreIndexed.Package" + } + } + ], + "WinGetVersion": "1.0.0" + })"); + + auto parseResult = PackagesJson::TryParseJson(json); + INFO(parseResult.Errors); + + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed); + REQUIRE(parseResult.Errors.find("Missing required property 'Sources'.") != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1313,14 +1313,11 @@ TEST_CASE("ImportFlow_InvalidJsonFile", "[ImportFlow][workflow]") context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Invalid.json").GetPath().string()); ImportCommand importCommand({}); - // TODO: Enable when we have schema validation - /* importCommand.Execute(context); INFO(importOutput.str()); // Command should have failed REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - */ } void VerifyMotw(const std::filesystem::path& testFile, DWORD zone) diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -267,6 +267,7 @@ <ClInclude Include="Public\AppInstallerVersions.h" /> <ClInclude Include="Public\winget\ExperimentalFeature.h" /> <ClInclude Include="Public\winget\ExtensionCatalog.h" /> + <ClInclude Include="Public\winget\JsonSchemaValidation.h" /> <ClInclude Include="Public\winget\LocIndependent.h" /> <ClInclude Include="Public\winget\Manifest.h" /> <ClInclude Include="Public\winget\ManifestInstaller.h" /> @@ -310,6 +311,7 @@ <ClCompile Include="HttpStream\HttpRandomAccessStream.cpp"> <ExcludedFromBuild Condition="'$(Configuration)'=='Fuzzing'">true</ExcludedFromBuild> </ClCompile> + <ClCompile Include="JsonSchemaValidation.cpp" /> <ClCompile Include="JsonUtil.cpp" /> <ClCompile Include="Manifest\Manifest.cpp" /> <ClCompile Include="Manifest\ManifestCommon.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -156,6 +156,9 @@ <ClInclude Include="Public\winget\ManifestCommon.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\JsonSchemaValidation.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -263,6 +266,9 @@ <ClCompile Include="Manifest\Manifest.cpp"> <Filter>Manifest</Filter> </ClCompile> + <ClCompile Include="JsonSchemaValidation.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/JsonSchemaValidation.cpp b/src/AppInstallerCommonCore/JsonSchemaValidation.cpp @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/JsonSchemaValidation.h" + +namespace AppInstaller::JsonSchema +{ + std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType) + { + HMODULE resourceModule = NULL; + GetModuleHandleEx( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, + (PCWSTR)LoadResourceAsString, + &resourceModule); + THROW_LAST_ERROR_IF_NULL(resourceModule); + + HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType); + THROW_LAST_ERROR_IF_NULL(resourceInfoHandle); + + HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle); + + ULONG resourceSize = 0; + char* resourceContent = NULL; + resourceSize = SizeofResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF(resourceSize == 0); + + resourceContent = reinterpret_cast<char*>(LockResource(resourceMemoryHandle)); + THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent); + + std::string resourceStr; + resourceStr.assign(resourceContent, resourceSize); + + return resourceStr; + } + + Json::Value LoadSchemaDoc(const std::string& schemaStr) + { + Json::Value schemaJson; + int schemaLength = static_cast<int>(schemaStr.length()); + Json::CharReaderBuilder charReaderBuilder; + const std::unique_ptr<Json::CharReader> jsonReader(charReaderBuilder.newCharReader()); + std::string errorMsg; + if (!jsonReader->parse(schemaStr.c_str(), schemaStr.c_str() + schemaLength, &schemaJson, &errorMsg)) { + THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %s", errorMsg.c_str()); + } + + return schemaJson; + } + + Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType) + { + return LoadSchemaDoc(LoadResourceAsString(resourceName, resourceType)); + } + + void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema) + { + valijson::SchemaParser schemaParser; + valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); + schemaParser.populateSchema(jsonSchemaAdapter, schema); + } + + bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results) + { + valijson::Validator schemaValidator; + valijson::adapters::JsonCppAdapter jsonAdapter(json); + return schemaValidator.validate(schema, jsonAdapter, &results); + } + + std::string GetErrorStringFromResults(valijson::ValidationResults& results) + { + valijson::ValidationResults::Error error; + std::stringstream ss; + + ss << "Schema validation failed." << std::endl; + while (results.popError(error)) + { + std::string context; + for (auto itr = error.context.begin(); itr != error.context.end(); itr++) + { + context += *itr; + } + + ss << "Error context: " << context << " Description: " << error.description << std::endl; + } + + return ss.str(); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -2,6 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "winget/Yaml.h" +#include "winget/JsonSchemaValidation.h" #include "winget/ManifestCommon.h" #include "winget/ManifestSchemaValidation.h" #include "winget/ManifestYamlParser.h" @@ -86,35 +87,6 @@ namespace AppInstaller::Manifest::YamlParser } } - std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType) - { - HMODULE resourceModule = NULL; - GetModuleHandleEx( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, - (PCWSTR)LoadResourceAsString, - &resourceModule); - THROW_LAST_ERROR_IF_NULL(resourceModule); - - HRSRC resourceInfoHandle = FindResource(resourceModule, resourceName, resourceType); - THROW_LAST_ERROR_IF_NULL(resourceInfoHandle); - - HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle); - THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle); - - ULONG resourceSize = 0; - char* resourceContent = NULL; - resourceSize = SizeofResource(resourceModule, resourceInfoHandle); - THROW_LAST_ERROR_IF(resourceSize == 0); - - resourceContent = reinterpret_cast<char*>(LockResource(resourceMemoryHandle)); - THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent); - - std::string resourceStr; - resourceStr.assign(resourceContent, resourceSize); - - return resourceStr; - } - Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType) { std::string schemaStr; @@ -124,19 +96,19 @@ namespace AppInstaller::Manifest::YamlParser switch (manifestType) { case AppInstaller::Manifest::ManifestTypeEnum::Singleton: - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_SINGLETON), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Version: - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_VERSION), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Installer: - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_INSTALLER), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::DefaultLocale: - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; case AppInstaller::Manifest::ManifestTypeEnum::Locale: - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_V1_LOCALE), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); break; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); @@ -144,19 +116,10 @@ namespace AppInstaller::Manifest::YamlParser } else { - schemaStr = LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); + schemaStr = JsonSchema::LoadResourceAsString(MAKEINTRESOURCE(IDX_MANIFEST_SCHEMA_PREVIEW), MAKEINTRESOURCE(MANIFESTSCHEMA_RESOURCE_TYPE)); } - Json::Value schemaJson; - int schemaLength = static_cast<int>(schemaStr.length()); - Json::CharReaderBuilder charReaderBuilder; - const std::unique_ptr<Json::CharReader> jsonReader(charReaderBuilder.newCharReader()); - std::string errorMsg; - if (!jsonReader->parse(schemaStr.c_str(), schemaStr.c_str() + schemaLength, &schemaJson, &errorMsg)) { - THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %s", errorMsg.c_str()); - } - - return schemaJson; + return JsonSchema::LoadSchemaDoc(schemaStr); } std::vector<ValidationError> ValidateAgainstSchema(const std::vector<YamlManifestInfo>& manifestList, const ManifestVer& manifestVersion) @@ -164,7 +127,6 @@ namespace AppInstaller::Manifest::YamlParser std::vector<ValidationError> errors; // A list of schema validator to avoid multiple loadings of same schema std::map<ManifestTypeEnum, valijson::Schema> schemaList; - valijson::Validator schemaValidator; for (const auto& entry : manifestList) { @@ -173,36 +135,17 @@ namespace AppInstaller::Manifest::YamlParser // Copy constructor of valijson::Schema was private valijson::Schema& newSchema = schemaList.emplace( std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second; - valijson::SchemaParser schemaParser; Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType); - valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); - schemaParser.populateSchema(jsonSchemaAdapter, newSchema); + JsonSchema::PopulateSchema(schemaJson, newSchema); } const auto& schema = schemaList.find(entry.ManifestType)->second; - Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root); - valijson::adapters::JsonCppAdapter manifestJsonAdapter(manifestJson); valijson::ValidationResults results; - if (!schemaValidator.validate(schema, manifestJsonAdapter, &results)) + if (!JsonSchema::Validate(schema, manifestJson, results)) { - valijson::ValidationResults::Error error; - std::stringstream ss; - - ss << "Schema validation failed." << std::endl; - while (results.popError(error)) - { - std::string context; - for (auto itr = error.context.begin(); itr != error.context.end(); itr++) - { - context += *itr; - } - - ss << "Error context: " << context << " Description: " << error.description << std::endl; - } - - errors.emplace_back(ValidationError::MessageWithFile(ss.str(), entry.FileName)); + errors.emplace_back(ValidationError::MessageWithFile(JsonSchema::GetErrorStringFromResults(results), entry.FileName)); } } diff --git a/src/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/JsonSchemaValidation.h @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <json.h> + +namespace AppInstaller::JsonSchema +{ + // Load an embedded resource from binary and return as std::string + std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType); + + // Load schema as parsed json doc + Json::Value LoadSchemaDoc(const std::string& schemaStr); + + // Load an embedded resource from binary and return as Json::Value + Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType); + + // Populate a valijson Schema object from a json value + void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema); + + // Validate a json doc with a schema + // Returns whether it was successful and fills the results object + bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results); + + // Extracts the error messages from a result into a single non-localized string + std::string GetErrorStringFromResults(valijson::ValidationResults& results); +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -11,9 +11,6 @@ namespace AppInstaller::Manifest::YamlParser // Forward declarations struct YamlManifestInfo; - // Load an embedded resource from binary and return as std::string - std::string LoadResourceAsString(PCWSTR resourceName, PCWSTR resourceType); - // Load manifest schema as parsed json doc Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType); diff --git a/src/WinGetSchemas/PackagesSchema.h b/src/WinGetSchemas/PackagesSchema.h @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define PACKAGESSCHEMA_RESOURCE_TYPE 300 + +#define IDX_PACKAGES_SCHEMA_V1 301 diff --git a/src/WinGetSchemas/WinGetSchemas.rc b/src/WinGetSchemas/WinGetSchemas.rc @@ -0,0 +1,66 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "PackagesSchema.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Packages schema +// +IDX_PACKAGES_SCHEMA_V1 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.1.0.json" diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems b/src/WinGetSchemas/WinGetSchemas.vcxitems @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Label="Globals"> + <MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects> + <HasSharedItems>true</HasSharedItems> + <ItemsProjectGuid>{952b513f-8a00-4d74-9271-925afb3c6252}</ItemsProjectGuid> + </PropertyGroup> + <ItemDefinitionGroup> + <ClCompile> + <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)</AdditionalIncludeDirectories> + </ClCompile> + </ItemDefinitionGroup> + <ItemGroup> + <ProjectCapability Include="SourceItemsFromImports" /> + </ItemGroup> + <ItemGroup> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\packages\packages.schema.1.0.json" /> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\settings\settings.schema.0.2.json" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="$(MSBuildThisFileDirectory)PackagesSchema.h" /> + <ClInclude Include="$(MSBuildThisFileDirectory)resource.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="$(MSBuildThisFileDirectory)WinGetSchemas.rc" /> + </ItemGroup> +</Project>+ \ No newline at end of file diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems.filters b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <Filter Include="packages"> + <UniqueIdentifier>{9b8a4c46-6227-45fe-840b-8f50fb10ddb1}</UniqueIdentifier> + </Filter> + <Filter Include="settings"> + <UniqueIdentifier>{931a4cce-b01f-4d2f-b39a-8600f2010a97}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\settings\settings.schema.0.2.json"> + <Filter>settings</Filter> + </None> + <None Include="$(MSBuildThisFileDirectory)..\..\schemas\JSON\packages\packages.schema.1.0.json"> + <Filter>packages</Filter> + </None> + </ItemGroup> + <ItemGroup> + <ClInclude Include="$(MSBuildThisFileDirectory)resource.h" /> + <ClInclude Include="$(MSBuildThisFileDirectory)PackagesSchema.h" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="$(MSBuildThisFileDirectory)WinGetSchemas.rc" /> + </ItemGroup> +</Project>+ \ No newline at end of file diff --git a/src/WinGetSchemas/resource.h b/src/WinGetSchemas/resource.h @@ -0,0 +1,14 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by WinGetSchemas.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif