winget-cli

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

commit 937ec874ecb707eb2df37b74ebe0100ccb7e9261
parent 36b0a236a0da48c61e7778e8fce994c858f01625
Author: JohnMcPMS <johnmcp@microsoft.com>
Date:   Thu, 27 Apr 2023 09:59:01 -0700

Enable module/resource syntax and improve file error reporting (#3186)

This change adds a new syntax that can be used when referencing resources in a configuration file: `Module/Resource`. This is only added to a new 0.2 schema version, mainly to prevent an issue with older clients failing to run the newer files in a way that is less obvious than "unrecognized configuration file version".

As part of adding a new schema version, I also added the schema version as a property to the `ConfigurationSet` and `ConfigurationUnit`. This will enable the processor to handle any semantic differences that might arise as schemas evolve.

Finally, the result from opening a configuration file now contains the field name and value, as well as the line and column number for the error (all values supplied as appropriate).  All of this information is better presented to the user from the family of `winget configure` commands.
Diffstat:
Msrc/AppInstallerCLICore/Resources.h | 5++++-
Msrc/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp | 19++++++++++++++++---
Msrc/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 16++++++++++++++--
Msrc/AppInstallerSharedLib/Public/AppInstallerErrors.h | 4+++-
Msrc/AppInstallerSharedLib/Public/winget/Yaml.h | 3+++
Msrc/AppInstallerSharedLib/Yaml.cpp | 7++++++-
Msrc/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs | 4+++-
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs | 2++
Msrc/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs | 162++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/Microsoft.Management.Configuration/ConfigurationProcessor.cpp | 5+++--
Msrc/Microsoft.Management.Configuration/ConfigurationSet.cpp | 13+++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationSet.h | 4++++
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser.cpp | 100++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser.h | 42++++++++++++++++++++++++++++++++++++++----
Msrc/Microsoft.Management.Configuration/ConfigurationSetParserError.h | 11+++++++++--
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp | 55+++++++++++++++++++++++++++++++++++++------------------
Msrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h | 4++++
Asrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h | 28++++++++++++++++++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationUnit.cpp | 13+++++++++++++
Msrc/Microsoft.Management.Configuration/ConfigurationUnit.h | 4++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl | 17+++++++++++++++++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj | 2++
Msrc/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters | 6++++++
Msrc/Microsoft.Management.Configuration/OpenConfigurationSetResult.cpp | 20+++++++++++++++++++-
Msrc/Microsoft.Management.Configuration/OpenConfigurationSetResult.h | 8+++++++-
26 files changed, 575 insertions(+), 65 deletions(-)

diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h @@ -54,7 +54,9 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldMissing); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); @@ -379,6 +381,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(SearchSource); WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SeeLineAndColumn); WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -623,13 +623,21 @@ namespace AppInstaller::CLI::Workflow OpenConfigurationSetResult openResult = context.Get<Data::ConfigurationContext>().Processor().OpenConfigurationSet(inputStream); if (FAILED_LOG(static_cast<HRESULT>(openResult.ResultCode().value))) { + AICLI_LOG(Config, Error, << "Failed to open configuration set at " << absolutePath.u8string() << " with error 0x" << Logging::SetHRFormat << static_cast<HRESULT>(openResult.ResultCode().value)); + switch (openResult.ResultCode()) { - case WINGET_CONFIG_ERROR_INVALID_FIELD: - context.Reporter.Error() << Resource::String::ConfigurationFieldInvalid(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + case WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE: + context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidType(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE: + context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidValue(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }, Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_MISSING_FIELD: + context.Reporter.Error() << Resource::String::ConfigurationFieldMissing(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; break; case WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION: - context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; break; case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE: case WINGET_CONFIG_ERROR_INVALID_YAML: @@ -638,6 +646,11 @@ namespace AppInstaller::CLI::Workflow break; } + if (openResult.Line() != 0) + { + context.Reporter.Error() << Resource::String::SeeLineAndColumn(openResult.Line(), openResult.Column()) << std::endl; + } + AICLI_TERMINATE_CONTEXT(openResult.ResultCode()); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1732,8 +1732,8 @@ Please specify one of them using the --source option to proceed.</value> <data name="ConfigureValidateCommandShortDescription" xml:space="preserve"> <value>Validates a configuration file</value> </data> - <data name="ConfigurationFieldInvalid" xml:space="preserve"> - <value>The field '{0}' in the configuration file is invalid.</value> + <data name="ConfigurationFieldInvalidType" xml:space="preserve"> + <value>The field '{0}' in the configuration file is the wrong type.</value> <comment>{Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file.</comment> </data> <data name="ConfigurationFileArgumentDescription" xml:space="preserve"> @@ -1925,4 +1925,16 @@ Please specify one of them using the --source option to proceed.</value> <value>This configuration unit was not run for an unknown reason: {0}</value> <comment>{Locked="{0}"} {0} is a placeholder for the unrecognized error code.</comment> </data> + <data name="ConfigurationFieldInvalidValue" xml:space="preserve"> + <value>The field '{0}' has an invalid value: {1}</value> + <comment>{Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value.</comment> + </data> + <data name="ConfigurationFieldMissing" xml:space="preserve"> + <value>The field '{0}' is missing or empty.</value> + <comment>{Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file.</comment> + </data> + <data name="SeeLineAndColumn" xml:space="preserve"> + <value>See line {0}, column {1} in the file.</value> + <comment>{Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively.</comment> + </data> </root> \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -155,7 +155,7 @@ // Configuration Errors #define WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE ((HRESULT)0x8A15C001) #define WINGET_CONFIG_ERROR_INVALID_YAML ((HRESULT)0x8A15C002) -#define WINGET_CONFIG_ERROR_INVALID_FIELD ((HRESULT)0x8A15C003) +#define WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE ((HRESULT)0x8A15C003) #define WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION ((HRESULT)0x8A15C004) #define WINGET_CONFIG_ERROR_SET_APPLY_FAILED ((HRESULT)0x8A15C005) #define WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER ((HRESULT)0x8A15C006) @@ -165,6 +165,8 @@ #define WINGET_CONFIG_ERROR_MANUALLY_SKIPPED ((HRESULT)0x8A15C00A) #define WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED ((HRESULT)0x8A15C00B) #define WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE ((HRESULT)0x8A15C00C) +#define WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE ((HRESULT)0x8A15C00D) +#define WINGET_CONFIG_ERROR_MISSING_FIELD ((HRESULT)0x8A15C00E) // Configuration Processor Errors #define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101) diff --git a/src/AppInstallerSharedLib/Public/winget/Yaml.h b/src/AppInstallerSharedLib/Public/winget/Yaml.h @@ -55,8 +55,11 @@ namespace AppInstaller::YAML const char* what() const noexcept override; + const Mark& GetMark() const; + private: std::string m_what; + YAML::Mark m_mark; }; // A YAML node. diff --git a/src/AppInstallerSharedLib/Yaml.cpp b/src/AppInstallerSharedLib/Yaml.cpp @@ -136,7 +136,7 @@ namespace AppInstaller::YAML } Exception::Exception(Type type, const char* problem, const Mark& problemMark, const char* context, const Mark& contextMark) : - wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) + wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR), m_mark(problemMark) { std::ostringstream out; OutputExceptionHeader(out, type); @@ -174,6 +174,11 @@ namespace AppInstaller::YAML return m_what.c_str(); } + const Mark& Exception::GetMark() const + { + return m_mark; + } + Node::Node(Type type, std::string tag, const YAML::Mark& mark) : m_type(type), m_tag(std::move(tag)), m_mark(mark) { diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs @@ -17,7 +17,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers public static readonly int WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE = unchecked((int)0x8A15C001); public static readonly int WINGET_CONFIG_ERROR_INVALID_YAML = unchecked((int)0x8A15C002); - public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD = unchecked((int)0x8A15C003); + public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE = unchecked((int)0x8A15C003); public static readonly int WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION = unchecked((int)0x8A15C004); public static readonly int WINGET_CONFIG_ERROR_SET_APPLY_FAILED = unchecked((int)0x8A15C005); public static readonly int WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER = unchecked((int)0x8A15C006); @@ -27,6 +27,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers public static readonly int WINGET_CONFIG_ERROR_MANUALLY_SKIPPED = unchecked((int)0x8A15C00A); public static readonly int WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED = unchecked((int)0x8A15C00B); public static readonly int WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); + public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); + public static readonly int WINGET_CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); #pragma warning restore SA1025 // Code should not contain multiple whitespace in a row #pragma warning restore SA1600 // Elements should be documented diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs @@ -57,6 +57,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Empty(testSet.ConfigurationUnits); testSet.ConfigurationUnits = new ConfigurationUnit[] { new ConfigurationUnit() }; Assert.Equal(1, testSet.ConfigurationUnits.Count); + + Assert.NotEqual(string.Empty, testSet.SchemaVersion); } /// <summary> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -22,6 +22,11 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests public class OpenConfigurationSetTests : ConfigurationProcessorTestBase { /// <summary> + /// The directives key for the module property. + /// </summary> + internal const string ModuleDirective = "module"; + + /// <summary> /// Initializes a new instance of the <see cref="OpenConfigurationSetTests"/> class. /// </summary> /// <param name="fixture">Unit test fixture.</param> @@ -58,6 +63,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.NotNull(result.ResultCode); Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); Assert.Equal(string.Empty, result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); } /// <summary> @@ -72,7 +79,9 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests Assert.Null(result.Set); Assert.NotNull(result.ResultCode); Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); - Assert.Equal(string.Empty, result.Field); + Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); } /// <summary> @@ -86,8 +95,10 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream("yaml: yep")); Assert.Null(result.Set); Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD, result.ResultCode.HResult); - Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); + Assert.Equal("properties", result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); } /// <summary> @@ -104,8 +115,10 @@ properties: ")); Assert.Null(result.Set); Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD, result.ResultCode.HResult); - Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); + Assert.Equal("configurationVersion", result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); } /// <summary> @@ -123,7 +136,10 @@ properties: Assert.Null(result.Set); Assert.NotNull(result.ResultCode); Assert.Equal(Errors.WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ResultCode.HResult); - Assert.Equal("99999999", result.Field); + Assert.Equal("configurationVersion", result.Field); + Assert.Equal("99999999", result.Value); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); } /// <summary> @@ -187,8 +203,10 @@ properties: ")); Assert.Null(result.Set); Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD, result.ResultCode.HResult); - Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); + Assert.Equal("resources", result.Field); + Assert.Equal(4U, result.Line); + Assert.NotEqual(0U, result.Column); } /// <summary> @@ -207,8 +225,10 @@ properties: ")); Assert.Null(result.Set); Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD, result.ResultCode.HResult); - Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); + Assert.Equal("resources[0]", result.Field); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); } /// <summary> @@ -322,5 +342,127 @@ properties: Assert.Contains("SettingStringBool", settings); Assert.Equal("false", settings["SettingStringBool"]); } + + /// <summary> + /// Test that module gets left in resource name in 0.1. + /// </summary> + [Fact] + public void ModuleInResourceName_NotFor0_1() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: + - resource: Module/Resource + id: Identifier + settings: + SettingInt: 1 +")); + + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + + Assert.Equal("0.1", result.Set.SchemaVersion); + Assert.Single(result.Set.ConfigurationUnits); + + var unit = result.Set.ConfigurationUnits[0]; + Assert.NotNull(unit); + Assert.Equal("0.1", unit.SchemaVersion); + Assert.Equal("Module/Resource", unit.UnitName); + Assert.Empty(unit.Directives); + } + + /// <summary> + /// Test that module gets parsed out of resource name in 0.2. + /// </summary> + [Fact] + public void ModuleInResourceName() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: Module + settings: + SettingInt: 1 +")); + + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + + Assert.Equal("0.2", result.Set.SchemaVersion); + Assert.Single(result.Set.ConfigurationUnits); + + var unit = result.Set.ConfigurationUnits[0]; + Assert.NotNull(unit); + Assert.Equal("0.2", unit.SchemaVersion); + Assert.Equal("Resource", unit.UnitName); + Assert.Single(unit.Directives); + Assert.True(unit.Directives.ContainsKey(ModuleDirective)); + Assert.Equal("Module", unit.Directives[ModuleDirective]); + } + + /// <summary> + /// Test that module is in the resource name and the directives and are different. + /// </summary> + [Fact] + public void ModuleInResourceName_DirectiveDifferent() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: DifferentModule + settings: + SettingInt: 1 +")); + + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); + Assert.Equal(ModuleDirective, result.Field); + Assert.Equal("DifferentModule", result.Value); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); + } + + /// <summary> + /// Test that providing only the module in the qualified name is an error. + /// </summary> + [Fact] + public void EmptyResourceWithModule() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/ + id: Identifier + settings: + SettingInt: 1 +")); + + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); + Assert.Equal("resource", result.Field); + Assert.Equal("Module/", result.Value); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); + } } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -223,7 +223,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::unique_ptr<ConfigurationSetParser> parser = ConfigurationSetParser::Create(localStream); if (FAILED(parser->Result())) { - result->Initialize(parser->Result(), parser->Field()); + result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); co_return *result; } @@ -231,9 +231,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation configurationSet->Initialize(parser->GetConfigurationUnits()); if (FAILED(parser->Result())) { - result->Initialize(parser->Result(), parser->Field()); + result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); co_return *result; } + configurationSet->SchemaVersion(parser->GetSchemaVersion()); result->Initialize(*configurationSet); } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ConfigurationSet.h" #include "ConfigurationSet.g.cpp" +#include "ConfigurationSetParser.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -11,6 +12,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation GUID instanceIdentifier; THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); m_instanceIdentifier = instanceIdentifier; + m_schemaVersion = ConfigurationSetParser::LatestVersion(); } ConfigurationSet::ConfigurationSet(const guid& instanceIdentifier) : @@ -100,6 +102,17 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_configurationUnits = winrt::single_threaded_vector<ConfigurationUnit>(std::move(temp)); } + hstring ConfigurationSet::SchemaVersion() + { + return m_schemaVersion; + } + + void ConfigurationSet::SchemaVersion(const hstring& value) + { + THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaVersion(value)); + m_schemaVersion = value; + } + event_token ConfigurationSet::ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>& handler) { return m_configurationSetChange.add(handler); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.h b/src/Microsoft.Management.Configuration/ConfigurationSet.h @@ -41,6 +41,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation Windows::Foundation::Collections::IVectorView<ConfigurationUnit> ConfigurationUnits(); void ConfigurationUnits(const Windows::Foundation::Collections::IVectorView<ConfigurationUnit>& value); + hstring SchemaVersion(); + void SchemaVersion(const hstring& value); + event_token ConfigurationSetChange(const Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>& handler); void ConfigurationSetChange(const event_token& token) noexcept; @@ -56,6 +59,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation guid m_instanceIdentifier; clock::time_point m_firstApply{}; Windows::Foundation::Collections::IVector<ConfigurationUnit> m_configurationUnits{ winrt::single_threaded_vector<ConfigurationUnit>() }; + hstring m_schemaVersion; winrt::event<Windows::Foundation::TypedEventHandler<WinRT_Self, ConfigurationSetChangeData>> m_configurationSetChange; MutableFlag m_mutableFlag; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -7,10 +7,12 @@ #include <AppInstallerLogging.h> #include <AppInstallerStrings.h> #include <AppInstallerVersions.h> -#include <winget/Yaml.h> #include "ConfigurationSetParserError.h" #include "ConfigurationSetParser_0_1.h" +#include "ConfigurationSetParser_0_2.h" + +using namespace AppInstaller::YAML; namespace winrt::Microsoft::Management::Configuration::implementation { @@ -52,32 +54,49 @@ namespace winrt::Microsoft::Management::Configuration::implementation { AICLI_LOG_LARGE_STRING(Config, Verbose, << "Parsing configuration set:", input); - AppInstaller::YAML::Node document; - + Node document; + std::string documentError; + Mark documentErrorMark; + try { - document = AppInstaller::YAML::Load(input); + document = Load(input); + } + catch (const Exception& exc) + { + documentError = exc.what(); + documentErrorMark = exc.GetMark(); } CATCH_LOG(); if (!document.IsMap()) { - AICLI_LOG(Config, Info, << "Invalid YAML"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_YAML); + AICLI_LOG(Config, Error, << "Invalid YAML: " << documentError << " at [line " << documentErrorMark.line << ", col " << documentErrorMark.column << "]"); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_YAML, documentError, documentErrorMark); } - AppInstaller::YAML::Node& propertiesNode = document[NodeName_Properties]; - if (!propertiesNode.IsMap()) + Node& propertiesNode = document[GetFieldName(FieldName::Properties)]; + if (!propertiesNode) { - AICLI_LOG(Config, Info, << "Invalid properties"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD, NodeName_Properties); + AICLI_LOG(Config, Error, << "No properties"); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::Properties)); + } + else if (!propertiesNode.IsMap()) + { + AICLI_LOG(Config, Error, << "Invalid properties type"); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::Properties), propertiesNode.Mark()); } - AppInstaller::YAML::Node& versionNode = propertiesNode[NodeName_ConfigurationVersion]; - if (!versionNode.IsScalar()) + Node& versionNode = propertiesNode[GetFieldName(FieldName::ConfigurationVersion)]; + if (!versionNode) + { + AICLI_LOG(Config, Error, << "No configuration version"); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::ConfigurationVersion)); + } + else if (!versionNode.IsScalar()) { - AICLI_LOG(Config, Info, << "Invalid configuration version"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD, NodeName_ConfigurationVersion); + AICLI_LOG(Config, Error, << "Invalid configuration version type"); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::ConfigurationVersion), versionNode.Mark()); } AppInstaller::Utility::SemanticVersion schemaVersion(versionNode.as<std::string>()); @@ -86,15 +105,60 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return std::make_unique<ConfigurationSetParser_0_1>(std::move(document)); } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) + { + return std::make_unique<ConfigurationSetParser_0_2>(std::move(document)); + } + + AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetFieldName(FieldName::ConfigurationVersion), versionNode.as<std::string>()); + } + + bool ConfigurationSetParser::IsRecognizedSchemaVersion(hstring value) try + { + using namespace AppInstaller::Utility; + + SemanticVersion schemaVersion(ConvertToUTF8(value)); - AICLI_LOG(Config, Info, << "Unknown configuration version: " << schemaVersion.ToString()); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, versionNode.as<std::string>()); + return (schemaVersion == SemanticVersion{ "0.1" } || schemaVersion == SemanticVersion{ "0.2" }); } + catch (...) { LOG_CAUGHT_EXCEPTION(); return false; } - void ConfigurationSetParser::SetError(hresult result, std::string_view field) + hstring ConfigurationSetParser::LatestVersion() { - AICLI_LOG(Config, Error, << "ConfigurationSetParser error: " << AppInstaller::Logging::SetHRFormat << result << " [" << field << "]"); + return hstring{ L"0.2" }; + } + + void ConfigurationSetParser::SetError(hresult result, std::string_view field, std::string_view value, uint32_t line, uint32_t column) + { + AICLI_LOG(Config, Error, << "ConfigurationSetParser error: " << AppInstaller::Logging::SetHRFormat << result << " for " << field << " with value `" << value << "` at [line " << line << ", col " << column << "]"); m_result = result; m_field = AppInstaller::Utility::ConvertToUTF16(field); + m_value = AppInstaller::Utility::ConvertToUTF16(value); + m_line = line; + m_column = column; + } + + void ConfigurationSetParser::SetError(hresult result, std::string_view field, const Mark& mark, std::string_view value) + { + SetError(result, field, value, static_cast<uint32_t>(mark.line), static_cast<uint32_t>(mark.column)); + } + + std::string_view ConfigurationSetParser::GetFieldName(FieldName fieldName) + { + switch (fieldName) + { + case FieldName::ConfigurationVersion: return "configurationVersion"sv; + case FieldName::Properties: return "properties"sv; + case FieldName::Resource: return "resource"sv; + case FieldName::ModuleDirective: return "module"sv; + } + + THROW_HR(E_UNEXPECTED); + } + + hstring ConfigurationSetParser::GetFieldNameHString(FieldName fieldName) + { + return hstring{ AppInstaller::Utility::ConvertToUTF16(GetFieldName(fieldName)) }; } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h @@ -2,6 +2,7 @@ // Licensed under the MIT License. #pragma once #include <ConfigurationUnit.h> +#include <winget/Yaml.h> #include <winrt/Windows.Storage.Streams.h> #include <memory> #include <string_view> @@ -14,15 +15,19 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Interface for parsing a configuration set stream. struct ConfigurationSetParser { - static constexpr std::string_view NodeName_Properties = "properties"sv; - static constexpr std::string_view NodeName_ConfigurationVersion = "configurationVersion"sv; - // Create a parser from the given stream. static std::unique_ptr<ConfigurationSetParser> Create(const Windows::Storage::Streams::IInputStream& stream); // Create a parser from the given bytes (the encoding is detected). static std::unique_ptr<ConfigurationSetParser> Create(std::string_view input); + // Determines if the given value is a recognized schema version. + // This will only return true for a version that we fully recognize. + static bool IsRecognizedSchemaVersion(hstring value); + + // Gets the latest schema version. + static hstring LatestVersion(); + virtual ~ConfigurationSetParser() noexcept = default; ConfigurationSetParser(const ConfigurationSetParser&) = delete; @@ -33,19 +38,48 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Retrieve the configuration units from the parser. virtual std::vector<Configuration::ConfigurationUnit> GetConfigurationUnits() = 0; + // Retrieves the schema version of the parser. + virtual hstring GetSchemaVersion() = 0; + // The latest result code from the parser. hresult Result() const { return m_result; } // The field related to the result code. hstring Field() const { return m_field; } + // The value of the field. + hstring Value() const { return m_value; } + + // The line related to the result code. + uint32_t Line() const { return m_line; } + + // The column related to the result code. + uint32_t Column() const { return m_column; } + protected: ConfigurationSetParser() = default; // Set the error state - void SetError(hresult result, std::string_view field = {}); + void SetError(hresult result, std::string_view field = {}, std::string_view value = {}, uint32_t line = 0, uint32_t column = 0); + void SetError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark, std::string_view value = {}); + + // The various field names that are used in parsing. + enum class FieldName + { + ConfigurationVersion, + Properties, + Resource, + ModuleDirective, + }; + + // Gets the value of the field name. + static std::string_view GetFieldName(FieldName fieldName); + static hstring GetFieldNameHString(FieldName fieldName); hresult m_result; hstring m_field; + hstring m_value; + uint32_t m_line = 0; + uint32_t m_column = 0; }; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h b/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h @@ -9,11 +9,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Parser object that only indicates an error occurred. struct ConfigurationSetParserError : public ConfigurationSetParser { - ConfigurationSetParserError(hresult result, std::string_view field = {}) + ConfigurationSetParserError(hresult result, std::string_view field = {}, std::string_view value = {}) { - SetError(result, field); + SetError(result, field, value); + } + + ConfigurationSetParserError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark) + { + SetError(result, field, mark); } std::vector<Configuration::ConfigurationUnit> GetConfigurationUnits() override { return {}; } + + hstring GetSchemaVersion() override { return {}; } }; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp @@ -13,8 +13,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation using namespace AppInstaller::YAML; #define CHECK_ERROR(_op_) (_op_); if (FAILED(m_result)) { return; } -#define FIELD_ERROR(_field_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD, (_field_)); return -#define FIELD_ERROR_IF(_condition_,_field_) if (_condition_) { FIELD_ERROR(_field_); } + +#define FIELD_TYPE_ERROR(_field_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, (_field_), (_mark_)); return +#define FIELD_TYPE_ERROR_IF(_condition_,_field_,_mark_) if (_condition_) { FIELD_TYPE_ERROR(_field_,_mark_); } + +#define FIELD_MISSING_ERROR(_field_) SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, (_field_)); return +#define FIELD_MISSING_ERROR_IF(_condition_,_field_) if (_condition_) { FIELD_MISSING_ERROR(_field_); } namespace { @@ -106,7 +110,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::vector<Configuration::ConfigurationUnit> ConfigurationSetParser_0_1::GetConfigurationUnits() { std::vector<Configuration::ConfigurationUnit> result; - const Node& properties = m_document[NodeName_Properties]; + const Node& properties = m_document[GetFieldName(FieldName::Properties)]; ParseConfigurationUnitsFromSubsection(properties, "assertions", ConfigurationUnitIntent::Assert, result); ParseConfigurationUnitsFromSubsection(properties, "parameters", ConfigurationUnitIntent::Inform, result); ParseConfigurationUnitsFromSubsection(properties, "resources", ConfigurationUnitIntent::Apply, result); @@ -114,6 +118,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation return result; } + hstring ConfigurationSetParser_0_1::GetSchemaVersion() + { + static hstring s_schemaVersion{ L"0.1" }; + return s_schemaVersion; + } + void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromSubsection(const Node& document, std::string_view subsection, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result) { if (FAILED(m_result)) @@ -128,7 +138,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return; } - FIELD_ERROR_IF(!subsectionNode.IsSequence(), subsection); + FIELD_TYPE_ERROR_IF(!subsectionNode.IsSequence(), subsection, subsectionNode.Mark()); std::ostringstream strstr; strstr << subsection; @@ -139,38 +149,47 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (!item.IsMap()) { strstr << '[' << index << ']'; - FIELD_ERROR(strstr.str()); + FIELD_TYPE_ERROR(strstr.str(), item.Mark()); } index++; auto configurationUnit = make_self<wil::details::module_count_wrapper<ConfigurationUnit>>(); + configurationUnit->SchemaVersion(GetSchemaVersion()); - CHECK_ERROR(GetStringValueForUnit(item, "resource", true, configurationUnit.get(), &ConfigurationUnit::UnitName)); - CHECK_ERROR(GetStringValueForUnit(item, "id", false, configurationUnit.get(), &ConfigurationUnit::Identifier)); - configurationUnit->Intent(intent); - CHECK_ERROR(GetStringArrayForUnit(item, "dependsOn", configurationUnit.get(), &ConfigurationUnit::Dependencies)); - CHECK_ERROR(GetValueSet(item, "directives", false, configurationUnit->Directives())); - CHECK_ERROR(GetValueSet(item, "settings", false, configurationUnit->Settings())); + ParseConfigurationUnit(configurationUnit.get(), item, intent); result.emplace_back(*configurationUnit); } } + void ConfigurationSetParser_0_1::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) + { + CHECK_ERROR(GetStringValueForUnit(unitNode, GetFieldName(FieldName::Resource), true, unit, &ConfigurationUnit::UnitName)); + CHECK_ERROR(GetStringValueForUnit(unitNode, "id", false, unit, &ConfigurationUnit::Identifier)); + unit->Intent(intent); + CHECK_ERROR(GetStringArrayForUnit(unitNode, "dependsOn", unit, &ConfigurationUnit::Dependencies)); + CHECK_ERROR(GetValueSet(unitNode, "directives", false, unit->Directives())); + CHECK_ERROR(GetValueSet(unitNode, "settings", false, unit->Settings())); + } + void ConfigurationSetParser_0_1::GetStringValueForUnit(const Node& item, std::string_view valueName, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)) { const Node& valueNode = item[valueName]; if (valueNode) { - FIELD_ERROR_IF(!valueNode.IsScalar(), valueName); + FIELD_TYPE_ERROR_IF(!valueNode.IsScalar(), valueName, valueNode.Mark()); } else { - FIELD_ERROR_IF(required, valueName); + FIELD_MISSING_ERROR_IF(required, valueName); return; } - (unit->*propertyFunction)(hstring{ valueNode.as<std::wstring>() }); + hstring value{ valueNode.as<std::wstring>() }; + FIELD_MISSING_ERROR_IF(value.empty() && required, valueName); + + (unit->*propertyFunction)(std::move(value)); } void ConfigurationSetParser_0_1::GetStringArrayForUnit(const Node& item, std::string_view arrayName, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)) @@ -182,7 +201,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return; } - FIELD_ERROR_IF(!arrayNode.IsSequence(), arrayName); + FIELD_TYPE_ERROR_IF(!arrayNode.IsSequence(), arrayName, arrayNode.Mark()); std::vector<hstring> arrayValue; @@ -195,7 +214,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (!arrayItem.IsScalar()) { strstr << '[' << index << ']'; - FIELD_ERROR(strstr.str()); + FIELD_TYPE_ERROR(strstr.str(), arrayItem.Mark()); } index++; @@ -211,11 +230,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (mapNode) { - FIELD_ERROR_IF(!mapNode.IsMap(), mapName); + FIELD_TYPE_ERROR_IF(!mapNode.IsMap(), mapName, mapNode.Mark()); } else { - FIELD_ERROR_IF(required, mapName); + FIELD_MISSING_ERROR_IF(required, mapName); return; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h @@ -22,8 +22,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Retrieve the configuration units from the parser. std::vector<Configuration::ConfigurationUnit> GetConfigurationUnits() override; + // Retrieves the schema version of the parser. + hstring GetSchemaVersion() override; + protected: void ParseConfigurationUnitsFromSubsection(const AppInstaller::YAML::Node& document, std::string_view subsection, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result); + virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent); void GetStringValueForUnit(const AppInstaller::YAML::Node& item, std::string_view valueName, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)); void GetStringArrayForUnit(const AppInstaller::YAML::Node& item, std::string_view arrayName, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)); void GetValueSet(const AppInstaller::YAML::Node& item, std::string_view mapName, bool required, const Windows::Foundation::Collections::ValueSet& valueSet); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetParser_0_2.h" + +#include <AppInstallerErrors.h> +#include <AppInstallerStrings.h> + +#include <sstream> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using namespace AppInstaller::YAML; + +#define FIELD_TYPE_ERROR(_field_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, (_field_), (_mark_)); return +#define FIELD_TYPE_ERROR_IF(_condition_,_field_,_mark_) if (_condition_) { FIELD_TYPE_ERROR(_field_,_mark_); } + +#define FIELD_VALUE_ERROR(_field_,_value_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, (_field_), (_mark_), (_value_)); return +#define FIELD_VALUE_ERROR_IF(_condition_,_field_,_value_,_mark_) if (_condition_) { FIELD_VALUE_ERROR(_field_,_value_,_mark_); } + + namespace + { + // Contains the qualified resource name information. + struct QualifiedResourceName + { + QualifiedResourceName(hstring input) + { + std::wstring_view inputView = input; + size_t pos = inputView.find('/'); + + if (pos != std::wstring_view::npos) + { + Module = inputView.substr(0, pos); + Resource = inputView.substr(pos + 1); + } + else + { + Resource = input; + } + } + + hstring Module; + hstring Resource; + }; + } + + hstring ConfigurationSetParser_0_2::GetSchemaVersion() + { + static hstring s_schemaVersion{ L"0.2" }; + return s_schemaVersion; + } + + void ConfigurationSetParser_0_2::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) + { + using namespace AppInstaller::Utility; + + ConfigurationSetParser_0_1::ParseConfigurationUnit(unit, unitNode, intent); + + // Move module qualification into directives if present + QualifiedResourceName qualifiedName{ unit->UnitName() }; + + FIELD_VALUE_ERROR_IF(qualifiedName.Resource.empty(), GetFieldName(FieldName::Resource), ConvertToUTF8(unit->UnitName()), unitNode.Mark()); + + if (!qualifiedName.Module.empty()) + { + // If the module is provided in both the resource name and the directives, ensure that it matches + hstring moduleDirectiveFieldName = GetFieldNameHString(FieldName::ModuleDirective); + auto moduleDirective = unit->Directives().TryLookup(moduleDirectiveFieldName); + if (moduleDirective) + { + auto moduleProperty = moduleDirective.try_as<Windows::Foundation::IPropertyValue>(); + FIELD_TYPE_ERROR_IF(!moduleProperty, GetFieldName(FieldName::ModuleDirective), unitNode.Mark()); + FIELD_TYPE_ERROR_IF(moduleProperty.Type() != Windows::Foundation::PropertyType::String, GetFieldName(FieldName::ModuleDirective), unitNode.Mark()); + hstring moduleValue = moduleProperty.GetString(); + FIELD_VALUE_ERROR_IF(qualifiedName.Module != moduleValue, GetFieldName(FieldName::ModuleDirective), ConvertToUTF8(moduleValue), unitNode.Mark()); + } + else + { + unit->Directives().Insert(moduleDirectiveFieldName, Windows::Foundation::PropertyValue::CreateString(qualifiedName.Module)); + } + + // Set the unit name to be just the resource portion + unit->UnitName(qualifiedName.Resource); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetParser_0_1.h" + +#include <winget/Yaml.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Parser for schema version 0.2 + struct ConfigurationSetParser_0_2 : public ConfigurationSetParser_0_1 + { + ConfigurationSetParser_0_2(AppInstaller::YAML::Node&& document) : ConfigurationSetParser_0_1(std::move(document)) {} + + virtual ~ConfigurationSetParser_0_2() noexcept = default; + + ConfigurationSetParser_0_2(const ConfigurationSetParser_0_2&) = delete; + ConfigurationSetParser_0_2& operator=(const ConfigurationSetParser_0_2&) = delete; + ConfigurationSetParser_0_2(ConfigurationSetParser_0_2&&) = default; + ConfigurationSetParser_0_2& operator=(ConfigurationSetParser_0_2&&) = default; + + // Retrieves the schema version of the parser. + hstring GetSchemaVersion() override; + + protected: + void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent) override; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "ConfigurationUnit.h" #include "ConfigurationUnit.g.cpp" +#include "ConfigurationSetParser.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -11,6 +12,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation GUID instanceIdentifier; THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); m_instanceIdentifier = instanceIdentifier; + m_schemaVersion = ConfigurationSetParser::LatestVersion(); } ConfigurationUnit::ConfigurationUnit(const guid& instanceIdentifier) : @@ -114,4 +116,15 @@ namespace winrt::Microsoft::Management::Configuration::implementation { m_shouldApply = value; } + + hstring ConfigurationUnit::SchemaVersion() + { + return m_schemaVersion; + } + + void ConfigurationUnit::SchemaVersion(const hstring& value) + { + THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaVersion(value)); + m_schemaVersion = value; + } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -45,6 +45,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool ShouldApply(); void ShouldApply(bool value); + hstring SchemaVersion(); + void SchemaVersion(const hstring& value); + #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Dependencies(std::vector<hstring>&& value); void Details(IConfigurationUnitProcessorDetails&& details); @@ -59,6 +62,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation Windows::Foundation::Collections::ValueSet m_settings; IConfigurationUnitProcessorDetails m_details{ nullptr }; bool m_shouldApply = true; + hstring m_schemaVersion; MutableFlag m_mutableFlag; #endif diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -212,6 +212,10 @@ namespace Microsoft.Management.Configuration // Allows for control over whether this unit should be applied when the set containing it is applied. Boolean ShouldApply; + + // The schema version to use for the unit. + // Will be set to the schema version when read in, and default to the latest if created manually. + String SchemaVersion; } // The change event type that has occurred for a configuration set change. @@ -279,6 +283,10 @@ namespace Microsoft.Management.Configuration // Can be set on a mutable configuration set; this will copy the items into the internal storage. Windows.Foundation.Collections.IVectorView<ConfigurationUnit> ConfigurationUnits; + // The schema version to use for the set. + // Will be set to the schema version when read in, and default to the latest if created manually. + String SchemaVersion; + // Only changes for this set are sent to this event. // This includes things like: start/stop of the entire set for application or test, start/stop of a unit for application or test. event Windows.Foundation.TypedEventHandler<ConfigurationSet, ConfigurationSetChangeData> ConfigurationSetChange; @@ -454,6 +462,15 @@ namespace Microsoft.Management.Configuration // The field that is missing/invalid, if appropriate for the specific ResultCode. String Field{ get; }; + + // The value of the field, if appropriate for the specific ResultCode. + String Value{ get; }; + + // The line number for the failure reason, if determined. + UInt32 Line{ get; }; + + // The column number for the failure reason, if determined. + UInt32 Column{ get; }; } // The type of conflict between configuration sets that was detected. diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -155,6 +155,7 @@ <ClInclude Include="ConfigurationSetParser.h" /> <ClInclude Include="ConfigurationSetParserError.h" /> <ClInclude Include="ConfigurationSetParser_0_1.h" /> + <ClInclude Include="ConfigurationSetParser_0_2.h" /> <ClInclude Include="ConfigurationUnit.h" /> <ClInclude Include="ConfigurationUnitResultInformation.h" /> <ClInclude Include="DiagnosticInformation.h" /> @@ -186,6 +187,7 @@ <ClCompile Include="ConfigurationSetChangeData.cpp" /> <ClCompile Include="ConfigurationSetParser.cpp" /> <ClCompile Include="ConfigurationSetParser_0_1.cpp" /> + <ClCompile Include="ConfigurationSetParser_0_2.cpp" /> <ClCompile Include="ConfigurationUnit.cpp" /> <ClCompile Include="ConfigurationUnitResultInformation.cpp" /> <ClCompile Include="DiagnosticInformation.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -84,6 +84,9 @@ <ClCompile Include="Telemetry\Telemetry.cpp"> <Filter>Telemetry</Filter> </ClCompile> + <ClCompile Include="ConfigurationSetParser_0_2.cpp"> + <Filter>Parser</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -174,6 +177,9 @@ <ClInclude Include="Telemetry\Telemetry.h"> <Filter>Telemetry</Filter> </ClInclude> + <ClInclude Include="ConfigurationSetParser_0_2.h"> + <Filter>Parser</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" /> diff --git a/src/Microsoft.Management.Configuration/OpenConfigurationSetResult.cpp b/src/Microsoft.Management.Configuration/OpenConfigurationSetResult.cpp @@ -11,10 +11,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation m_set = std::move(configurationSet); } - void OpenConfigurationSetResult::Initialize(hresult resultCode, hstring field) + void OpenConfigurationSetResult::Initialize(hresult resultCode, hstring field, hstring value, uint32_t line, uint32_t column) { m_resultCode = resultCode; m_field = field; + m_value = value; + m_line = line; + m_column = column; } Configuration::ConfigurationSet OpenConfigurationSetResult::Set() @@ -31,4 +34,19 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return m_field; } + + hstring OpenConfigurationSetResult::Value() + { + return m_value; + } + + uint32_t OpenConfigurationSetResult::Line() + { + return m_line; + } + + uint32_t OpenConfigurationSetResult::Column() + { + return m_column; + } } diff --git a/src/Microsoft.Management.Configuration/OpenConfigurationSetResult.h b/src/Microsoft.Management.Configuration/OpenConfigurationSetResult.h @@ -12,18 +12,24 @@ namespace winrt::Microsoft::Management::Configuration::implementation #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) void Initialize(Configuration::ConfigurationSet configurationSet); - void Initialize(hresult resultCode, hstring field = {}); + void Initialize(hresult resultCode, hstring field = {}, hstring value = {}, uint32_t line = 0, uint32_t column = 0); #endif Configuration::ConfigurationSet Set(); hresult ResultCode(); hstring Field(); + hstring Value(); + uint32_t Line(); + uint32_t Column(); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) private: Configuration::ConfigurationSet m_set = nullptr; hresult m_resultCode; hstring m_field; + hstring m_value; + uint32_t m_line = 0; + uint32_t m_column = 0; #endif }; }