From 5a29cd5e9806ccd2dea40d60709d44521c928eee Mon Sep 17 00:00:00 2001 From: Joe Mayo Date: Mon, 4 May 2026 20:36:47 -0700 Subject: [PATCH] Upgrade YamlDotNet to 17.1.0 --- src/Directory.Packages.props | 36 ++-- ...rosoft.PowerPlatform.Formulas.Tools.csproj | 9 +- src/PAModel/packages.lock.json | 127 ++++++------ src/PAModelTests/PAModelTests.csproj | 2 - .../Serialization/PaYamlSerializerTests.cs | 43 ++++ .../PaYamlSerializationContext.cs | 9 + .../PaYaml/Serialization/PaYamlSerializer.cs | 4 +- .../Serialization/PaYamlSerializerOptions.cs | 6 + src/Persistence/PersistenceErrorCode.cs | 6 + src/Persistence/packages.lock.json | 185 ++++++++++-------- ...werPlatform.PowerApps.YamlValidator.csproj | 3 +- src/YamlValidator/SchemaLoader.cs | 18 +- src/YamlValidator/Validator.cs | 26 ++- 13 files changed, 278 insertions(+), 196 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index daf3c7fd..eb54de4c 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,10 +4,17 @@ true + + + + - + + + + - - - - + + + + - - - - - - - - - - - + + + + + + + + diff --git a/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj b/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj index 0e9565bf..0f0b6f40 100644 --- a/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj +++ b/src/PAModel/Microsoft.PowerPlatform.Formulas.Tools.csproj @@ -21,12 +21,15 @@ - - + + + + + + - diff --git a/src/PAModel/packages.lock.json b/src/PAModel/packages.lock.json index 7b4250db..09f295cd 100644 --- a/src/PAModel/packages.lock.json +++ b/src/PAModel/packages.lock.json @@ -16,82 +16,93 @@ }, "System.Text.Encodings.Web": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "WUH+viO8VDG8NpFKvOBwpeyKUiPOMz3kQpA6AKCD4b2NG1pBhyC4AwTb357iZmTxZDnkM4IsFnvzN8W8OKmsHg==", "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "F8Pu2QLUMeniVbtiyk7n7LCfFYxlcJ8ASaSwglJyq6dxa34iCQrikQszsgJClIJWuSWjcyhKkV7daAzYJqeVwA==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4", - "System.ValueTuple": "4.5.0" + "Microsoft.Bcl.AsyncInterfaces": "10.0.7", + "System.Buffers": "4.6.1", + "System.IO.Pipelines": "10.0.7", + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", + "System.Text.Encodings.Web": "10.0.7", + "System.Threading.Tasks.Extensions": "4.6.3", + "System.ValueTuple": "4.6.2" } }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "resolved": "10.0.7", + "contentHash": "g0Xp9A+B8jCf5pNIIhFOQXPJkte3D87shfTLY+ylwfSh22U5oQH6tvvmcUuqJvt/wtwKk0WdNp2OGEczHJlJdg==", "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + "resolved": "4.6.1", + "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "LTxXYYKmRhPKWveYmfzuRTUnzsfY7CN+WOq6aTRgYE9vJ8BUvIWPCaSx4HxqBwXViTPSjR9cHDOVuVPuZGRR/Q==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3", + "System.Threading.Tasks.Extensions": "4.6.3" + } }, "System.Numerics.Vectors": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + "resolved": "4.6.1", + "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" }, "System.ValueTuple": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + "resolved": "4.6.2", + "contentHash": "yQgmjfFximrNm9LIV3mL6T5MzjeC+epeE5rl4hXxAlYmxby7RM1dPSkIKXk9HNkl6G54h2JHOmLD46+Pey+IRg==" }, "System.Memory": { "type": "CentralTransitive", - "requested": "[4.5.5, )", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "requested": "[4.6.3, )", + "resolved": "4.6.3", + "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Buffers": "4.6.1", + "System.Numerics.Vectors": "4.6.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.Threading.Tasks.Extensions": { "type": "CentralTransitive", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "requested": "[4.6.3, )", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } } }, @@ -102,23 +113,11 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Text.Encodings.Web": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, - "System.Text.Json": { - "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" - }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" } }, "net8.0": { @@ -128,23 +127,11 @@ "resolved": "13.0.3", "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" }, - "System.Text.Encodings.Web": { - "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" - }, - "System.Text.Json": { - "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==" - }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" } } } diff --git a/src/PAModelTests/PAModelTests.csproj b/src/PAModelTests/PAModelTests.csproj index 4f7c2ed1..840c197f 100644 --- a/src/PAModelTests/PAModelTests.csproj +++ b/src/PAModelTests/PAModelTests.csproj @@ -9,8 +9,6 @@ - - diff --git a/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs b/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs index b64ec39e..d99c5449 100644 --- a/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs +++ b/src/Persistence.Tests/PaYaml/Serialization/PaYamlSerializerTests.cs @@ -304,4 +304,47 @@ public void SerializeComponentCustomPropertyUnionPropertyOrder() Default: =true """.Replace("\r\n", "\n")); } + + [TestMethod] + public void DeserializeRespectsConfiguredMaximumRecursion() + { + // Arrange: build a deeply-nested mapping that exceeds the configured recursion limit. + var options = new PaYamlSerializerOptions { MaximumRecursion = 5 }; + var yaml = BuildNestedMappingYaml(depth: 20); + + // Act + Action act = () => PaYamlSerializer.Deserialize>(yaml, options); + + // Assert: the exception is wrapped as PersistenceLibraryException with the + // YamlDotNet MaximumRecursionLevelReachedException as the inner exception. + act.Should().Throw() + .WithErrorCode(PersistenceErrorCode.MaximumRecursionLevelReached) + .WithInnerException(); + } + + [TestMethod] + [DataRow(null)] + [DataRow(55)] + public void DeserializeSucceedsWhenMaximumRecursionRaisedOrNull(int? maxRecursion) + { + // Arrange: same depth-20 input that fails with default recursion, raised limit. + var options = new PaYamlSerializerOptions { MaximumRecursion = maxRecursion }; + var yaml = BuildNestedMappingYaml(depth: 20); + + // Act + FluentActions.Invoking(() => PaYamlSerializer.Deserialize>(yaml, options)) + .Should().NotThrow() + .Which.Should().NotBeNull(); + } + + private static string BuildNestedMappingYaml(int depth) + { + var sb = new System.Text.StringBuilder(); + for (var i = 0; i < depth; i++) + { + sb.Append(new string(' ', i * 2)).Append("k:").AppendLine(); + } + sb.Append(new string(' ', depth * 2)).Append("k: leaf").AppendLine(); + return sb.ToString(); + } } diff --git a/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs b/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs index e1d0ac6b..4a3773c4 100644 --- a/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs +++ b/src/Persistence/PaYaml/Serialization/PaYamlSerializationContext.cs @@ -13,6 +13,10 @@ public class PaYamlSerializationContext : IDisposable public PaYamlSerializationContext(PaYamlSerializerOptions options) { Options = options ?? throw new ArgumentNullException(nameof(options)); + if (Options.MaximumRecursion.HasValue && Options.MaximumRecursion.Value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(options), "MaximumRecursion must be an integer greater than 0 or null."); + } } /// @@ -27,6 +31,11 @@ internal void ApplyToDeserializerBuilder(DeserializerBuilder builder) .IgnoreFields() ; + if (Options.MaximumRecursion.HasValue) + { + builder.WithMaximumRecursion(Options.MaximumRecursion.Value); + } + AddTypeConverters(builder); Options.AdditionalDeserializerConfiguration?.Invoke(builder); } diff --git a/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs b/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs index c02873c8..fc64811f 100644 --- a/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs +++ b/src/Persistence/PaYaml/Serialization/PaYamlSerializer.cs @@ -114,7 +114,9 @@ private static void WriteTextWriter(TextWriter writer, in TValue? value, catch (YamlException ex) { var errorCode = PersistenceErrorCode.YamlInvalidSyntax; - if (ex.InnerException is ArgumentException && ex.InnerException.Message.Contains("An item with the same key has already been added")) + if (ex is MaximumRecursionLevelReachedException) + errorCode = PersistenceErrorCode.MaximumRecursionLevelReached; + else if (ex.InnerException is ArgumentException && ex.InnerException.Message.Contains("An item with the same key has already been added")) errorCode = PersistenceErrorCode.DuplicateNameInSequence; throw PersistenceLibraryException.FromYamlException(ex, errorCode); diff --git a/src/Persistence/PaYaml/Serialization/PaYamlSerializerOptions.cs b/src/Persistence/PaYaml/Serialization/PaYamlSerializerOptions.cs index f5e3d4f1..d0ee5e89 100644 --- a/src/Persistence/PaYaml/Serialization/PaYamlSerializerOptions.cs +++ b/src/Persistence/PaYaml/Serialization/PaYamlSerializerOptions.cs @@ -11,6 +11,12 @@ public record PaYamlSerializerOptions public string NewLine { get; init; } = "\n"; + /// + /// Maximum recursion depth allowed during deserialization. + /// Bounds pathological/untrusted YAML input. + /// + public int? MaximumRecursion { get; init; } + public PFxExpressionYamlFormattingOptions PFxExpressionYamlFormatting { get; init; } = new(); public Action? AdditionalDeserializerConfiguration { get; init; } diff --git a/src/Persistence/PersistenceErrorCode.cs b/src/Persistence/PersistenceErrorCode.cs index 61220186..795929fc 100644 --- a/src/Persistence/PersistenceErrorCode.cs +++ b/src/Persistence/PersistenceErrorCode.cs @@ -22,6 +22,12 @@ public enum PersistenceErrorCode // 3xxx - Deserialization errors DeserializationError = 3000, YamlInvalidSyntax = 3001, + /// + /// Indicates that the deserializer exceeded the maximum recursion depth when processing the YAML document. + /// This can be caused by excessively deep or circular references in the object graph represented by the YAML. + /// The default maximum recursion depth can be configured through . + /// + MaximumRecursionLevelReached = 3002, YamlInvalidSchema = 3101, EditorStateJsonEmptyOrNull = 3102, InvalidEditorStateJson = 3300, diff --git a/src/Persistence/packages.lock.json b/src/Persistence/packages.lock.json index 1b6927cf..afe64179 100644 --- a/src/Persistence/packages.lock.json +++ b/src/Persistence/packages.lock.json @@ -4,39 +4,40 @@ ".NETFramework,Version=v4.8": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" + "Microsoft.Bcl.AsyncInterfaces": "10.0.7", + "System.Threading.Tasks.Extensions": "4.6.3" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[8.0.3, )", - "resolved": "8.0.3", - "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "System.Buffers": "4.6.1", + "System.Diagnostics.DiagnosticSource": "10.0.7", + "System.Memory": "4.6.3" } }, "PolySharp": { "type": "Direct", - "requested": "[1.14.1, )", - "resolved": "1.14.1", - "contentHash": "mOOmFYwad3MIOL14VCjj02LljyF1GNw1wP0YVlxtcPvqdxjGGMNdNJJxHptlry3MOd8b40Flm8RPOM8JOlN2sQ==" + "requested": "[1.15.0, )", + "resolved": "1.15.0", + "contentHash": "FbU0El+EEjdpuIX4iDbeS7ki1uzpJPx8vbqOzEtqnl1GZeAGJfq+jCbxeJL2y0EPnUNk8dRnnqR2xnYXg9Tf+g==" }, "System.Collections.Immutable": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "0Ti4Jv1ga3eurH5HaCVsPybcBl+08YfzM9smqAJzHvqV494xK+0pSbytGrMTWhph+zsyKIaoGNiR5u3by5bj+A==", "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.IO.Compression": { @@ -47,129 +48,155 @@ }, "System.Memory": { "type": "Direct", - "requested": "[4.5.5, )", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "requested": "[4.6.3, )", + "resolved": "4.6.3", + "contentHash": "qdcDOgnFZY40+Q9876JUHnlHu7bosOHX8XISRoH94fwk6hgaeQGSgfZd8srWRZNt5bV9ZW2TljcegDNxsf+96A==", "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Buffers": "4.6.1", + "System.Numerics.Vectors": "4.6.1", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "System.Text.Json": { "type": "Direct", - "requested": "[8.0.5, )", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "F8Pu2QLUMeniVbtiyk7n7LCfFYxlcJ8ASaSwglJyq6dxa34iCQrikQszsgJClIJWuSWjcyhKkV7daAzYJqeVwA==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4", - "System.ValueTuple": "4.5.0" + "Microsoft.Bcl.AsyncInterfaces": "10.0.7", + "System.Buffers": "4.6.1", + "System.IO.Pipelines": "10.0.7", + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2", + "System.Text.Encodings.Web": "10.0.7", + "System.Threading.Tasks.Extensions": "4.6.3", + "System.ValueTuple": "4.6.2" } }, "System.Threading.Tasks.Extensions": { "type": "Direct", - "requested": "[4.5.4, )", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "requested": "[4.6.3, )", + "resolved": "4.6.3", + "contentHash": "7sCiwilJLYbTZELaKnc7RecBBXWXA+xMLQWZKWawBxYjp6DBlSE3v9/UcvKBvr1vv2tTOhipiogM8rRmxlhrVA==", "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "resolved": "10.0.7", + "contentHash": "g0Xp9A+B8jCf5pNIIhFOQXPJkte3D87shfTLY+ylwfSh22U5oQH6tvvmcUuqJvt/wtwKk0WdNp2OGEczHJlJdg==", "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" + "System.Threading.Tasks.Extensions": "4.6.3" } }, "System.Buffers": { "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + "resolved": "4.6.1", + "contentHash": "N8GXpmiLMtljq7gwvyS+1QvKT/W2J8sNAvx+HVg4NGmsG/H+2k/y9QI23auLJRterrzCiDH+IWAw4V/GPwsMlw==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Fu6AxFf9bHz/Q7DQmxKC0o+UgFes8bs2Xh+PH/x31yExRAOASTwlzjZsISTtqVU5gQshKHLZopxEBTaIyfv0wg==", + "dependencies": { + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "LTxXYYKmRhPKWveYmfzuRTUnzsfY7CN+WOq6aTRgYE9vJ8BUvIWPCaSx4HxqBwXViTPSjR9cHDOVuVPuZGRR/Q==", + "dependencies": { + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3", + "System.Threading.Tasks.Extensions": "4.6.3" + } }, "System.Numerics.Vectors": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + "resolved": "4.6.1", + "contentHash": "sQxefTnhagrhoq2ReR0D/6K0zJcr9Hrd6kikeXsA1I8kOCboTavcUC4r7TSfpKFeE163uMuxZcyfO1mGO3EN8Q==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + "resolved": "6.1.2", + "contentHash": "2hBr6zdbIBTDE3EhK7NSVNdX58uTK6iHW/P/Axmm9sl1xoGSLqDvMtpecn226TNwHByFokYwJmt/aQQNlO5CRw==" }, "System.ValueTuple": { "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + "resolved": "4.6.2", + "contentHash": "yQgmjfFximrNm9LIV3mL6T5MzjeC+epeE5rl4hXxAlYmxby7RM1dPSkIKXk9HNkl6G54h2JHOmLD46+Pey+IRg==" }, "System.Text.Encodings.Web": { "type": "CentralTransitive", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "WUH+viO8VDG8NpFKvOBwpeyKUiPOMz3kQpA6AKCD4b2NG1pBhyC4AwTb357iZmTxZDnkM4IsFnvzN8W8OKmsHg==", "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" + "System.Buffers": "4.6.1", + "System.Memory": "4.6.3", + "System.Runtime.CompilerServices.Unsafe": "6.1.2" } } }, "net10.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[8.0.3, )", - "resolved": "8.0.3", - "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7" } }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" } }, "net8.0": { "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Direct", - "requested": "[8.0.2, )", - "resolved": "8.0.2", - "contentHash": "3iE7UF7MQkCv1cxzCahz+Y/guQbTqieyxyaWKhrRO91itI9cOKO76OHeQDahqG4MmW5umr3CcCvGmK92lWNlbg==" + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw==" }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Direct", - "requested": "[8.0.3, )", - "resolved": "8.0.3", - "contentHash": "dL0QGToTxggRLMYY4ZYX5AMwBb+byQBd/5dMiZE07Nv73o6I5Are3C7eQTh7K2+A4ct0PVISSr7TZANbiNb2yQ==", + "requested": "[10.0.7, )", + "resolved": "10.0.7", + "contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2" + "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7", + "System.Diagnostics.DiagnosticSource": "10.0.7" } }, "YamlDotNet": { "type": "Direct", - "requested": "[16.1.3, )", - "resolved": "16.1.3", - "contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ==" + "requested": "[17.1.0, )", + "resolved": "17.1.0", + "contentHash": "AhsNXgeAs3Ugt653t8LC44xXDuldFfwBpWbWX9pN3e4Yg8U5Bk8jLn8eXtGv5HV2V2nHu7F46fqsPC+tpcTGAA==" + }, + "System.Diagnostics.DiagnosticSource": { + "type": "Transitive", + "resolved": "10.0.7", + "contentHash": "Fu6AxFf9bHz/Q7DQmxKC0o+UgFes8bs2Xh+PH/x31yExRAOASTwlzjZsISTtqVU5gQshKHLZopxEBTaIyfv0wg==" } } } diff --git a/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj b/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj index 17a85fcf..e47c681c 100644 --- a/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj +++ b/src/YamlValidator/Microsoft.PowerPlatform.PowerApps.YamlValidator.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net8.0 + net48;net8.0 Latest enable enable @@ -19,7 +19,6 @@ - diff --git a/src/YamlValidator/SchemaLoader.cs b/src/YamlValidator/SchemaLoader.cs index 85e3e4fc..e67cce36 100644 --- a/src/YamlValidator/SchemaLoader.cs +++ b/src/YamlValidator/SchemaLoader.cs @@ -14,18 +14,12 @@ internal class SchemaLoader public JsonSchema Load() { var assembly = typeof(SchemaLoader).Assembly; + var assemblyName = assembly.GetName().Name; JsonSchema? node = null; foreach (var file in assembly.GetManifestResourceNames()) { - var fileStream = assembly.GetManifestResourceStream(file); - var assemblyName = assembly.GetName().Name; - if (fileStream == null) - { - throw new YamlValidatorLibraryException($"The schema could not be loaded from assembly."); - } - using var streamReader = new StreamReader(fileStream); - var jsonSchemaString = streamReader.ReadToEnd(); + string jsonSchemaString = ReadSchemaFromManifestFile(assembly, file); var schema = JsonSchema.FromText(jsonSchemaString); // assembly name is Microsoft.PowerPlatform.PowerApps.Persistence @@ -48,6 +42,14 @@ public JsonSchema Load() throw new YamlValidatorLibraryException("The schema could not be serialized from the assembly."); } return node; + + static string ReadSchemaFromManifestFile(System.Reflection.Assembly assembly, string file) + { + var fileStream = assembly.GetManifestResourceStream(file) + ?? throw new YamlValidatorLibraryException($"The schema could not be loaded from assembly."); + using var streamReader = new StreamReader(fileStream); + return streamReader.ReadToEnd(); + } } } diff --git a/src/YamlValidator/Validator.cs b/src/YamlValidator/Validator.cs index e3154d2e..7042dd13 100644 --- a/src/YamlValidator/Validator.cs +++ b/src/YamlValidator/Validator.cs @@ -12,12 +12,14 @@ internal class Validator : IValidator { private readonly EvaluationOptions _verbosityOptions; 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; _schema = schema; } + public ValidatorResults Validate(string yamlFileData) { YamlStream yamlStream; @@ -27,36 +29,30 @@ public ValidatorResults Validate(string yamlFileData) } catch (YamlException) { - return new ValidatorResults(false, new List { new(Constants.notYamlError) }); + return new ValidatorResults(false, [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) + if (yamlStream.Documents.Count <= 0) { - return new ValidatorResults(false, new List { new(Constants.emptyYamlError) }); + return new(false, [new(Constants.emptyYamlError)]); } + + var jsonData = yamlStream.Documents[0].ToJsonNode(); var results = _schema.Evaluate(jsonData, _verbosityOptions); - var schemaValidity = results.IsValid; var yamlValidatorErrors = new List(); - if (!schemaValidity) + if (!results.IsValid && results.Details is not null) { - IReadOnlyList traceList = results.Details.Where( - node => !node.IsValid && - node.HasErrors).ToList(); + var traceList = results.Details.Where(node => !node.IsValid); foreach (var trace in traceList) { var instanceLocation = trace.InstanceLocation.ToString(); var schemaPath = trace.EvaluationPath.ToString(); - var errors = trace.Errors; - yamlValidatorErrors.Add(new ValidatorError(instanceLocation, schemaPath, errors)); + yamlValidatorErrors.Add(new(instanceLocation, schemaPath, trace.Errors)); } } - IReadOnlyList fileErrors = yamlValidatorErrors; - var finalResults = new ValidatorResults(results.IsValid, fileErrors); - return finalResults; + return new(results.IsValid, yamlValidatorErrors); } }