winget-cli

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

commit 993de6b6996023c399071be59fdf35ba60cd1d83
parent f9fc158c2cf69952de1abd738438caa37161c1fa
Author: Akinwale Alagbe <24412729+hackean-msft@users.noreply.github.com>
Date:   Tue, 13 Sep 2022 11:41:12 -0700

Return error codes for dependencies failure (#2410)

* Surface dependencies specific errors

* Updated flags result

* Sort alphabetically

* Return message ID when string mapping not found

* Addressed PR comments

* Renamed field to context

* Proper name change

* Addressed PR comments

* Addressed PR comments

* Change method name to reflect functionality.

* Included excluded files.

* Addressed PR comments

* Spaces and indentation fix.

* Add capability to return multiple error result.

* code refactor

* Addressed pr comment

* Addressed PR comments

* Addressed pr comment

Co-authored-by: Akinwale Alagbe <akalagbe@microsoft.com>
Diffstat:
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj | 1+
Msrc/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters | 3+++
Asrc/AppInstallerCLITests/PackageDependenciesValidationUtil.cpp | 26++++++++++++++++++++++++++
Msrc/AppInstallerCLITests/YamlManifest.cpp | 62+++++++++++++++++++++++++++++++-------------------------------
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj | 2++
Msrc/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters | 10++++++++--
Msrc/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp | 2+-
Msrc/AppInstallerCommonCore/Manifest/ManifestValidation.cpp | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Manifest/YamlParser.cpp | 32++++++++++++++++----------------
Asrc/AppInstallerCommonCore/PackageDependenciesValidationUtil.cpp | 38++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerCommonCore/Public/winget/ManifestValidation.h | 146+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Asrc/AppInstallerCommonCore/Public/winget/PackageDependenciesValidationUtil.h | 24++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/ArpVersionValidation.cpp | 9+++++----
Msrc/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp | 24++++++++++--------------
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp | 2+-
Msrc/WinGetUtil/Exports.cpp | 9++++++++-
Msrc/WinGetUtil/WinGetUtil.h | 7+++++++
17 files changed, 324 insertions(+), 141 deletions(-)

diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -211,6 +211,7 @@ <ClCompile Include="MsixManifest.cpp" /> <ClCompile Include="NameNormalization.cpp" /> <ClCompile Include="PackageCollection.cpp" /> + <ClCompile Include="PackageDependenciesValidationUtil.cpp" /> <ClCompile Include="PackageTrackingCatalog.cpp" /> <ClCompile Include="PortableEntry.cpp" /> <ClCompile Include="PortableIndex.cpp" /> diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -230,6 +230,9 @@ <ClCompile Include="PortableEntry.cpp"> <Filter>Source Files\Common</Filter> </ClCompile> + <ClCompile Include="PackageDependenciesValidationUtil.cpp"> + <Filter>Source Files\Repository</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLITests/PackageDependenciesValidationUtil.cpp b/src/AppInstallerCLITests/PackageDependenciesValidationUtil.cpp @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" +#include <public/winget/PackageDependenciesValidationUtil.h> + +using namespace AppInstaller::Manifest; + +TEST_CASE("GetValidationResultFromException", "[PackageDependenciesValidationUtil][dependencies]") +{ + std::map<WinGetManifestDependenciesErrorResult, const ManifestException> dependenciesErrorMessageMap = + { + { WinGetManifestDependenciesErrorResult::SingleManifestPackageHasDependencies, ManifestException { { ManifestError::SingleManifestPackageHasDependencies }, APPINSTALLER_CLI_ERROR_MANIFEST_FAILED } }, + { WinGetManifestDependenciesErrorResult::MultiManifestPackageHasDependencies, ManifestException { { ManifestError::MultiManifestPackageHasDependencies }, APPINSTALLER_CLI_ERROR_MANIFEST_FAILED } }, + { WinGetManifestDependenciesErrorResult::MissingManifestDependenciesNode, ManifestException { { ManifestError::MissingManifestDependenciesNode }, APPINSTALLER_CLI_ERROR_MANIFEST_FAILED } }, + { WinGetManifestDependenciesErrorResult::NoSuitableMinVersionDependency, ManifestException { { ManifestError::NoSuitableMinVersionDependency }, APPINSTALLER_CLI_ERROR_MANIFEST_FAILED } }, + { WinGetManifestDependenciesErrorResult::FoundDependencyLoop, ManifestException { { ManifestError::FoundDependencyLoop }, APPINSTALLER_CLI_ERROR_MANIFEST_FAILED } }, + }; + + for (auto current : dependenciesErrorMessageMap) + { + REQUIRE(GetDependenciesValidationResultFromException(current.second) == current.first); + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp @@ -34,17 +34,17 @@ bool operator==(const MultiValue& a, const MultiValue& b) void ValidateError( const ValidationError& error, ValidationError::Level level, - std::string message, + AppInstaller::StringResource::StringId message, std::string field, std::string value) { REQUIRE(level == error.ErrorLevel); REQUIRE(message == error.Message); - REQUIRE(field == error.Field); + REQUIRE(field == error.Context); REQUIRE(value == error.Value); } -void ValidateError(const ValidationError& error, ValidationError::Level level, std::string message) +void ValidateError(const ValidationError& error, ValidationError::Level level, AppInstaller::StringResource::StringId message) { ValidateError(error, level, message, std::string(), std::string()); } @@ -252,12 +252,12 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { ManifestTestCase TestCases[] = { - { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. Field: Arch" }, + { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. [Architecture]" }, { "Manifest-Bad-ArchMissing.yaml", "Missing required property 'Arch'" }, - { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. Field: Channel" }, - { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. Field: installerType" }, - { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. Field: installertype" }, - { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. Field: INSTALLERTYPE" }, + { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. [Channel]" }, + { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. [installerType]" }, + { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. [installertype]" }, + { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. [INSTALLERTYPE]" }, { "Manifest-Bad-DuplicateKey.yaml", "Duplicate field found in the manifest." }, { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Duplicate field found in the manifest." }, { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Duplicate field found in the manifest." }, @@ -270,16 +270,16 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, - { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. Field: InstallerType" }, - { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. Field: InstallerType" }, + { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. [InstallerType]" }, + { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. [InstallerType]" }, { "Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml", "Only zero or one entry for Apps and Features may be specified for InstallerType portable." }, { "Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml", "Only zero or one value for Commands may be specified for InstallerType portable." }, { "Manifest-Bad-InstallerTypePortable-InvalidScope.yaml", "Scope is not supported for InstallerType portable." }, { "Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml", "Relative file path must not point to a location outside of archive directory" }, - { "Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml", "Required field missing. Field: RelativeFilePath" }, + { "Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml", "Required field missing. [RelativeFilePath]" }, { "Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml", "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes." }, - { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml", "Required field missing. Field: NestedInstallerFiles" }, - { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml", "Required field missing. Field: NestedInstallerType" }, + { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml", "Required field missing. [NestedInstallerFiles]" }, + { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml", "Required field missing. [NestedInstallerType]" }, { "Manifest-Bad-InstallerUniqueness.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, @@ -288,25 +288,25 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-NameMissing.yaml", "Missing required property 'Name'" }, { "Manifest-Bad-PublisherMissing.yaml", "Missing required property 'Publisher'" }, { "Manifest-Bad-Sha256Invalid.yaml", "Failed to validate against schema associated with property name 'Sha256'" }, - { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. Field: InstallerSha256" }, - { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. Field: NotASwitch", true }, - { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. Field: Fake", true }, + { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. [InstallerSha256]" }, + { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. [NotASwitch]", true }, + { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. [Fake]", true }, { "Manifest-Bad-UnsupportedVersion.yaml", "Unsupported ManifestVersion" }, - { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. Field: InstallerUrl" }, - { "Manifest-Bad-UrlMissing.yaml", "Required field missing. Field: InstallerUrl" }, + { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. [InstallerUrl]" }, + { "Manifest-Bad-UrlMissing.yaml", "Required field missing. [InstallerUrl]" }, { "Manifest-Bad-VersionInvalid.yaml", "Failed to validate against schema associated with property name 'Version'" }, { "Manifest-Bad-VersionMissing.yaml", "Missing required property 'Version'" }, { "Manifest-Bad-InvalidManifestVersionValue.yaml", "Failed to validate against schema associated with property name 'ManifestVersion'" }, - { "InstallFlowTest_MSStore.yaml", "Field value is not supported. Field: InstallerType Value: msstore" }, - { "Manifest-Bad-PackageFamilyNameOnMSI.yaml", "The specified installer type does not support PackageFamilyName. Field: InstallerType Value: msi" }, - { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. Field: InstallerType Value: msix" }, - { "Manifest-Bad-InvalidUpdateBehavior.yaml", "Invalid field value. Field: UpdateBehavior" }, + { "InstallFlowTest_MSStore.yaml", "Field value is not supported. [InstallerType] Value: msstore" }, + { "Manifest-Bad-PackageFamilyNameOnMSI.yaml", "The specified installer type does not support PackageFamilyName. [InstallerType] Value: msi" }, + { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. [InstallerType] Value: msix" }, + { "Manifest-Bad-InvalidUpdateBehavior.yaml", "Invalid field value. [UpdateBehavior]" }, { "Manifest-Bad-InvalidLocale.yaml", "The locale value is not a well formed bcp47 language tag." }, { "Manifest-Bad-AppsAndFeaturesEntriesOnMSIX.yaml", "The specified installer type does not write to Apps and Features entry." }, { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers.", true }, { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers.", false, GetTestManifestValidateOption(false, true) }, - { "Manifest-Bad-ApproximateVersionInPackageVersion.yaml", "Approximate version not allowed. Field: PackageVersion" }, - { "Manifest-Bad-ApproximateVersionInArpVersion.yaml", "Approximate version not allowed. Field: DisplayVersion" }, + { "Manifest-Bad-ApproximateVersionInPackageVersion.yaml", "Approximate version not allowed. [PackageVersion]" }, + { "Manifest-Bad-ApproximateVersionInArpVersion.yaml", "Approximate version not allowed. [DisplayVersion]" }, }; for (auto const& testCase : TestCases) @@ -841,31 +841,31 @@ TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") { // Singleton and multi file manifest together std::vector<YamlManifestInfo> input = { v1SingletonManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should not contain file with the particular ManifestType. Field: ManifestType Value: singleton")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should not contain file with the particular ManifestType. [ManifestType] Value: singleton")); } { // More than 1 version manifest std::vector<YamlManifestInfo> input = { v1VersionManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: version")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: version")); } { // More than 1 installer manifest std::vector<YamlManifestInfo> input = { v1VersionManifest, v1InstallerManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: installer")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: installer")); } { // More than 1 default locale manifest std::vector<YamlManifestInfo> input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. Field: ManifestType Value: defaultLocale")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: defaultLocale")); } { // Duplicate locales std::vector<YamlManifestInfo> input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1LocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest contains duplicate PackageLocale. Field: PackageLocale Value: en-GB")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest contains duplicate PackageLocale. [PackageLocale] Value: en-GB")); } { @@ -881,7 +881,7 @@ TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") auto installerManifestCopy = v1InstallerManifest; installerManifestCopy.Root["PackageIdentifier"].SetScalar("Another.Identifier"); std::vector<YamlManifestInfo> input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. Field: PackageIdentifier Value: Another.Identifier")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageIdentifier] Value: Another.Identifier")); } { @@ -889,7 +889,7 @@ TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") auto installerManifestCopy = v1InstallerManifest; installerManifestCopy.Root["PackageVersion"].SetScalar("Another.Version"); std::vector<YamlManifestInfo> input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. Field: PackageVersion Value: Another.Version")); + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageVersion] Value: Another.Version")); } { diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -325,6 +325,7 @@ <ClInclude Include="Public\winget\MsixManifestValidation.h" /> <ClInclude Include="Public\winget\NameNormalization.h" /> <ClInclude Include="Public\winget\Filesystem.h" /> + <ClInclude Include="Public\winget\PackageDependenciesValidationUtil.h" /> <ClInclude Include="Public\winget\Regex.h" /> <ClInclude Include="Public\winget\Registry.h" /> <ClInclude Include="Public\winget\PathVariable.h" /> @@ -392,6 +393,7 @@ </ClCompile> <ClCompile Include="MsixManifest.cpp" /> <ClCompile Include="NameNormalization.cpp" /> + <ClCompile Include="PackageDependenciesValidationUtil.cpp" /> <ClCompile Include="Regex.cpp" /> <ClCompile Include="Registry.cpp" /> <ClCompile Include="Resources.cpp" /> diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -219,6 +219,9 @@ <ClInclude Include="Public\winget\PathVariable.h"> <Filter>Public\winget</Filter> </ClInclude> + <ClInclude Include="Public\winget\PackageDependenciesValidationUtil.h"> + <Filter>Public\winget</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -386,9 +389,12 @@ <ClCompile Include="Manifest\MsixManifestValidation.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClInclude Include="PathVariable.cpp"> + <ClCompile Include="PathVariable.cpp"> <Filter>Source Files</Filter> - </ClInclude> + </ClCompile> + <ClCompile Include="PackageDependenciesValidationUtil.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -187,7 +187,7 @@ namespace AppInstaller::Manifest::YamlParser if (!JsonSchema::Validate(schema, manifestJson, results)) { - errors.emplace_back(ValidationError::MessageWithFile(JsonSchema::GetErrorStringFromResults(results), entry.FileName)); + errors.emplace_back(ValidationError::MessageContextWithFile(ManifestError::SchemaError, JsonSchema::GetErrorStringFromResults(results), entry.FileName)); } } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -11,6 +11,61 @@ namespace AppInstaller::Manifest { + namespace + { + const auto& GetErrorIdToMessageMap() + { + static std::map<AppInstaller::StringResource::StringId, std::string_view> ErrorIdToMessageMap = { + { AppInstaller::Manifest::ManifestError::InvalidRootNode, "Encountered unexpected root node."sv }, + { AppInstaller::Manifest::ManifestError::FieldUnknown, "Unknown field."sv }, + { AppInstaller::Manifest::ManifestError::FieldIsNotPascalCase, "All field names should be PascalCased."sv }, + { AppInstaller::Manifest::ManifestError::FieldDuplicate, "Duplicate field found in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::RequiredFieldEmpty, "Required field with empty value."sv }, + { AppInstaller::Manifest::ManifestError::RequiredFieldMissing, "Required field missing."sv }, + { AppInstaller::Manifest::ManifestError::InvalidFieldValue, "Invalid field value."sv }, + { AppInstaller::Manifest::ManifestError::ExeInstallerMissingSilentSwitches, "Silent and SilentWithProgress switches are not specified for InstallerType exe.Please make sure the installer can run unattended."sv }, + { AppInstaller::Manifest::ManifestError::FieldNotSupported, "Field is not supported."sv }, + { AppInstaller::Manifest::ManifestError::FieldValueNotSupported, "Field value is not supported."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateInstallerEntry, "Duplicate installer entry found."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "The specified installer type does not support PackageFamilyName."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportProductCode, "The specified installer type does not support ProductCode."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "The specified installer type does not write to Apps and Features entry."sv }, + { AppInstaller::Manifest::ManifestError::IncompleteMultiFileManifest, "The multi file manifest is incomplete.A multi file manifest must contain at least version, installer and defaultLocale manifest."sv }, + { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestFieldValue, "The multi file manifest has inconsistent field values."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestType, "The multi file manifest should contain only one file with the particular ManifestType."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestLocale, "The multi file manifest contains duplicate PackageLocale."sv }, + { AppInstaller::Manifest::ManifestError::UnsupportedMultiFileManifestType, "The multi file manifest should not contain file with the particular ManifestType."sv }, + { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestDefaultLocale, "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."sv }, + { AppInstaller::Manifest::ManifestError::FieldFailedToProcess, "Failed to process field."sv }, + { AppInstaller::Manifest::ManifestError::InvalidBcp47Value, "The locale value is not a well formed bcp47 language tag."sv }, + { AppInstaller::Manifest::ManifestError::BothAllowedAndExcludedMarketsDefined, "Both AllowedMarkets and ExcludedMarkets defined."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateReturnCodeEntry, "Duplicate installer return code found."sv }, + { AppInstaller::Manifest::ManifestError::FieldRequireVerifiedPublisher, "Field usage requires verified publishers."sv }, + { AppInstaller::Manifest::ManifestError::SingleManifestPackageHasDependencies, "Package has a single manifest and is a dependency of other manifests."sv }, + { AppInstaller::Manifest::ManifestError::MultiManifestPackageHasDependencies, "Deleting the manifest will be break the following dependencies."sv }, + { AppInstaller::Manifest::ManifestError::MissingManifestDependenciesNode, "Dependency not found: "sv }, + { AppInstaller::Manifest::ManifestError::NoSuitableMinVersionDependency,"No Suitable Minimum Version : "sv }, + { AppInstaller::Manifest::ManifestError::FoundDependencyLoop, "Loop found."sv }, + { AppInstaller::Manifest::ManifestError::ExceededAppsAndFeaturesEntryLimit, "Only zero or one entry for Apps and Features may be specified for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::ExceededCommandsLimit, "Only zero or one value for Commands may be specified for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::ScopeNotSupported, "Scope is not supported for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::InstallerMsixInconsistencies, "Inconsistent value in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::OptionalFieldMissing, "Optional field missing."sv }, + { AppInstaller::Manifest::ManifestError::InstallerFailedToProcess, "Failed to process installer."sv }, + { AppInstaller::Manifest::ManifestError::NoSupportedPlatforms, "No supported platforms."sv }, + { AppInstaller::Manifest::ManifestError::ApproximateVersionNotAllowed, "Approximate version not allowed."sv }, + { AppInstaller::Manifest::ManifestError::ArpVersionOverlapWithIndex, "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index.Existing DisplayVersion range in index : "sv }, + { AppInstaller::Manifest::ManifestError::ArpVersionValidationInternalError, "Internal error while validating DisplayVersion against index."sv }, + { AppInstaller::Manifest::ManifestError::ExceededNestedInstallerFilesLimit, "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."sv }, + { AppInstaller::Manifest::ManifestError::RelativeFilePathEscapesDirectory, "Relative file path must not point to a location outside of archive directory"sv }, + { AppInstaller::Manifest::ManifestError::ArpValidationError, "Arp Validation Error"sv }, + { AppInstaller::Manifest::ManifestError::SchemaError, "Schema Error"sv }, + { AppInstaller::Manifest::ManifestError::MsixSignatureHashFailed, "Failed to calculate MSIX signature hash.Please verify that the input file is a valid, signed MSIX."sv } + }; + + return ErrorIdToMessageMap; + } + } std::vector<ValidationError> ValidateManifest(const Manifest& manifest, bool fullValidation) { std::vector<ValidationError> resultErrors; @@ -318,4 +373,17 @@ namespace AppInstaller::Manifest return errors; } + + std::string ValidationError::GetErrorMessage() const + { + const auto& ErrorIdToMessageMap = GetErrorIdToMessageMap(); + const auto itr = ErrorIdToMessageMap.find(Message); + + if (itr != ErrorIdToMessageMap.end()) + { + return std::string(itr->second); + } + + return Utility::ConvertToUTF8(Message); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -23,25 +23,25 @@ namespace AppInstaller::Manifest::YamlParser if (!entry.Root["PackageIdentifier"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::RequiredFieldMissing, "PackageIdentifier", entry.FileName)); } if (!entry.Root["PackageVersion"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::RequiredFieldMissing, "PackageVersion", entry.FileName)); } if (!entry.Root["ManifestVersion"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::RequiredFieldMissing, "ManifestVersion", entry.FileName)); } if (!entry.Root["ManifestType"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestType", entry.FileName)); } else @@ -53,7 +53,7 @@ namespace AppInstaller::Manifest::YamlParser case ManifestTypeEnum::Version: if (!entry.Root["DefaultLocale"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::RequiredFieldMissing, "DefaultLocale", entry.FileName)); } break; @@ -62,7 +62,7 @@ namespace AppInstaller::Manifest::YamlParser case ManifestTypeEnum::DefaultLocale: if (!entry.Root["PackageLocale"]) { - errors.emplace_back(ValidationError::MessageFieldWithFile( + errors.emplace_back(ValidationError::MessageContextWithFile( ManifestError::RequiredFieldMissing, "PackageLocale", entry.FileName)); } break; @@ -158,21 +158,21 @@ namespace AppInstaller::Manifest::YamlParser std::string localPackageId = entry.Root["PackageIdentifier"].as<std::string>(); if (localPackageId != packageId) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); } std::string localPackageVersion = entry.Root["PackageVersion"].as<std::string>(); if (localPackageVersion != packageVersion) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); } std::string localManifestVersion = entry.Root["ManifestVersion"].as<std::string>(); if (localManifestVersion != manifestVersionStr) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); } @@ -185,7 +185,7 @@ namespace AppInstaller::Manifest::YamlParser case ManifestTypeEnum::Version: if (isVersionManifestFound) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } else @@ -197,7 +197,7 @@ namespace AppInstaller::Manifest::YamlParser case ManifestTypeEnum::Installer: if (isInstallerManifestFound) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } else @@ -208,7 +208,7 @@ namespace AppInstaller::Manifest::YamlParser case ManifestTypeEnum::DefaultLocale: if (isDefaultLocaleManifestFound) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } else @@ -219,7 +219,7 @@ namespace AppInstaller::Manifest::YamlParser if (localesSet.find(packageLocale) != localesSet.end()) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); } else @@ -233,7 +233,7 @@ namespace AppInstaller::Manifest::YamlParser auto packageLocale = entry.Root["PackageLocale"sv].as<std::string>(); if (localesSet.find(packageLocale) != localesSet.end()) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); } else @@ -243,7 +243,7 @@ namespace AppInstaller::Manifest::YamlParser } break; default: - errors.emplace_back(ValidationError::MessageFieldValueWithFile( + errors.emplace_back(ValidationError::MessageContextValueWithFile( ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); } } @@ -266,7 +266,7 @@ namespace AppInstaller::Manifest::YamlParser if (validateOption.FullValidation && manifestType == ManifestTypeEnum::Merged) { - errors.emplace_back(ValidationError::MessageFieldValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); + errors.emplace_back(ValidationError::MessageContextValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); } if (!validateOption.SchemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) diff --git a/src/AppInstallerCommonCore/PackageDependenciesValidationUtil.cpp b/src/AppInstallerCommonCore/PackageDependenciesValidationUtil.cpp @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include <Public/winget/PackageDependenciesValidationUtil.h> +#include <winget/ManifestValidation.h> + +namespace AppInstaller::Manifest +{ + WinGetManifestDependenciesErrorResult GetDependenciesValidationResultFromException(const AppInstaller::Manifest::ManifestException& manifestException) + { + auto result = WinGetManifestDependenciesErrorResult::None; + auto validationErrors = manifestException.Errors(); + + for (const auto& validationError : validationErrors) + { + auto message = validationError.Message; + + std::map<AppInstaller::StringResource::StringId, WinGetManifestDependenciesErrorResult> dependenciesErrorMessageMap = + { + {ManifestError::SingleManifestPackageHasDependencies, WinGetManifestDependenciesErrorResult::SingleManifestPackageHasDependencies}, + {ManifestError::MultiManifestPackageHasDependencies, WinGetManifestDependenciesErrorResult::MultiManifestPackageHasDependencies }, + {ManifestError::MissingManifestDependenciesNode, WinGetManifestDependenciesErrorResult::MissingManifestDependenciesNode }, + {ManifestError::NoSuitableMinVersionDependency, WinGetManifestDependenciesErrorResult::NoSuitableMinVersionDependency }, + {ManifestError::FoundDependencyLoop, WinGetManifestDependenciesErrorResult::FoundDependencyLoop } + }; + + auto itr = dependenciesErrorMessageMap.find(message); + + if (itr != dependenciesErrorMessageMap.end()) + { + result |= itr->second; + } + } + + return result; + } +}+ \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -3,11 +3,10 @@ #pragma once #include <AppInstallerErrors.h> #include <winget/Manifest.h> +#include <winget/Resources.h> #include <wil/result.h> - #include <functional> -#include <string> namespace YAML { class Node; } @@ -15,52 +14,55 @@ namespace AppInstaller::Manifest { namespace ManifestError { + const char* const ErrorMessagePrefix = "Manifest Error: "; const char* const WarningMessagePrefix = "Manifest Warning: "; - const char* const InvalidRootNode = "Encountered unexpected root node."; - const char* const FieldUnknown = "Unknown field."; - const char* const FieldIsNotPascalCase = "All field names should be PascalCased."; - const char* const FieldDuplicate = "Duplicate field found in the manifest."; - const char* const RequiredFieldEmpty = "Required field with empty value."; - const char* const RequiredFieldMissing = "Required field missing."; - const char* const InvalidFieldValue = "Invalid field value."; - const char* const ExeInstallerMissingSilentSwitches = "Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended."; - const char* const FieldNotSupported = "Field is not supported."; - const char* const FieldValueNotSupported = "Field value is not supported."; - const char* const DuplicateInstallerEntry = "Duplicate installer entry found."; - const char* const InstallerTypeDoesNotSupportPackageFamilyName = "The specified installer type does not support PackageFamilyName."; - const char* const InstallerTypeDoesNotSupportProductCode = "The specified installer type does not support ProductCode."; - const char* const InstallerTypeDoesNotWriteAppsAndFeaturesEntry = "The specified installer type does not write to Apps and Features entry."; - const char* const IncompleteMultiFileManifest = "The multi file manifest is incomplete. A multi file manifest must contain at least version, installer and defaultLocale manifest."; - const char* const InconsistentMultiFileManifestFieldValue = "The multi file manifest has inconsistent field values."; - const char* const DuplicateMultiFileManifestType = "The multi file manifest should contain only one file with the particular ManifestType."; - const char* const DuplicateMultiFileManifestLocale = "The multi file manifest contains duplicate PackageLocale."; - const char* const UnsupportedMultiFileManifestType = "The multi file manifest should not contain file with the particular ManifestType."; - const char* const InconsistentMultiFileManifestDefaultLocale = "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."; - const char* const FieldFailedToProcess = "Failed to process field."; - const char* const InvalidBcp47Value = "The locale value is not a well formed bcp47 language tag."; - const char* const BothAllowedAndExcludedMarketsDefined = "Both AllowedMarkets and ExcludedMarkets defined."; - const char* const DuplicateReturnCodeEntry = "Duplicate installer return code found."; - const char* const FieldRequireVerifiedPublisher = "Field usage requires verified publishers."; - const char* const SingleManifestPackageHasDependencies = "Package has a single manifest and is a dependency of other manifests."; - const char* const MultiManifestPackageHasDependencies = "Deleting the manifest will be break the following dependencies."; - const char* const MissingManifestDependenciesNode = "Dependency not found: "; - const char* const NoSuitableMinVersion = "No Suitable Minimum Version: "; - const char* const FoundLoop = "Loop found."; - const char* const ExceededAppsAndFeaturesEntryLimit = "Only zero or one entry for Apps and Features may be specified for InstallerType portable."; - const char* const ExceededCommandsLimit = "Only zero or one value for Commands may be specified for InstallerType portable."; - const char* const ScopeNotSupported = "Scope is not supported for InstallerType portable."; - const char* const InstallerMsixInconsistencies = "Inconsistent value in the manifest."; - const char* const OptionalFieldMissing = "Optional field missing."; - const char* const InstallerFailedToProcess = "Failed to process installer."; - const char* const NoSupportedPlatforms = "No supported platforms."; - const char* const ApproximateVersionNotAllowed = "Approximate version not allowed."; - const char* const ArpVersionOverlapWithIndex = "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: "; - const char* const ArpVersionValidationInternalError = "Internal error while validating DisplayVersion against index."; - const char* const ExceededNestedInstallerFilesLimit = "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."; - const char* const RelativeFilePathEscapesDirectory = "Relative file path must not point to a location outside of archive directory"; - const char* const MsixSignatureHashFailed = "Failed to calculate MSIX signature hash. Please verify that the input file is a valid, signed MSIX."; + WINGET_DEFINE_RESOURCE_STRINGID(ApproximateVersionNotAllowed); + WINGET_DEFINE_RESOURCE_STRINGID(ArpValidationError); + WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionOverlapWithIndex); + WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionValidationInternalError); + WINGET_DEFINE_RESOURCE_STRINGID(BothAllowedAndExcludedMarketsDefined); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestLocale); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestType); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateInstallerEntry); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateReturnCodeEntry); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededAppsAndFeaturesEntryLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededCommandsLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededNestedInstallerFilesLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExeInstallerMissingSilentSwitches); + WINGET_DEFINE_RESOURCE_STRINGID(FieldDuplicate); + WINGET_DEFINE_RESOURCE_STRINGID(FieldFailedToProcess); + WINGET_DEFINE_RESOURCE_STRINGID(FieldIsNotPascalCase); + WINGET_DEFINE_RESOURCE_STRINGID(FieldNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FieldRequireVerifiedPublisher); + WINGET_DEFINE_RESOURCE_STRINGID(FieldUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(FieldValueNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FoundDependencyLoop); + WINGET_DEFINE_RESOURCE_STRINGID(IncompleteMultiFileManifest); + WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestDefaultLocale); + WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestFieldValue); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedToProcess); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerMsixInconsistencies); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportPackageFamilyName); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportProductCode); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotWriteAppsAndFeaturesEntry); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidBcp47Value); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidFieldValue); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidRootNode); + WINGET_DEFINE_RESOURCE_STRINGID(MissingManifestDependenciesNode); + WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MultiManifestPackageHasDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(NoSuitableMinVersionDependency); + WINGET_DEFINE_RESOURCE_STRINGID(NoSupportedPlatforms); + WINGET_DEFINE_RESOURCE_STRINGID(OptionalFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(RelativeFilePathEscapesDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaError); + WINGET_DEFINE_RESOURCE_STRINGID(ScopeNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(SingleManifestPackageHasDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedMultiFileManifestType); } struct ValidationError @@ -71,8 +73,8 @@ namespace AppInstaller::Manifest Error }; - std::string Message; - std::string Field = {}; + AppInstaller::StringResource::StringId Message; + std::string Context = {}; std::string Value = {}; // line and column are 1 based size_t Line = 0; @@ -80,50 +82,52 @@ namespace AppInstaller::Manifest Level ErrorLevel = Level::Error; std::string FileName; - ValidationError(std::string message) : + ValidationError(AppInstaller::StringResource::StringId message) : Message(std::move(message)) {} - ValidationError(std::string message, Level level) : + ValidationError(AppInstaller::StringResource::StringId message, Level level) : Message(std::move(message)), ErrorLevel(level) {} - ValidationError(std::string message, std::string field) : - Message(std::move(message)), Field(std::move(field)) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context) : + Message(std::move(message)), Context(std::move(context)) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, Level level) : + Message(std::move(message)), Context(std::move(context)), ErrorLevel(level) {} - ValidationError(std::string message, std::string field, Level level) : - Message(std::move(message)), Field(std::move(field)), ErrorLevel(level) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string_view value) : + Message(std::move(message)), Context(std::move(context)), Value(value) {} - ValidationError(std::string message, std::string field, std::string_view value) : - Message(std::move(message)), Field(std::move(field)), Value(value) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)) {} - ValidationError(std::string message, std::string field, std::string value) : - Message(std::move(message)), Field(std::move(field)), Value(std::move(value)) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, Level level) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), ErrorLevel(level) {} - ValidationError(std::string message, std::string field, std::string value, Level level) : - Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), ErrorLevel(level) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column) {} - ValidationError(std::string message, std::string field, std::string value, size_t line, size_t column) : - Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), Line(line), Column(column) {} + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column, Level level) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} - ValidationError(std::string message, std::string field, std::string value, size_t line, size_t column, Level level) : - Message(std::move(message)), Field(std::move(field)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} + std::string GetErrorMessage() const; - static ValidationError MessageWithFile(std::string message, std::string file) + static ValidationError MessageWithFile(AppInstaller::StringResource::StringId message, std::string file) { ValidationError error{ message }; error.FileName = file; return error; } - static ValidationError MessageFieldWithFile(std::string message, std::string field, std::string file) + static ValidationError MessageContextWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string file) { - ValidationError error{ message, field }; + ValidationError error{ message, context }; error.FileName = file; return error; } - static ValidationError MessageFieldValueWithFile(std::string message, std::string field, std::string value, std::string file) + static ValidationError MessageContextValueWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string value, std::string file) { - ValidationError error{ message, field, value }; + ValidationError error{ message, context, value }; error.FileName = file; return error; } @@ -168,11 +172,11 @@ namespace AppInstaller::Manifest { m_manifestErrorMessage += ManifestError::WarningMessagePrefix; } - m_manifestErrorMessage += error.Message; + m_manifestErrorMessage += error.GetErrorMessage(); - if (!error.Field.empty()) + if (!error.Context.empty()) { - m_manifestErrorMessage += " Field: " + error.Field; + m_manifestErrorMessage += " [" + error.Context + "]"; } if (!error.Value.empty()) { diff --git a/src/AppInstallerCommonCore/Public/winget/PackageDependenciesValidationUtil.h b/src/AppInstallerCommonCore/Public/winget/PackageDependenciesValidationUtil.h @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <winget/ManifestValidation.h> + +namespace AppInstaller::Manifest +{ + enum WinGetManifestDependenciesErrorResult : uint32_t + { + None = 0x0, + + // Each validation step should have an enum for corresponding failure. + SingleManifestPackageHasDependencies = 0x10000, + MultiManifestPackageHasDependencies = 0x20000, + MissingManifestDependenciesNode = 0x40000, + NoSuitableMinVersionDependency = 0x80000, + FoundDependencyLoop = 0x100000, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetManifestDependenciesErrorResult); + + WinGetManifestDependenciesErrorResult GetDependenciesValidationResultFromException(const ManifestException& manifestException); +}+ \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp b/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp @@ -67,11 +67,12 @@ namespace AppInstaller::Repository { if (manifestArpVersionRange.Overlaps(arpInIndex)) { - std::string errorMsg = Manifest::ManifestError::ArpVersionOverlapWithIndex; - errorMsg.append("[" + arpInIndex.GetMinVersion().ToString() + ", " + arpInIndex.GetMaxVersion().ToString() + "]"); - AICLI_LOG(Repo, Error, << errorMsg); + std::string context = (" [" + arpInIndex.GetMinVersion().ToString() + ", " + arpInIndex.GetMaxVersion().ToString() + "]"); + auto validationError = Manifest::ValidationError(Manifest::ManifestError::ArpVersionOverlapWithIndex, context); + + AICLI_LOG(Repo, Error, << validationError.GetErrorMessage() << context); THROW_EXCEPTION(Manifest::ManifestException( - { Manifest::ValidationError(errorMsg) }, + { validationError }, APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); } } diff --git a/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp b/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp @@ -82,23 +82,23 @@ namespace AppInstaller::Repository } void ThrowOnManifestValidationFailed( - std::vector<std::pair<DependentManifestInfo, Utility::Version>> failedManifests, std::string error) + std::vector<std::pair<DependentManifestInfo, Utility::Version>> failedManifests, AppInstaller::StringResource::StringId error) { auto itrStart = failedManifests.begin(); - std::string dependentPackages{ itrStart->first.Id + "." + itrStart->first.Version }; + std::vector<Manifest::ValidationError> validationErrors; + validationErrors.emplace_back(error, "PackageIdentifier.PackageVersion", itrStart->first.Id + '.' + itrStart->first.Version ); std::for_each( itrStart + 1, failedManifests.end(), [&](std::pair<DependentManifestInfo, Utility::Version> current) { - dependentPackages.append(", " + current.first.Id + "." + current.first.Version); + validationErrors.emplace_back(error, "PackageIdentifier.PackageVersion", current.first.Id + '.' + current.first.Version); }); - error.append("\n" + dependentPackages); THROW_EXCEPTION( - Manifest::ManifestException({ Manifest::ValidationError(error) }, - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); + Manifest::ManifestException( + std::move(validationErrors), APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); } }; @@ -124,18 +124,15 @@ namespace AppInstaller::Repository auto packageLatest = GetPackageLatestVersion(index, node.Id); if (!packageLatest.has_value()) { - std::string error = ManifestError::MissingManifestDependenciesNode; - error.append(" ").append(node.Id); - dependenciesError.emplace_back(ValidationError(error)); + dependenciesError.emplace_back( + ManifestError::MissingManifestDependenciesNode, "PackageIdentifier", node.Id); foundErrors = true; return depList; } if (node.MinVersion > packageLatest.value().second) { - std::string error = ManifestError::NoSuitableMinVersion; - error.append(" ").append(node.Id); - dependenciesError.emplace_back(ValidationError(error)); + dependenciesError.emplace_back(ManifestError::NoSuitableMinVersionDependency, "PackageIdentifier", node.Id); foundErrors = true; return depList; } @@ -165,8 +162,7 @@ namespace AppInstaller::Repository if (graph.HasLoop()) { - std::string error = ManifestError::FoundLoop; - dependenciesError.emplace_back(error); + dependenciesError.emplace_back(ManifestError::FoundDependencyLoop); THROW_EXCEPTION(ManifestException(std::move(dependenciesError), APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp @@ -241,7 +241,7 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0 { if (error.ErrorLevel == Manifest::ValidationError::Level::Error) { - AICLI_LOG(Repo, Error, << "Received manifest contains validation error: " << error.Message); + AICLI_LOG(Repo, Error, << "Received manifest contains validation error: " << error.GetErrorMessage()); errors++; } } diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp @@ -13,6 +13,7 @@ #include <winget/ThreadGlobals.h> #include <winget/InstallerMetadataCollectionContext.h> #include <PackageDependenciesValidation.h> +#include <public/winget/PackageDependenciesValidationUtil.h> #include <ArpVersionValidation.h> using namespace AppInstaller::Utility; @@ -356,7 +357,13 @@ extern "C" } catch (const ManifestException& e) { - WI_SetFlagIf(validationResult, WinGetValidateManifestResult::DependenciesValidationFailure, !e.IsWarningOnly()); + if (!e.IsWarningOnly()) + { + validationResult |= WinGetValidateManifestResult::DependenciesValidationFailure; + } + + validationResult |= static_cast<WinGetValidateManifestResult>( AppInstaller::Manifest::GetDependenciesValidationResultFromException(e) ); + if (message) { validationMessage += e.GetManifestErrorMessage(); diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h @@ -77,6 +77,13 @@ extern "C" ArpVersionValidationFailure = 0x2, InstallerValidationFailure = 0x4, + // Dependencies validation result. + SingleManifestPackageHasDependencies = 0x10000, + MultiManifestPackageHasDependencies = 0x20000, + MissingManifestDependenciesNode = 0x40000, + NoSuitableMinVersionDependency = 0x80000, + FoundDependencyLoop = 0x100000, + // Internal error meaning validation does not complete as desired. InternalError = 0x1000, };