Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Yaml Validator Iteration One into Main Branch #677

Merged
merged 12 commits into from
Jun 25, 2024
2 changes: 1 addition & 1 deletion .version/PipelineAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
using System.Reflection;
[assembly: AssemblyVersion("0.0.0.0")]
[assembly: AssemblyFileVersion("0.0.0.0")]
[assembly: AssemblyInformationalVersion("0.0.0.0-dev-00000000")]
[assembly: AssemblyInformationalVersion("0.0.0.0-dev-00000000")]
2 changes: 1 addition & 1 deletion docs/pa.yaml-schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/pa.yaml-schema.json",
"$id": "pa.yaml-schema.json",
"title": "Microsoft Power Apps",
"description": "Canvas YAML",
"oneOf": [
Expand Down
2 changes: 1 addition & 1 deletion docs/subschemas/control-property-schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/control-type-schema.json",
"$id": "control-property-schema.json",
"title": "Microsoft Power Apps Properties",
"description": "The properties of the control",
"type": "object",
Expand Down
2 changes: 1 addition & 1 deletion docs/subschemas/control-type-schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://raw.githubusercontent.com/microsoft/PowerApps-Tooling/master/docs/control-type-schema.json",
"$id": "control-type-schema.json",
"title": "Microsoft Power Apps Control Type",
"description": "The type of the control",
"type": "string",
Expand Down
13 changes: 13 additions & 0 deletions src/PASopa.sln
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.PowerPlatform.Pow
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Persistence.Tests", "Persistence.Tests\Persistence.Tests.csproj", "{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlValidator", "YamlValidator\YamlValidator.csproj", "{F0AD11CE-E634-4945-A6B1-7866CDE0059C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YamlValidator.Tests", "YamlValidator.Tests\YamlValidator.Tests.csproj", "{8BA5DD4B-9423-4827-AF37-540E0300DB9A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7361DB16-D534-4E0E-8597-BE22317DEF47}"
ProjectSection(SolutionItems) = preProject
Directory.Build.props = Directory.Build.props
Expand Down Expand Up @@ -52,13 +56,22 @@ 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
{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F0AD11CE-E634-4945-A6B1-7866CDE0059C}.Release|Any CPU.Build.0 = Release|Any CPU
{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BA5DD4B-9423-4827-AF37-540E0300DB9A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{8AD94CC0-7330-4880-A8E0-177B37CDB27B} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
{8AB1C901-FE5E-44BF-AA21-B8F20A9D7CDD} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
{8BA5DD4B-9423-4827-AF37-540E0300DB9A} = {4993E606-484B-46D9-892E-7AE9CE8D4423}
{7361DB16-D534-4E0E-8597-BE22317DEF47} = {794D8C68-BF6F-49C8-BCA5-AA52D8F45EF4}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
Expand Down
46 changes: 46 additions & 0 deletions src/YamlValidator.Tests/ValidatorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Json.Schema;
using Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;

namespace Persistence.Tests.YamlValidator;

[TestClass]
public class ValidatorTest
{
private const string _validPath = @".\_TestData\ValidYaml";
private const string _invalidPath = @".\_TestData\InvalidYaml";
private const string _schemaPath = @"..\YamlValidator\schema\pa.yaml-schema.json";

private readonly JsonSchema _schema;
private readonly Validator _yamlValidator;

public ValidatorTest()
{
var schemaFileLoader = new SchemaLoader();
_schema = schemaFileLoader.Load(_schemaPath);
var resultVerbosity = new VerbosityData(Constants.Verbose);
_yamlValidator = new Validator(resultVerbosity.EvalOptions, resultVerbosity.JsonOutputOptions);
}

[TestMethod]
[DataRow($@"{_invalidPath}\ScreenWithNameNoColon.yaml", false)]
[DataRow($@"{_invalidPath}\ScreenWithNameNoValue.yaml", false)]
[DataRow($@"{_invalidPath}\ScreenWithoutControlProperty.yaml", false)]
[DataRow($@"{_invalidPath}\WrongControlDefinition.yaml", false)]
[DataRow($@"{_invalidPath}\ControlWithInvalidProperty.yaml", false)]
[DataRow($@"{_invalidPath}\EmptyArray.yaml", false)]
[DataRow($@"{_invalidPath}\Empty.yaml", false)]
[DataRow($@"{_invalidPath}\NamelessObjectNoControl.yaml", false)]
[DataRow($@"{_validPath}\NamelessObjectWithControl.yaml", true)]
[DataRow($@"{_validPath}\ValidScreen1.yaml", true)]
[DataRow($@"{_validPath}\SimpleNoRecursiveDefinition.yaml", true)]

public void TestValidation(string filepath, bool expectedResult)
{
var rawYaml = Utility.ReadFileData($@"{filepath}");
var result = _yamlValidator.Validate(_schema, rawYaml);
Assert.IsTrue(result.SchemaValid == expectedResult);
}
}
34 changes: 34 additions & 0 deletions src/YamlValidator.Tests/YamlValidator.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<None Include="_TestData\**\*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0-preview.5.24306.7" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\YamlValidator\YamlValidator.csproj" />
</ItemGroup>


<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Screen2:
Control: Screen
InvalidProperty: "invalid"
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-
-
-
-
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test:
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Screen:
ComponentName: "test"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TestScreen:
"abcd"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:
Control: "bdbd"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
LoginPage:
Control: "Button"
30 changes: 30 additions & 0 deletions src/YamlValidator.Tests/_TestData/ValidYaml/ValidScreen1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Screen2:
Control: Screen
Children:
- ButtonCanvas2:
Control: Button
Properties:
OnSelect: =Navigate(Screen1)
Text: ="Back"
Height: =53
Width: =172
X: =632
Y: =550
- TextCanvas1:
Control: Text
Properties:
Align: ='TextCanvas.Align'.Center
Size: =50
Text: ="Hello"
Height: =91
Width: =368
X: =517
Y: =44
- Image1:
Control: Image
Properties:
Image: ='pexels-pixabay-417173'
Height: =361
Width: =466
X: =447
Y: =135
14 changes: 14 additions & 0 deletions src/YamlValidator/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
public class Constants
{
public const string FileTypeName = "file";
public const string FolderTypeName = "folder";
public const string YamlFileExtension = ".yaml";
public const string YmlFileExtension = ".yml";
public const string JsonFileExtension = ".json";

public const string Verbose = "verbose";
}
109 changes: 109 additions & 0 deletions src/YamlValidator/InputProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
public class InputProcessor
{

private static void ProcessFiles(string path, string schema, string pathType)
{
// read only records
var filePathInfo = new ValidationRequest(path, schema, pathType);
var verbosityInfo = new VerbosityData(Constants.Verbose);

var validator = new Validator(verbosityInfo.EvalOptions, verbosityInfo.JsonOutputOptions);
var schemaLoader = new SchemaLoader();
var fileLoader = new YamlLoader();
var orchestrator = new Orchestrator(fileLoader, schemaLoader, validator);
orchestrator.RunValidation(filePathInfo);
}
public static RootCommand GetRootCommand()
{

var pathOption = new Option<string>(
name: "--path",
description: "The path to the input yaml file or directory of yaml files"
)
{ IsRequired = true };

pathOption.AddValidator(result =>
{
var inputFilePath = result.GetValueForOption(pathOption);

// either file or folder must be passed
var pathType = string.Empty;
if (string.IsNullOrEmpty(inputFilePath))
{
result.ErrorMessage = "The input is invalid, input must be a filepath to a yaml file \\" +
"or a folder path to a folder of yaml files";
}
else if (!Directory.Exists(inputFilePath) && !File.Exists(inputFilePath))
{
result.ErrorMessage = "The input path does not exist";
}
else if (Directory.Exists(inputFilePath))
{
if (Directory.GetFiles(inputFilePath, $"*{Constants.YamlFileExtension}").Length == 0)
{
result.ErrorMessage = "The input folder does not contain any yaml files";
}
}
else if (File.Exists(inputFilePath))
{
if (Path.GetExtension(inputFilePath) != Constants.YamlFileExtension)
{
result.ErrorMessage = "The input file must be a yaml file";
}
}
});

// assume local schema file exists in nuget package, use relative filepath for now
var schemaOption = new Option<string>(
name: "--schema",
description: "The path to the schema json file",
getDefaultValue: () => @".\schema\pa.yaml-schema.json"
);

schemaOption.AddValidator(result =>
{
var schemaPath = result.GetValueForOption(schemaOption);
if (string.IsNullOrEmpty(schemaPath))
{
result.ErrorMessage = "Schema option selected, but no schema was provided";
}
else if (Path.GetExtension(schemaPath) != Constants.JsonFileExtension)
{
result.ErrorMessage = "The schema file must be a json file";
}
else if (!File.Exists(schemaPath))
{
result.ErrorMessage = "The schema file does not exist";
}
});

// define root
var rootCommand = new RootCommand("YAML validator cli-tool");

// validate command
var validateCommand = new Command("validate", "Validate the input yaml file")
{
pathOption,
schemaOption
};

validateCommand.SetHandler((pathOptionVal, schemaOptionVal) =>
{
var pathType = File.GetAttributes(pathOptionVal).HasFlag(FileAttributes.Directory) ? Constants.FolderTypeName :
Constants.FileTypeName;
ProcessFiles(pathOptionVal, schemaOptionVal, pathType);

}, pathOption, schemaOption);

rootCommand.AddCommand(validateCommand);

return rootCommand;

}
}
42 changes: 42 additions & 0 deletions src/YamlValidator/Orchestrator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;
public class Orchestrator
{
private readonly YamlLoader _fileLoader;
private readonly SchemaLoader _schemaLoader;
private readonly Validator _validator;

public Orchestrator(YamlLoader fileLoader, SchemaLoader schemaLoader, Validator validator)
{
_fileLoader = fileLoader;
_schemaLoader = schemaLoader;
_validator = validator;
}

public void RunValidation(ValidationRequest inputData)
{
var schemaPath = inputData.SchemaPath;
var path = inputData.FilePath;
var pathType = inputData.FilePathType;

var yamlData = _fileLoader.Load(path, pathType);
var serializedSchema = _schemaLoader.Load(schemaPath);

foreach (var yamlFileData in yamlData)
{
Console.WriteLine($"Validation for {yamlFileData.Key}");
var result = _validator.Validate(serializedSchema, yamlFileData.Value);
Console.WriteLine($"Validation Result: {result.SchemaValid}");
foreach (var error in result.TraversalResults)
{
Console.WriteLine($"{error}");
}
Console.WriteLine();
}
}



}
15 changes: 15 additions & 0 deletions src/YamlValidator/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.YamlValidator;

public class Program
{
private static void Main(string[] args)
{
var inputProcessor = InputProcessor.GetRootCommand();
inputProcessor.Invoke(args);
}
}
Loading
Loading