Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<PackageVersion Include="System.Text.Encodings.Web" Version="8.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageVersion Include="Yaml2JsonNode" Version="2.1.1" />
<PackageVersion Include="YamlDotNet" Version="15.1.6" />
<PackageVersion Include="YamlDotNet" Version="16.1.3" />

</ItemGroup>
</Project>
18 changes: 9 additions & 9 deletions src/PAModel/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
},
"YamlDotNet": {
"type": "Direct",
"requested": "[15.1.6, )",
"resolved": "15.1.6",
"contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
},
"Microsoft.Bcl.AsyncInterfaces": {
"type": "Transitive",
Expand Down Expand Up @@ -116,9 +116,9 @@
},
"YamlDotNet": {
"type": "Direct",
"requested": "[15.1.6, )",
"resolved": "15.1.6",
"contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
},
"net8.0": {
Expand All @@ -142,9 +142,9 @@
},
"YamlDotNet": {
"type": "Direct",
"requested": "[15.1.6, )",
"resolved": "15.1.6",
"contentHash": "T/cQEK/KHK96Q8kytJ4iUGDXg1/fj2Qtk6rCQeIlHYU1zTeyGVHW0QNZgREQyxZpygGMDMmrXNWt0sj5TsQnjA=="
"requested": "[16.1.3, )",
"resolved": "16.1.3",
"contentHash": "gtHGiDvU9VTtWte8f0thIM38cL1oowOjStKpeAEKKfA+Rc4AvekJzqFDZiiPcc4kw00ZiwR4OTJS56L16q98DQ=="
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,26 @@
// Licensed under the MIT License.

using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
using YamlDotNet.Serialization;

namespace Persistence.Tests.PaYaml.Serialization;

[TestClass]
public class NamedObjectMappingSerializationTests : SerializationTestBase
{
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetDeserializer(builder, context);
builder.WithTypeConverter(new NamedObjectMappingYamlConverter());
}

protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetSerializer(builder, context);
builder.WithTypeConverter(new NamedObjectMappingYamlConverter());
}

[TestMethod]
// Null literals
[DataRow("TheMapping: ~", null)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public class NamedObjectSequenceSerializationTests : SerializationTestBase
protected override void ConfigureYamlDotNetDeserializer(DeserializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetDeserializer(builder, context);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(context));
builder.WithTypeConverter(new NamedObjectYamlConverter<string>());
}

protected override void ConfigureYamlDotNetSerializer(SerializerBuilder builder, PaYamlSerializationContext context)
{
base.ConfigureYamlDotNetSerializer(builder, context);
builder.WithTypeConverter(new NamedObjectYamlConverter<string>(context));
builder.WithTypeConverter(new NamedObjectYamlConverter<string>());
}

[TestMethod]
Expand Down Expand Up @@ -75,7 +75,7 @@ public void ReadYamlMappingSetsNamedObjectStart()
var testObject = DeserializeViaYamlDotNet<TestOM<string>>(yaml);
testObject.ShouldNotBeNull();
testObject.TheSequence.ShouldNotBeNull();
testObject.TheSequence.Names.Should().Equal(new[] { "n1", "n3", "n2" }, "ordering of a sequence is by code order");
testObject.TheSequence.Names.Should().Equal(["n1", "n3", "n2"], "ordering of a sequence is by code order");
testObject.TheSequence.GetNamedObject("n1").Should()
.HaveValueEqual("v1")
.And.HaveStartEqual(2, 5);
Expand All @@ -92,7 +92,7 @@ public void SerializeAsPropertyBeingNullOrEmpty()
{
SerializeViaYamlDotNet(new TestOM<string> { TheSequence = null })
.Should().Be("{}" + DefaultOptions.NewLine);
SerializeViaYamlDotNet(new TestOM<string> { TheSequence = new() })
SerializeViaYamlDotNet(new TestOM<string> { TheSequence = [] })
.Should().Be("{}" + DefaultOptions.NewLine);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ protected string SerializeViaYamlDotNet<T>(T? testObject, PaYamlSerializerOption
using var serializationContext = new PaYamlSerializationContext(options);
var builder = new SerializerBuilder();
ConfigureYamlDotNetSerializer(builder, serializationContext);
serializationContext.ValueSerializer = builder.BuildValueSerializer();
var serializer = builder.Build();

return serializer.Serialize(testObject);
Expand All @@ -40,11 +39,9 @@ protected virtual void ConfigureYamlDotNetSerializer(SerializerBuilder builder,
using var serializationContext = new PaYamlSerializationContext(options);
var builder = new DeserializerBuilder();
ConfigureYamlDotNetDeserializer(builder, serializationContext);
serializationContext.ValueDeserializer = builder.BuildValueDeserializer();
var deserializer = builder.Build();

var value = deserializer.Deserialize<T?>(yaml);
serializationContext.OnDeserialization();
return value;
}

Expand Down
14 changes: 0 additions & 14 deletions src/Persistence/PaYaml/Models/NamedObjectMapping.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
using YamlDotNet.Core;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;

/// <summary>
Expand Down Expand Up @@ -42,14 +38,4 @@ protected override NamedObject<TValue> CreateNamedObject(string name, TValue val

return new NamedObject<TValue>(name, value);
}

protected override NamedObject<TValue> ReadNamedObjectFromMappingEntryEvents(IParser parser, ObjectDeserializer nestedObjectDeserializer)
{
return NamedObjectYamlConverter<TValue>.ReadNameAndValueEventsCore(parser, nestedObjectDeserializer);
}

protected override void WriteNamedObjectToMappingEntryEvents(IEmitter emitter, NamedObject<TValue> namedObject, ObjectSerializer nestedObjectSerializer)
{
NamedObjectYamlConverter<TValue>.WriteNameAndValueEventsCore(emitter, namedObject, nestedObjectSerializer);
}
}
61 changes: 1 addition & 60 deletions src/Persistence/PaYaml/Models/NamedObjectMappingBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,13 @@

using System.Collections;
using System.Diagnostics.CodeAnalysis;
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;

/// <summary>
/// Base implementation for an <see cref="INamedObject{TName, TValue}"/> yaml mapping.
/// </summary>
public abstract class NamedObjectMappingBase<TName, TValue, TNamedObject> : INamedObjectCollection<TName, TValue, TNamedObject>, IYamlConvertible
public abstract class NamedObjectMappingBase<TName, TValue, TNamedObject> : INamedObjectCollection<TName, TValue, TNamedObject>
where TName : notnull
where TValue : notnull
where TNamedObject : INamedObject<TName, TValue>
Expand Down Expand Up @@ -157,59 +153,4 @@ public bool TryGetValue(TName name, [MaybeNullWhen(false)] out TValue value)
return false;
}
}

#region IYamlConvertible

private void Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
Debug.Assert(expectedType.IsAssignableTo(typeof(NamedObjectMappingBase<TName, TValue, TNamedObject>)));

if (parser.TryConsumeNull())
{
// REVIEW: We may not want to support null scalars when reading named object mappings.
// This will require us to use a custom converter to be able to have access to return a null value.
// For now, we think all uses would be benign currently to just treat null inputs as an empty collection:
return;
}

parser.Consume<MappingStart>();
while (!parser.TryConsume<MappingEnd>(out _))
{
var itemStartEvent = parser.Current!;
var namedObject = ReadNamedObjectFromMappingEntryEvents(parser, nestedObjectDeserializer);

if (!TryAdd(namedObject))
{
var existingNamedObject = GetNamedObject(namedObject.Name);
throw new YamlException(itemStartEvent.Start, itemStartEvent.End, $"Duplicate name '{namedObject.Name}' used at {itemStartEvent}. First use is located at {existingNamedObject.Start}.");
}
}
}

private void Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
foreach (var namedObject in InnerCollection.Values)
{
WriteNamedObjectToMappingEntryEvents(emitter, namedObject, nestedObjectSerializer);
}

emitter.Emit(new MappingEnd());
}

protected abstract TNamedObject ReadNamedObjectFromMappingEntryEvents(IParser parser, ObjectDeserializer nestedObjectDeserializer);

protected abstract void WriteNamedObjectToMappingEntryEvents(IEmitter emitter, TNamedObject namedObject, ObjectSerializer nestedObjectSerializer);

void IYamlConvertible.Read(IParser parser, Type expectedType, ObjectDeserializer nestedObjectDeserializer)
{
Read(parser, expectedType, nestedObjectDeserializer);
}

void IYamlConvertible.Write(IEmitter emitter, ObjectSerializer nestedObjectSerializer)
{
Write(emitter, nestedObjectSerializer);
}

#endregion
}
2 changes: 1 addition & 1 deletion src/Persistence/PaYaml/Models/PaYamlLocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;

public record PaYamlLocation(int Line, int Column)
public record PaYamlLocation(long Line, long Column)
{
internal static PaYamlLocation? FromMark(Mark mark)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Concurrent;
using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
using YamlDotNet.Core;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;

/// <summary>
/// Converts any <see cref="NamedObjectMapping{TValue}"/> to and from YAML.
/// The converter is non-generic so a single registration handles all closed <c>TValue</c> types.
/// </summary>
internal sealed class NamedObjectMappingYamlConverter : IYamlTypeConverter
{
private static readonly ConcurrentDictionary<Type, IYamlTypeConverter> ConverterCache = new();

public bool Accepts(Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(NamedObjectMapping<>);
}

public object? ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
{
return GetConverter(type).ReadYaml(parser, type, rootDeserializer);
}

public void WriteYaml(IEmitter emitter, object? value, Type type, ObjectSerializer serializer)
{
GetConverter(type).WriteYaml(emitter, value, type, serializer);
}

private static IYamlTypeConverter GetConverter(Type closedMappingType)
{
return ConverterCache.GetOrAdd(closedMappingType, static t =>
{
var valueType = t.GetGenericArguments()[0];
var converterType = typeof(NamedObjectMappingYamlConverter<>).MakeGenericType(valueType);
return (IYamlTypeConverter)Activator.CreateInstance(converterType)!;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Models;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.PaYaml.Serialization;

/// <summary>
/// Strongly-typed converter for <see cref="NamedObjectMapping{TValue}"/>.
/// Typically discovered and dispatched to by the non-generic
/// <see cref="NamedObjectMappingYamlConverter"/>, but can also be registered directly.
/// </summary>
internal sealed class NamedObjectMappingYamlConverter<TValue> : YamlConverter<NamedObjectMapping<TValue>>
where TValue : notnull
{
public override NamedObjectMapping<TValue> ReadYaml(IParser parser, Type typeToConvert, ObjectDeserializer rootDeserializer)
{
var mapping = new NamedObjectMapping<TValue>();

if (parser.TryConsumeNull())
{
// REVIEW: We may not want to support null scalars when reading named object mappings.
// For now, treat null inputs as an empty collection (matches prior IYamlConvertible behavior).
return mapping;
}

parser.Consume<MappingStart>();
while (!parser.TryConsume<MappingEnd>(out _))
{
var itemStartEvent = parser.Current!;
var namedObject = NamedObjectYamlConverter<TValue>.ReadNameAndValueEventsCore(parser, rootDeserializer);

if (!mapping.TryAdd(namedObject))
{
var existingNamedObject = mapping.GetNamedObject(namedObject.Name);
throw new YamlException(itemStartEvent.Start, itemStartEvent.End, $"Duplicate name '{namedObject.Name}' used at {itemStartEvent}. First use is located at {existingNamedObject.Start}.");
}
}

return mapping;
}

public override void WriteYaml(IEmitter emitter, NamedObjectMapping<TValue>? value, Type typeToConvert, ObjectSerializer serializer)
{
if (value is null)
{
emitter.EmitNull();
return;
}

emitter.Emit(new MappingStart(AnchorName.Empty, TagName.Empty, isImplicit: true, MappingStyle.Block));
foreach (var namedObject in value)
{
NamedObjectYamlConverter<TValue>.WriteNameAndValueEventsCore(emitter, namedObject, serializer);
}

emitter.Emit(new MappingEnd());
}
}
Loading
Loading