diff --git a/src/PASopa.sln b/src/PASopa.sln
index a7347547..b8fea533 100644
--- a/src/PASopa.sln
+++ b/src/PASopa.sln
@@ -26,6 +26,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7361DB16-D53
Versions.props = Versions.props
EndProjectSection
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlValidator", "YamlValidator\Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj", "{296D952B-C284-4AAB-9A79-59538CA7BF38}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "YamlValidator.Tests", "YamlValidator.Tests\YamlValidator.Tests.csproj", "{E6180C81-59C1-4B25-90E0-E4390D455518}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -52,6 +56,14 @@ Global
{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {296D952B-C284-4AAB-9A79-59538CA7BF38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {296D952B-C284-4AAB-9A79-59538CA7BF38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {296D952B-C284-4AAB-9A79-59538CA7BF38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {296D952B-C284-4AAB-9A79-59538CA7BF38}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E6180C81-59C1-4B25-90E0-E4390D455518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E6180C81-59C1-4B25-90E0-E4390D455518}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E6180C81-59C1-4B25-90E0-E4390D455518}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E6180C81-59C1-4B25-90E0-E4390D455518}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -60,6 +72,7 @@ Global
{8AD94CC0-7330-4880-A8E0-177B37CDB27B} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
{7361DB16-D534-4E0E-8597-BE22317DEF47} = {794D8C68-BF6F-49C8-BCA5-AA52D8F45EF4}
+ {E6180C81-59C1-4B25-90E0-E4390D455518} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C936F8B1-DE7A-4401-95D5-5E199210F960}
diff --git a/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj b/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
index 7f978dd4..f4748665 100644
--- a/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
+++ b/src/Persistence/Microsoft.PowerPlatform.PowerApps.Persistence.csproj
@@ -32,7 +32,7 @@
true
true
- $(NoWarn);NU1601;CA1822
+ $(NoWarn);NU1601
@@ -43,18 +43,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/Persistence/YamlValidator/Constants.cs b/src/Persistence/YamlValidator/Constants.cs
deleted file mode 100644
index c750ab6a..00000000
--- a/src/Persistence/YamlValidator/Constants.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-
-public static class Constants
-{
- public const string FileTypeName = "file";
- public const string FolderTypeName = "folder";
- public const string YamlFileExtension = ".pa.yaml";
- public const string JsonFileExtension = ".json";
-
- public const string Verbose = "verbose";
-
- // runtime constants
- // default schema path
- public static readonly string DefaultSchemaPath = Path.Combine(".", "schema", "pa.yaml-schema.json");
-}
diff --git a/src/Persistence/YamlValidator/SchemaLoader.cs b/src/Persistence/YamlValidator/SchemaLoader.cs
deleted file mode 100644
index 48e65628..00000000
--- a/src/Persistence/YamlValidator/SchemaLoader.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using Json.Schema;
-
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-
-public class SchemaLoader
-{
- private const string _schemaFolderPath = "subschemas";
- private static readonly string _schemaPath = Path.Combine(".", "YamlValidator", "schema", "pa.yaml-schema.json");
-
- public JsonSchema Load()
- {
- var node = JsonSchema.FromFile(_schemaPath);
- var schemaFolder = Path.GetDirectoryName(_schemaPath);
- var subschemaPaths = Directory.GetFiles($@"{schemaFolder}{Path.DirectorySeparatorChar}{_schemaFolderPath}",
- $"*{Constants.JsonFileExtension}");
-
- foreach (var path in subschemaPaths)
- {
- var subschema = JsonSchema.FromFile(path);
- SchemaRegistry.Global.Register(subschema);
- }
-
- return node;
- }
-}
-
diff --git a/src/Persistence/YamlValidator/ValidationProcessor.cs b/src/Persistence/YamlValidator/ValidationProcessor.cs
deleted file mode 100644
index 85f173c2..00000000
--- a/src/Persistence/YamlValidator/ValidationProcessor.cs
+++ /dev/null
@@ -1,40 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-
-public class ValidationProcessor
-{
- private readonly YamlLoader _fileLoader;
- private readonly SchemaLoader _schemaLoader;
- private readonly Validator _validator;
-
- public ValidationProcessor(YamlLoader fileLoader, SchemaLoader schemaLoader, Validator validator)
- {
- _fileLoader = fileLoader;
- _schemaLoader = schemaLoader;
- _validator = validator;
- }
-
- public void RunValidation(ValidationRequest inputData)
- {
- var path = inputData.FilePath;
- var pathType = inputData.FilePathType;
-
- var yamlData = _fileLoader.Load(path, pathType);
- var serializedSchema = _schemaLoader.Load();
-
- foreach (var yamlFileData in yamlData)
- {
- Console.WriteLine($"Validating '{yamlFileData.Key}'");
- var result = _validator.Validate(serializedSchema, yamlFileData.Value);
- Console.WriteLine($"Validation {(result.SchemaValid ? "Passed" : "Failed")}");
-
- foreach (var error in result.TraversalResults)
- {
- Console.WriteLine($"{error}");
- }
- Console.WriteLine();
- }
- }
-}
diff --git a/src/Persistence/YamlValidator/VerbosityData.cs b/src/Persistence/YamlValidator/VerbosityData.cs
deleted file mode 100644
index d8383614..00000000
--- a/src/Persistence/YamlValidator/VerbosityData.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-using System.Text.Json;
-using Json.Schema;
-
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-public readonly record struct VerbosityData
-{
- public EvaluationOptions EvalOptions { get; }
- public JsonSerializerOptions JsonOutputOptions { get; }
-
- public VerbosityData(string verbosityLevel)
- {
- EvalOptions = new EvaluationOptions();
- JsonOutputOptions = new JsonSerializerOptions { Converters = { new EvaluationResultsJsonConverter() } };
-
- if (verbosityLevel == Constants.Verbose)
- {
- EvalOptions.OutputFormat = OutputFormat.List;
- return;
- }
- EvalOptions.OutputFormat = OutputFormat.Flag;
- }
-}
-
diff --git a/src/Persistence/YamlValidator/YamlLoader.cs b/src/Persistence/YamlValidator/YamlLoader.cs
deleted file mode 100644
index 8e8d94e3..00000000
--- a/src/Persistence/YamlValidator/YamlLoader.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) Microsoft Corporation.
-// Licensed under the MIT License.
-
-using System.Collections.ObjectModel;
-
-namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-
-public class YamlLoader
-{
- public IReadOnlyDictionary Load(string filePath, string pathType)
- {
- var deserializedYaml = new Dictionary();
-
- if (pathType == Constants.FileTypeName)
- {
- var fileName = Path.GetFileName(filePath);
- var yamlText = Utility.ReadFileData(filePath);
- deserializedYaml.Add(fileName, yamlText);
- }
- else if (pathType == Constants.FolderTypeName)
- {
- // TODO: Determine if argument flag should be required to specify recursive folder search
- try
- {
- var yamlFiles = Directory.EnumerateFiles(filePath, "*" + Constants.YamlFileExtension, SearchOption.AllDirectories);
- foreach (var filename in yamlFiles)
- {
- var fileName = Path.GetFullPath(filename);
- var yamlText = Utility.ReadFileData(filename);
- deserializedYaml.Add(fileName, yamlText);
- }
- }
- catch (UnauthorizedAccessException ex)
- {
- Console.WriteLine($"Unauthorized access exception: {ex.Message}");
- }
- catch (IOException ex)
- {
- Console.WriteLine($"IO exception: {ex.Message}");
- }
- }
- else
- {
- throw new ArgumentException("Invalid path type");
- }
-
- return new ReadOnlyDictionary(deserializedYaml);
- }
-}
diff --git a/src/YamlValidator.Tests/TestBase.cs b/src/YamlValidator.Tests/TestBase.cs
new file mode 100644
index 00000000..90349fa7
--- /dev/null
+++ b/src/YamlValidator.Tests/TestBase.cs
@@ -0,0 +1,38 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+
+namespace Persistence.YamlValidator.Tests;
+public abstract class TestBase : VSTestBase
+{
+ public static IServiceProvider ServiceProvider { get; set; }
+ public IValidatorFactory ValidatorFactory { get; private set; }
+
+ static TestBase()
+ {
+ ServiceProvider = BuildServiceProvider();
+ }
+
+ public TestBase()
+ {
+ ValidatorFactory = ServiceProvider.GetRequiredService();
+ }
+
+ private static ServiceProvider BuildServiceProvider()
+ {
+ var serviceCollection = new ServiceCollection();
+ var serviceProvider = ConfigureServices(serviceCollection);
+
+ return serviceProvider;
+ }
+
+ private static ServiceProvider ConfigureServices(IServiceCollection services)
+ {
+ services.AddPowerAppsPersistenceYamlValidator();
+
+ return services.BuildServiceProvider();
+ }
+
+}
diff --git a/src/YamlValidator.Tests/VSTestBase.cs b/src/YamlValidator.Tests/VSTestBase.cs
new file mode 100644
index 00000000..d2a32f09
--- /dev/null
+++ b/src/YamlValidator.Tests/VSTestBase.cs
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Persistence.YamlValidator.Tests;
+
+///
+/// Represents a shared base class for any test class that uses the Visual Studio Test Framework.
+///
+///
+/// DO NOT add any setup/tear down logic to this class, as not all tests may require it.
+/// The preferred approach is to use a different derived base class for tests that require setup/tear down logic specific to some shared scenarios.
+///
+public abstract class VSTestBase
+{
+ public required TestContext TestContext { get; set; }
+}
diff --git a/src/YamlValidator.Tests/ValidatorFactoryTest.cs b/src/YamlValidator.Tests/ValidatorFactoryTest.cs
new file mode 100644
index 00000000..1c6c3cd3
--- /dev/null
+++ b/src/YamlValidator.Tests/ValidatorFactoryTest.cs
@@ -0,0 +1,20 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+namespace Persistence.YamlValidator.Tests;
+
+[TestClass]
+public class ValidatorFactoryTest : TestBase
+{
+ [TestMethod]
+ public void GetValidatorTest()
+ {
+ var factory = new ValidatorFactory();
+ var validator = factory.CreateValidator();
+
+ Assert.IsNotNull(validator);
+ Assert.IsInstanceOfType(validator, typeof(IValidator));
+ }
+}
diff --git a/src/Persistence.Tests/YamlValidator/ValidatorTest.cs b/src/YamlValidator.Tests/ValidatorTest.cs
similarity index 58%
rename from src/Persistence.Tests/YamlValidator/ValidatorTest.cs
rename to src/YamlValidator.Tests/ValidatorTest.cs
index 20030b95..56f99dc7 100644
--- a/src/Persistence.Tests/YamlValidator/ValidatorTest.cs
+++ b/src/YamlValidator.Tests/ValidatorTest.cs
@@ -1,30 +1,25 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Json.Schema;
using Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-namespace Persistence.Tests.YamlValidator;
+namespace Persistence.YamlValidator.Tests;
[TestClass]
-public class ValidatorTest
+public class ValidatorTest : TestBase
{
- private static readonly string _validPath = Path.Combine(".", "_TestData", "ValidatorTests", "ValidYaml") +
+ private static readonly string _validPath = Path.Combine(".", "_TestData", "ValidYaml") +
Path.DirectorySeparatorChar;
- private static readonly string _invalidPath = Path.Combine(".", "_TestData", "ValidatorTests", "InvalidYaml") +
+ private static readonly string _invalidPath = Path.Combine(".", "_TestData", "InvalidYaml") +
Path.DirectorySeparatorChar;
- private readonly JsonSchema _schema;
- private readonly Validator _yamlValidator;
+ private readonly IValidator _yamlValidator;
public ValidatorTest()
{
- var schemaFileLoader = new SchemaLoader();
- _schema = schemaFileLoader.Load();
- var resultVerbosity = new VerbosityData(Constants.Verbose);
- _yamlValidator = new Validator(resultVerbosity.EvalOptions, resultVerbosity.JsonOutputOptions);
+ _yamlValidator = ValidatorFactory.CreateValidator();
}
[TestMethod]
@@ -34,8 +29,8 @@ public ValidatorTest()
public void TestValidationValidYaml(string filename)
{
- var rawYaml = Utility.ReadFileData($@"{_validPath}{filename}");
- var result = _yamlValidator.Validate(_schema, rawYaml);
+ var rawYaml = File.ReadAllText($@"{_validPath}{filename}");
+ var result = _yamlValidator.Validate(rawYaml);
Assert.IsTrue(result.SchemaValid);
}
@@ -48,10 +43,11 @@ public void TestValidationValidYaml(string filename)
[DataRow("EmptyArray.yaml")]
[DataRow("Empty.yaml")]
[DataRow("NamelessObjectNoControl.yaml")]
+ [DataRow("NotYaml.yaml")]
public void TestValidationInvalidYaml(string filename)
{
- var rawYaml = Utility.ReadFileData($@"{_invalidPath}{filename}");
- var result = _yamlValidator.Validate(_schema, rawYaml);
+ var rawYaml = File.ReadAllText($@"{_invalidPath}{filename}");
+ var result = _yamlValidator.Validate(rawYaml);
Assert.IsFalse(result.SchemaValid);
}
}
diff --git a/src/YamlValidator.Tests/YamlValidator.Tests.csproj b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
new file mode 100644
index 00000000..5cab08d7
--- /dev/null
+++ b/src/YamlValidator.Tests/YamlValidator.Tests.csproj
@@ -0,0 +1,39 @@
+
+
+
+ net8.0
+ Latest
+ enable
+ enable
+
+ false
+ true
+
+ true
+ ../../35MSSharedLib1024.snk
+ true
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ControlWithInvalidProperty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ControlWithInvalidProperty.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ControlWithInvalidProperty.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/ControlWithInvalidProperty.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/Empty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/Empty.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/Empty.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/Empty.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/EmptyArray.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/EmptyArray.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/EmptyArray.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/EmptyArray.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/NamelessObjectNoControl.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/NamelessObjectNoControl.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/NamelessObjectNoControl.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/NamelessObjectNoControl.yaml
diff --git a/src/YamlValidator.Tests/_TestData/InvalidYaml/NotYaml.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/NotYaml.yaml
new file mode 100644
index 00000000..d4dc3834
--- /dev/null
+++ b/src/YamlValidator.Tests/_TestData/InvalidYaml/NotYaml.yaml
@@ -0,0 +1,15 @@
+{
+ features => [
+ {
+ name => lorem ipsum,
+ points => [
+ "bullet 1",
+ "bullet 2"
+ ]
+ },
+ {
+ name => lorem ipsum 2,
+ description => lorem ipsum 3
+ }
+ ]
+}
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithNameNoColon.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoColon.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithNameNoColon.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoColon.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithNameNoValue.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoValue.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithNameNoValue.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithNameNoValue.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithoutControlProperty.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithoutControlProperty.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/ScreenWithoutControlProperty.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/ScreenWithoutControlProperty.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/WrongControlDefinition.yaml b/src/YamlValidator.Tests/_TestData/InvalidYaml/WrongControlDefinition.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/InvalidYaml/WrongControlDefinition.yaml
rename to src/YamlValidator.Tests/_TestData/InvalidYaml/WrongControlDefinition.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/NamelessObjectWithControl.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/NamelessObjectWithControl.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/NamelessObjectWithControl.yaml
rename to src/YamlValidator.Tests/_TestData/ValidYaml/NamelessObjectWithControl.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/SimpleNoRecursiveDefinition.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/SimpleNoRecursiveDefinition.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/SimpleNoRecursiveDefinition.yaml
rename to src/YamlValidator.Tests/_TestData/ValidYaml/SimpleNoRecursiveDefinition.yaml
diff --git a/src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/ValidScreen1.yaml b/src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml
similarity index 100%
rename from src/Persistence.Tests/_TestData/ValidatorTests/ValidYaml/ValidScreen1.yaml
rename to src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml
diff --git a/src/YamlValidator/Constants.cs b/src/YamlValidator/Constants.cs
new file mode 100644
index 00000000..e571e29d
--- /dev/null
+++ b/src/YamlValidator/Constants.cs
@@ -0,0 +1,15 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+public static class Constants
+{
+ public const string YamlFileExtension = ".pa.yaml";
+ public const string JsonFileExtension = ".json";
+
+ public const string notYamlError = "File is not YAML";
+ public const string emptyYamlError = "Empty YAML file";
+
+ public const string subNamespace = "YamlValidator";
+}
diff --git a/src/YamlValidator/Extensions/ServiceCollectionExtensions.cs b/src/YamlValidator/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..a27a804c
--- /dev/null
+++ b/src/YamlValidator/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// registers the MSAPP persistence services
+ ///
+ /// the services collection instance.
+ public static void AddPowerAppsPersistenceYamlValidator(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ }
+}
+
+
diff --git a/src/Persistence/YamlValidator/ValidationRequest.cs b/src/YamlValidator/IValidator.cs
similarity index 62%
rename from src/Persistence/YamlValidator/ValidationRequest.cs
rename to src/YamlValidator/IValidator.cs
index 568b16d8..10008c71 100644
--- a/src/Persistence/YamlValidator/ValidationRequest.cs
+++ b/src/YamlValidator/IValidator.cs
@@ -3,4 +3,7 @@
namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-public readonly record struct ValidationRequest(string FilePath, string FilePathType);
+public interface IValidator
+{
+ ValidatorResults Validate(string yamlString);
+}
diff --git a/src/YamlValidator/IValidatorFactory.cs b/src/YamlValidator/IValidatorFactory.cs
new file mode 100644
index 00000000..ee8721f0
--- /dev/null
+++ b/src/YamlValidator/IValidatorFactory.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+public interface IValidatorFactory
+{
+ IValidator CreateValidator();
+}
diff --git a/src/YamlValidator/InternalsVisibleTo.cs b/src/YamlValidator/InternalsVisibleTo.cs
new file mode 100644
index 00000000..d7c66fc7
--- /dev/null
+++ b/src/YamlValidator/InternalsVisibleTo.cs
@@ -0,0 +1,6 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("YamlValidator.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
diff --git a/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj b/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj
new file mode 100644
index 00000000..23e6f915
--- /dev/null
+++ b/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj
@@ -0,0 +1,38 @@
+
+
+
+ netstandard2.0;net8.0
+ Latest
+ enable
+ enable
+ true
+ ../../35MSSharedLib1024.snk
+ true
+
+
+
+
+
+
+ $(OutDir)\$(TargetFramework)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ schema\%(RecursiveDir)%(Filename)%(Extension)
+
+
+
+
\ No newline at end of file
diff --git a/src/YamlValidator/SchemaLoader.cs b/src/YamlValidator/SchemaLoader.cs
new file mode 100644
index 00000000..540a4685
--- /dev/null
+++ b/src/YamlValidator/SchemaLoader.cs
@@ -0,0 +1,53 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+internal class SchemaLoader
+{
+ private const string _schemaFolderPath = "schema";
+ private const string _subschemaFolderPath = "subschemas";
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Suppress to make classes stateless")]
+ public JsonSchema Load()
+ {
+ var assembly = typeof(SchemaLoader).Assembly;
+
+ JsonSchema? node = null;
+ foreach (var file in assembly.GetManifestResourceNames())
+ {
+ var fileStream = assembly.GetManifestResourceStream(file);
+ var assemblyName = assembly.GetName().Name;
+ if (fileStream == null)
+ {
+ throw new IOException($"Resource {file} could not found in assembly {assemblyName}");
+ }
+ using var streamReader = new StreamReader(fileStream);
+ var jsonSchemaString = streamReader.ReadToEnd();
+ var schema = JsonSchema.FromText(jsonSchemaString);
+
+ // assembly name is Microsoft.PowerPlatform.PowerApps.Persistence
+ // subNamespace is YamlValidator, schemas live in the linked schema folder
+ var rootFileName = $"{assemblyName}.{_schemaFolderPath}";
+
+ if (file.StartsWith($"{rootFileName}.{_subschemaFolderPath}.", StringComparison.Ordinal))
+ {
+ // these virtual uri's are used to resolve $ref's in the schema, they aren't
+ // represented like this in the dll
+ schema.BaseUri = new Uri($"file://{_schemaFolderPath}/{_subschemaFolderPath}/");
+ SchemaRegistry.Global.Register(schema);
+ continue;
+ }
+ schema.BaseUri = new Uri($"file://{_schemaFolderPath}");
+ node = schema;
+ }
+ if (node == null)
+ {
+ throw new InvalidDataException("Schema was not able to be read into memory");
+ }
+ return node;
+ }
+}
+
diff --git a/src/Persistence/YamlValidator/Utility.cs b/src/YamlValidator/Utility.cs
similarity index 73%
rename from src/Persistence/YamlValidator/Utility.cs
rename to src/YamlValidator/Utility.cs
index 7ccc089b..630548c1 100644
--- a/src/Persistence/YamlValidator/Utility.cs
+++ b/src/YamlValidator/Utility.cs
@@ -7,12 +7,6 @@ namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
public class Utility
{
- public static string ReadFileData(string filePath)
- {
- var yamlData = File.ReadAllText(filePath);
- return yamlData;
- }
-
public static YamlStream MakeYamlStream(string yamlString)
{
var stream = new YamlStream();
diff --git a/src/Persistence/YamlValidator/Validator.cs b/src/YamlValidator/Validator.cs
similarity index 55%
rename from src/Persistence/YamlValidator/Validator.cs
rename to src/YamlValidator/Validator.cs
index 06d0c4c7..e3154d2e 100644
--- a/src/Persistence/YamlValidator/Validator.cs
+++ b/src/YamlValidator/Validator.cs
@@ -3,41 +3,43 @@
using Json.Schema;
using Yaml2JsonNode;
-using System.Text.Json;
+using YamlDotNet.Core;
+using YamlDotNet.RepresentationModel;
namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
-public class Validator
+internal class Validator : IValidator
{
private readonly EvaluationOptions _verbosityOptions;
- private readonly JsonSerializerOptions _serializerOptions;
-
- public Validator(EvaluationOptions options, JsonSerializerOptions resultSerializeOptions)
+ private readonly JsonSchema _schema;
+ public Validator(EvaluationOptions options, JsonSchema schema)
{
// to do: add verbosity flag and allow users to choose verbosity of evaluation
_verbosityOptions = options;
- _serializerOptions = resultSerializeOptions;
+ _schema = schema;
}
-
- public ValidatorResults Validate(JsonSchema schema, string yamlFileData)
+ public ValidatorResults Validate(string yamlFileData)
{
- var yamlStream = Utility.MakeYamlStream(yamlFileData);
+ YamlStream yamlStream;
+ try
+ {
+ yamlStream = Utility.MakeYamlStream(yamlFileData);
+ }
+ catch (YamlException)
+ {
+ return new ValidatorResults(false, new List { new(Constants.notYamlError) });
+ }
+
var jsonData = yamlStream.Documents.Count > 0 ? yamlStream.Documents[0].ToJsonNode() : null;
// here we say that empty yaml is serialized as null json
if (jsonData == null)
{
- return new ValidatorResults(false, new List { new("Empty YAML file") });
+ return new ValidatorResults(false, new List { new(Constants.emptyYamlError) });
}
- var results = schema.Evaluate(jsonData, _verbosityOptions);
-
- // not used but may help if we ever need to serialize the evaluation results into json format to feed into
- // a VSCode extension or other tool
- var output = JsonSerializer.Serialize(results, _serializerOptions);
+ var results = _schema.Evaluate(jsonData, _verbosityOptions);
var schemaValidity = results.IsValid;
- // TBD: filter actual errors versus false positives
- // we look for errors that are not valid, have errors, and have an instance location (i.e are not oneOf errors)
var yamlValidatorErrors = new List();
if (!schemaValidity)
{
@@ -46,7 +48,10 @@ public ValidatorResults Validate(JsonSchema schema, string yamlFileData)
node.HasErrors).ToList();
foreach (var trace in traceList)
{
- yamlValidatorErrors.Add(new ValidatorError(trace));
+ var instanceLocation = trace.InstanceLocation.ToString();
+ var schemaPath = trace.EvaluationPath.ToString();
+ var errors = trace.Errors;
+ yamlValidatorErrors.Add(new ValidatorError(instanceLocation, schemaPath, errors));
}
}
diff --git a/src/Persistence/YamlValidator/ValidatorError.cs b/src/YamlValidator/ValidatorError.cs
similarity index 81%
rename from src/Persistence/YamlValidator/ValidatorError.cs
rename to src/YamlValidator/ValidatorError.cs
index 0ced0cfb..32f9b5ef 100644
--- a/src/Persistence/YamlValidator/ValidatorError.cs
+++ b/src/YamlValidator/ValidatorError.cs
@@ -1,20 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
-using Json.Schema;
-
namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
public class ValidatorError
{
public string InstanceLocation { get; }
public string SchemaPath { get; }
public IReadOnlyDictionary? Errors { get; }
- public ValidatorError(EvaluationResults results)
+ public ValidatorError(string instancePath, string schemaPath, IReadOnlyDictionary? errors)
{
- InstanceLocation = results.InstanceLocation.ToString();
- SchemaPath = results.EvaluationPath.ToString();
- Errors = results.Errors;
+ InstanceLocation = instancePath;
+ SchemaPath = schemaPath;
+ Errors = errors;
}
public ValidatorError(string error)
diff --git a/src/YamlValidator/ValidatorFactory.cs b/src/YamlValidator/ValidatorFactory.cs
new file mode 100644
index 00000000..d90e908e
--- /dev/null
+++ b/src/YamlValidator/ValidatorFactory.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using Json.Schema;
+
+namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
+
+internal class ValidatorFactory : IValidatorFactory
+{
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "required by IValidatorFactory interface")]
+ public IValidator CreateValidator()
+ {
+ // register schema in from memory into global schema registry
+ var schemaLoader = new SchemaLoader();
+ var serializedSchema = schemaLoader.Load();
+
+ var evalOptions = new EvaluationOptions
+ {
+ OutputFormat = OutputFormat.List
+ };
+
+ return new Validator(evalOptions, serializedSchema);
+ }
+}
diff --git a/src/Persistence/YamlValidator/ValidatorResults.cs b/src/YamlValidator/ValidatorResults.cs
similarity index 94%
rename from src/Persistence/YamlValidator/ValidatorResults.cs
rename to src/YamlValidator/ValidatorResults.cs
index e7565148..f3b9e221 100644
--- a/src/Persistence/YamlValidator/ValidatorResults.cs
+++ b/src/YamlValidator/ValidatorResults.cs
@@ -15,6 +15,7 @@ public ValidatorResults(bool schemaValid, IReadOnlyList traversa
TraversalResults = FilterErrors(traversalResults);
}
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Suppress to make classes stateless")]
// This will filter out the false positives that are not relevant to the error output, when the validation is false
private ReadOnlyCollection FilterErrors(IReadOnlyList traversalResults)
{