winget-cli

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

commit 4d51b5fb9b1ea5fbc8da1a053423457535e5fec4
parent 30e54e4c3b47d6b9e3de3afaddb2bc0e27365782
Author: yao-msft <50888816+yao-msft@users.noreply.github.com>
Date:   Fri,  9 Feb 2024 12:24:52 -0800

Winget client rest source parsing for 1.7 manifest (#4155)


Diffstat:
Msrc/AppInstallerCLITests/RestInterface_1_7.cpp | 375+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj | 2++
Msrc/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters | 9+++++++++
Msrc/AppInstallerRepositoryCore/ManifestJSONParser.cpp | 9+++++++--
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h | 2++
Msrc/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp | 35++++++++++++++++++++++++++---------
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h | 19+++++++++++++++++++
Asrc/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp | 50++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 490 insertions(+), 11 deletions(-)

diff --git a/src/AppInstallerCLITests/RestInterface_1_7.cpp b/src/AppInstallerCLITests/RestInterface_1_7.cpp @@ -82,6 +82,361 @@ namespace ] } })delimiter"); + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom", + "Repair": "/repair" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true, + "RepairBehavior": "uninstaller" + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Publisher>() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherUrl>() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Author>() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageName>() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PackageUrl>() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::License>() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Description>() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Tags>().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotes>() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::ReleaseNotesUrl>() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::PurchaseUrl>() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::InstallationNotes>() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get<Localization::Publisher>() == "Foo French"); + REQUIRE(frenchLocalization.Get<Localization::PublisherUrl>() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::PublisherSupportUrl>() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::PrivacyUrl>() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::Author>() == "FooBar French"); + REQUIRE(frenchLocalization.Get<Localization::PackageName>() == "Bar"); + REQUIRE(frenchLocalization.Get<Localization::PackageUrl>() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::License>() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get<Localization::LicenseUrl>() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::Copyright>() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get<Localization::CopyrightUrl>() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get<Localization::ShortDescription>() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get<Localization::Description>() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get<Localization::Tags>().size() == 3); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get<Localization::Tags>().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get<Localization::ReleaseNotes>() == "Release notes"); + REQUIRE(frenchLocalization.Get<Localization::ReleaseNotesUrl>() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get<Localization::Agreements>().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::PurchaseUrl>() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::InstallationNotes>() == "Installation Notes"); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get<Localization::Documentations>().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get<Localization::Icons>().size() == 1); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get<Localization::Icons>().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 8); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); + } + }; } TEST_CASE("GetManifests_GoodRequest_Authentication", "[RestSource][Interface_1_7]") @@ -228,4 +583,24 @@ TEST_CASE("Search_BadRequest_InvalidAuthenticationToken", "[RestSource][Interfac PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; request.Filters.emplace_back(std::move(filter)); REQUIRE_THROWS_HR(v1_7.Search(request), HTTP_E_STATUS_DENIED); +} + +TEST_CASE("GetManifests_GoodResponse_V1_7", "[RestSource][Interface_1_7]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_7{ TestRestUriString, {}, {}, {}, std::move(helper) }; + std::vector<AppInstaller::Manifest::Manifest> manifests = v1_7.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + AppInstaller::Manifest::Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.7.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -432,6 +432,7 @@ <ClInclude Include="Rest\Schema\1_6\Interface.h" /> <ClInclude Include="Rest\Schema\1_6\Json\ManifestDeserializer.h" /> <ClInclude Include="Rest\Schema\1_7\Interface.h" /> + <ClInclude Include="Rest\Schema\1_7\Json\ManifestDeserializer.h" /> <ClInclude Include="Rest\Schema\AuthenticationInfoParser.h" /> <ClInclude Include="Rest\Schema\CommonRestConstants.h" /> <ClInclude Include="Rest\Schema\HttpClientHelper.h" /> @@ -515,6 +516,7 @@ <ClCompile Include="Rest\Schema\1_5\RestInterface_1_5.cpp" /> <ClCompile Include="Rest\Schema\1_6\Json\ManifestDeserializer_1_6.cpp" /> <ClCompile Include="Rest\Schema\1_6\RestInterface_1_6.cpp" /> + <ClCompile Include="Rest\Schema\1_7\Json\ManifestDeserializer_1_7.cpp" /> <ClCompile Include="Rest\Schema\1_7\RestInterface_1_7.cpp" /> <ClCompile Include="Rest\Schema\AuthenticationInfoParser.cpp" /> <ClCompile Include="Rest\Schema\HttpClientHelper.cpp" /> diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -97,6 +97,9 @@ <Filter Include="Rest\Schema\1_7"> <UniqueIdentifier>{f42acfce-4fc0-435b-a9a2-bf5528aecbcc}</UniqueIdentifier> </Filter> + <Filter Include="Rest\Schema\1_7\Json"> + <UniqueIdentifier>{7fd6c265-81c0-4c6e-87b2-24bef117e21d}</UniqueIdentifier> + </Filter> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h"> @@ -402,6 +405,9 @@ <ClInclude Include="Rest\Schema\AuthenticationInfoParser.h"> <Filter>Rest\Schema</Filter> </ClInclude> + <ClInclude Include="Rest\Schema\1_7\Json\ManifestDeserializer.h"> + <Filter>Rest\Schema\1_7\Json</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -632,6 +638,9 @@ <ClCompile Include="Rest\Schema\AuthenticationInfoParser.cpp"> <Filter>Rest\Schema</Filter> </ClCompile> + <ClCompile Include="Rest\Schema\1_7\Json\ManifestDeserializer_1_7.cpp"> + <Filter>Rest\Schema\1_7\Json</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -7,6 +7,7 @@ #include "Rest/Schema/1_4/Json/ManifestDeserializer.h" #include "Rest/Schema/1_5/Json/ManifestDeserializer.h" #include "Rest/Schema/1_6/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_7/Json/ManifestDeserializer.h" namespace AppInstaller::Repository::JSON { @@ -37,14 +38,18 @@ namespace AppInstaller::Repository::JSON { m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_4::Json::ManifestDeserializer>(); } - else if (parts.size() > 1 && parts[1].Integer < 6) + else if (parts.size() > 1 && parts[1].Integer < 6) { m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_5::Json::ManifestDeserializer>(); } - else + else if (parts.size() > 1 && parts[1].Integer < 7) { m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_6::Json::ManifestDeserializer>(); } + else + { + m_pImpl->m_deserializer = std::make_unique<Rest::Schema::V1_7::Json::ManifestDeserializer>(); + } } else { diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h @@ -40,6 +40,8 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json virtual std::optional<Manifest::ManifestInstaller> DeserializeInstaller(const web::json::value& installerJsonObject) const; + virtual std::map<Manifest::InstallerSwitchType, Manifest::string_t> DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const; + std::optional<Manifest::DependencyList> DeserializeDependency(const web::json::value& dependenciesJsonObject) const; virtual Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const; diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp @@ -219,6 +219,16 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json std::optional<Manifest::ManifestInstaller> installerObject = DeserializeInstaller(installer); if (installerObject) { + // Merge default switches after parsing. + auto defaultSwitches = Manifest::GetDefaultKnownSwitches(installerObject->EffectiveInstallerType()); + for (auto const& defaultSwitch : defaultSwitches) + { + if (installerObject->Switches.find(defaultSwitch.first) == installerObject->Switches.end()) + { + installerObject->Switches[defaultSwitch.first] = defaultSwitch.second; + } + } + manifest.Installers.emplace_back(std::move(installerObject.value())); } } @@ -374,19 +384,11 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json } // Installer Switches - installer.Switches = Manifest::GetDefaultKnownSwitches(installer.EffectiveInstallerType()); std::optional<std::reference_wrapper<const web::json::value>> switches = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallerSwitches)); if (switches) { - const auto& installerSwitches = switches.value().get(); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Silent, installerSwitches, Silent); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::SilentWithProgress, installerSwitches, SilentWithProgress); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Interactive, installerSwitches, Interactive); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::InstallLocation, installerSwitches, InstallLocation); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Log, installerSwitches, Log); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Update, installerSwitches, Upgrade); - TryParseInstallerSwitchField(installer.Switches, InstallerSwitchType::Custom, installerSwitches, Custom); + installer.Switches = DeserializeInstallerSwitches(switches.value().get()); } // Installer SuccessCodes @@ -433,6 +435,21 @@ namespace AppInstaller::Repository::Rest::Schema::V1_0::Json return installer; } + std::map<Manifest::InstallerSwitchType, Manifest::string_t> ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const + { + std::map<Manifest::InstallerSwitchType, Manifest::string_t> installerSwitches; + + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Silent, installerSwitchesJsonObject, Silent); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::SilentWithProgress, installerSwitchesJsonObject, SilentWithProgress); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Interactive, installerSwitchesJsonObject, Interactive); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::InstallLocation, installerSwitchesJsonObject, InstallLocation); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Log, installerSwitchesJsonObject, Log); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Update, installerSwitchesJsonObject, Upgrade); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Custom, installerSwitchesJsonObject, Custom); + + return installerSwitches; + } + std::optional<Manifest::DependencyList> ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const { if (dependenciesObject.is_null()) diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_7::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_6::Json::ManifestDeserializer + { + protected: + + std::optional<Manifest::ManifestInstaller> DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + std::map<Manifest::InstallerSwitchType, Manifest::string_t> DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include <winget/JsonUtil.h> + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_7::Json +{ + namespace + { + // Installer + constexpr std::string_view InstallerSwitchRepair = "Repair"sv; + constexpr std::string_view RepairBehavior = "RepairBehavior"sv; + } + + std::optional<Manifest::ManifestInstaller> ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_6::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.RepairBehavior = Manifest::ConvertToRepairBehaviorEnum(JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RepairBehavior)).value_or("")); + } + + return result; + } + + std::map<Manifest::InstallerSwitchType, Manifest::string_t> ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const + { + auto installerSwitches = V1_6::Json::ManifestDeserializer::DeserializeInstallerSwitches(installerSwitchesJsonObject); + + auto repairValue = JSON::GetRawStringValueFromJsonNode(installerSwitchesJsonObject, JSON::GetUtilityString(InstallerSwitchRepair)); + + if (JSON::IsValidNonEmptyStringValue(repairValue)) + { + installerSwitches[Manifest::InstallerSwitchType::Repair] = repairValue.value(); + } + + return installerSwitches; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_7; + } +}