// SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2022-2026 Aykut Alparslan KOC using System.Collections.ObjectModel; namespace Ferrite.TL.Schema; public sealed class LayerTransformEdgeDefinition : ILayerTransformEdge { private readonly IReadOnlyDictionary _dispositions; private readonly IReadOnlyDictionary _converters; private readonly IReadOnlyList _dispositionValues; private readonly IReadOnlyDictionary _scalarUnwraps; public LayerEdge Edge { get; } public LayerSchema SourceSchema { get; } public LayerSchema TargetSchema { get; } public bool HasUnsupportedDispositions { get; } public IReadOnlyList Dispositions => _dispositionValues; public LayerTransformEdgeDefinition(LayerSchema sourceSchema, LayerSchema targetSchema, IEnumerable dispositions, IReadOnlyDictionary? converters = null) { if (sourceSchema.Layer == targetSchema.Layer) { throw new ArgumentException("An edge must join different layers."); } SourceSchema = sourceSchema; TargetSchema = targetSchema; Edge = new LayerEdge(sourceSchema.Layer, targetSchema.Layer); LayerConstructorDisposition[] values = dispositions.ToArray(); var byConstructor = new Dictionary(StringComparer.Ordinal); foreach (LayerConstructorDisposition disposition in values) { if (byConstructor.ContainsKey(disposition.SourceConstructor)) { throw new ArgumentException("Duplicate constructor disposition.", nameof(dispositions)); } byConstructor.Add(disposition.SourceConstructor, disposition); } _dispositions = new ReadOnlyDictionary(byConstructor); _dispositionValues = Array.AsReadOnly(values); var converterValues = new Dictionary( StringComparer.Ordinal); if (converters != null) { foreach (KeyValuePair converter in converters) { converterValues.Add(converter.Key, converter.Value); } } _converters = new ReadOnlyDictionary( converterValues); ValidateCoverage(); _scalarUnwraps = ScalarUnwraps(); HasUnsupportedDispositions = values.Any(x => x.Kind == LayerDispositionKind.Unsupported); } public LayerTransformResult Transform(byte[] source) { return Transform(source, null); } public LayerTransformResult Transform(byte[] source, string? vectorElementType) { int? constructor = source.Length >= 4 ? source[0] | source[1] << 8 | source[2] << 16 | source[3] << 24 : (int?)null; try { var reader = new LayerWireReader(source); var writer = new LayerWireWriter(); if (vectorElementType != null) { LayerType rootType = VectorResultType(vectorElementType); object rootValue = LayerWireCodec.ReadValue(reader, SourceSchema, rootType); if (!reader.IsComplete) { throw new LayerWireException( "Trailing bytes remain after the root vector."); } if (_scalarUnwraps.TryGetValue(vectorElementType, out LayerType? scalarType)) { LayerWireCodec.WriteValue(writer, TargetSchema, new LayerType(LayerWireKind.Vector, null, false, scalarType), UnwrapScalars((IReadOnlyList)rootValue)); return LayerTransformResult.TransformedVector(writer.ToArray(), VectorElementName(scalarType)); } LayerWireCodec.WriteValue(writer, TargetSchema, rootType, TransformValue(rootType, rootType, rootValue)); byte[] rootBytes = writer.ToArray(); return source.SequenceEqual(rootBytes) ? LayerTransformResult.Identity(source) : LayerTransformResult.Transformed(rootBytes); } LayerObjectValue sourceValue = LayerWireCodec.ReadBoxedObject(reader, SourceSchema); if (!reader.IsComplete) { throw new LayerWireException("Trailing bytes remain after the root object."); } if (TryUnwrapVector(sourceValue, out LayerType vectorType, out object? vector)) { LayerWireCodec.WriteValue(writer, TargetSchema, vectorType, TransformValue(vectorType, vectorType, vector)); return LayerTransformResult.TransformedVector(writer.ToArray(), VectorElementName(vectorType.ElementType!)); } LayerObjectValue targetValue = TransformObject(sourceValue); LayerWireCodec.WriteBoxedObject(writer, TargetSchema, targetValue); byte[] transformed = writer.ToArray(); if (source.SequenceEqual(transformed)) { return LayerTransformResult.Identity(source); } return LayerTransformResult.Transformed(transformed); } catch (LayerUncoveredException exception) { return LayerTransformResult.Failure(LayerTransformStatus.Uncovered, Edge, constructor, exception.Message); } catch (LayerWireException exception) { return LayerTransformResult.Failure(LayerTransformStatus.Malformed, Edge, constructor, exception.Message); } catch (Exception exception) { return LayerTransformResult.Failure(LayerTransformStatus.Malformed, Edge, constructor, exception.Message); } } private static LayerType VectorResultType(string elementType) { return new LayerType(LayerWireKind.Vector, null, false, VectorElementType(elementType)); } private static LayerType VectorElementType(string elementType) { switch (elementType) { case "int": return new LayerType(LayerWireKind.Int32); case "long": return new LayerType(LayerWireKind.Int64); case "double": return new LayerType(LayerWireKind.Double); case "string": return new LayerType(LayerWireKind.String); case "bytes": return new LayerType(LayerWireKind.Bytes); default: return new LayerType(LayerWireKind.Object, elementType); } } private static string VectorElementName(LayerType elementType) { switch (elementType.Kind) { case LayerWireKind.Int32: return "int"; case LayerWireKind.Int64: return "long"; case LayerWireKind.Double: return "double"; case LayerWireKind.String: return "string"; case LayerWireKind.Bytes: return "bytes"; default: return elementType.Name ?? throw new LayerUncoveredException("A vector element type has no name."); } } private bool TryUnwrapVector(LayerObjectValue source, out LayerType vectorType, out object? vector) { vectorType = null!; vector = null; if (!_dispositions.TryGetValue(source.Constructor.Name, out LayerConstructorDisposition? disposition) || disposition.Kind != LayerDispositionKind.Unwrap) { return false; } LayerField field = UnwrappedField(source, disposition, out object? value); if (field.Type.Kind != LayerWireKind.Vector) { return false; } vectorType = field.Type; vector = value; return true; } private IReadOnlyList UnwrapScalars(IReadOnlyList values) { var scalars = new object?[values.Count]; for (int index = 0; index < values.Count; index++) { var value = (LayerObjectValue)values[index]!; UnwrappedField(value, _dispositions[value.Constructor.Name], out scalars[index]); } return Array.AsReadOnly(scalars); } private IReadOnlyDictionary ScalarUnwraps() { var scalars = new Dictionary(StringComparer.Ordinal); foreach (LayerConstructorDisposition disposition in _dispositionValues) { LayerConstructor source = SourceSchema.GetConstructor(disposition.SourceConstructor); LayerType? type = disposition.Kind == LayerDispositionKind.Unwrap ? source.FindField(disposition.Field!)!.Type : null; if (type == null || !IsScalar(type)) { continue; } foreach (LayerConstructor sibling in SourceSchema.Constructors) { if (sibling.ResultType != source.ResultType) { continue; } if (!_dispositions.TryGetValue(sibling.Name, out LayerConstructorDisposition? unwrap) || unwrap.Kind != LayerDispositionKind.Unwrap || !sibling.FindField(unwrap.Field!)!.Type.HasSameWireType(type)) { throw new ArgumentException("Every constructor of " + source.ResultType + " must unwrap the same scalar."); } } scalars[source.ResultType] = type; } return new ReadOnlyDictionary(scalars); } private static bool IsScalar(LayerType type) { return type.Kind is LayerWireKind.Int32 or LayerWireKind.Int64 or LayerWireKind.Double or LayerWireKind.String or LayerWireKind.Bytes; } private static LayerField UnwrappedField(LayerObjectValue source, LayerConstructorDisposition disposition, out object? value) { LayerField field = source.Constructor.FindField(disposition.Field!)!; if (!source.TryGetValue(field.Name, out value) || value == null) { throw new LayerUncoveredException("Unwrapped field is absent: " + source.Constructor.Name + "." + field.Name + "."); } return field; } private void ValidateCoverage() { var changed = new HashSet(StringComparer.Ordinal); foreach (LayerConstructor source in SourceSchema.Constructors) { if (!TargetSchema.TryGetConstructor(source.Name, out LayerConstructor target) || !source.HasSameShape(target)) { changed.Add(source.Name); } } foreach (string name in changed) { if (!_dispositions.ContainsKey(name)) { throw new ArgumentException("Missing constructor disposition for " + name + "."); } } foreach (LayerConstructorDisposition disposition in _dispositionValues) { if (!changed.Contains(disposition.SourceConstructor)) { throw new ArgumentException("Disposition does not match a schema change for " + disposition.SourceConstructor + "."); } if (!SourceSchema.TryGetConstructor(disposition.SourceConstructor, out LayerConstructor source)) { throw new ArgumentException("Disposition names an unknown source constructor."); } if (disposition.Kind == LayerDispositionKind.Mechanical) { string targetName = disposition.TargetConstructor ?? source.Name; if (!TargetSchema.TryGetConstructor(targetName, out LayerConstructor target)) { throw new ArgumentException("Mechanical target constructor is missing."); } ValidateMechanicalMap(source, target); } else if (disposition.Kind == LayerDispositionKind.Semantic) { if (string.IsNullOrWhiteSpace(disposition.ConverterName)) { throw new ArgumentException("A semantic disposition requires a converter name."); } } else if (disposition.Kind == LayerDispositionKind.Unwrap) { LayerField? field = disposition.Field == null ? null : source.FindField(disposition.Field); if (field == null || field.Type.Kind is not (LayerWireKind.Object or LayerWireKind.Vector) && !IsScalar(field.Type)) { throw new ArgumentException("An unwrap disposition must name an object, vector or scalar field of " + source.Name + "."); } } } } private static void ValidateMechanicalMap(LayerConstructor source, LayerConstructor target) { foreach (LayerField targetField in target.Fields) { if (targetField.Type.Kind == LayerWireKind.Flags) { continue; } LayerField? sourceField = source.FindField(targetField.Name); if (sourceField == null || !sourceField.Type.HasSameWireType(targetField.Type) || sourceField.IsConditional != targetField.IsConditional) { throw new ArgumentException("Mechanical field map is not wire-compatible for " + source.Name + "." + targetField.Name + "."); } } } private LayerObjectValue TransformObject(LayerObjectValue source) { if (_dispositions.TryGetValue(source.Constructor.Name, out LayerConstructorDisposition? disposition)) { switch (disposition.Kind) { case LayerDispositionKind.Mechanical: return TransformMechanical(source, disposition.TargetConstructor ?? source.Constructor.Name); case LayerDispositionKind.Semantic: if (!_converters.TryGetValue(disposition.ConverterName!, out LayerSemanticConverter? converter)) { throw new LayerUncoveredException("Missing semantic converter " + disposition.ConverterName + "."); } LayerObjectValue? converted; try { converted = converter( new LayerConversionContext(SourceSchema, TargetSchema, (value, target, fields, _) => TransformCompatible(value, target, fields), TransformObject), source); } catch (Exception exception) { throw new LayerUncoveredException("Semantic converter " + disposition.ConverterName + " failed: " + exception.Message); } if (converted == null) { throw new LayerUncoveredException("Semantic converter " + disposition.ConverterName + " did not produce a value."); } if (!TargetSchema.TryGetConstructor(converted.Constructor.Name, out _)) { throw new LayerUncoveredException("Semantic converter " + disposition.ConverterName + " produced an unknown target constructor."); } return converted; case LayerDispositionKind.Unwrap: LayerField unwrapped = UnwrappedField(source, disposition, out object? value); if (unwrapped.Type.Kind != LayerWireKind.Object) { throw new LayerUncoveredException("Only a root result can unwrap " + source.Constructor.Name + "." + unwrapped.Name + "."); } return TransformObject((LayerObjectValue)value!); case LayerDispositionKind.Unsupported: case LayerDispositionKind.ProvenNotEmitted: case LayerDispositionKind.NoTargetConstructor: case LayerDispositionKind.Omit: case LayerDispositionKind.Stream: throw new LayerUncoveredException("Constructor disposition " + disposition.Kind + " cannot transform " + source.Constructor.Name + "."); default: throw new LayerUncoveredException("Unknown constructor disposition."); } } if (!TargetSchema.TryGetConstructor(source.Constructor.Name, out LayerConstructor target) || !source.Constructor.HasSameShape(target)) { throw new LayerUncoveredException("Constructor is not covered: " + source.Constructor.Name + "."); } return TransformMechanical(source, source.Constructor.Name); } private LayerObjectValue TransformMechanical(LayerObjectValue source, string targetConstructorName) { return TransformCompatible(source, targetConstructorName, null); } private LayerObjectValue TransformCompatible(LayerObjectValue source, string targetConstructorName, IReadOnlyDictionary? replacements) { LayerConstructor target = TargetSchema.GetConstructor(targetConstructorName); var fields = new Dictionary(StringComparer.Ordinal); foreach (LayerField targetField in target.Fields) { if (targetField.Type.Kind == LayerWireKind.Flags) { continue; } if (replacements != null && replacements.TryGetValue(targetField.Name, out object? replacement)) { fields.Add(targetField.Name, replacement); continue; } LayerField? sourceField = source.Constructor.FindField(targetField.Name); if (sourceField == null) { if (targetField.IsConditional) { continue; } throw new LayerUncoveredException("Target field has no source field: " + targetField.Name + "."); } if (!source.TryGetValue(sourceField.Name, out object? value)) { if (targetField.IsConditional) { continue; } throw new LayerUncoveredException("Required target field is absent: " + targetField.Name + "."); } if (!sourceField.Type.HasSameWireType(targetField.Type)) { throw new LayerUncoveredException("Target field has an incompatible source field: " + targetField.Name + "."); } fields.Add(targetField.Name, TransformValue(sourceField.Type, targetField.Type, value)); } return new LayerObjectValue(target, fields); } private bool IsOmitted(object? value) { return value is LayerObjectValue item && _dispositions.TryGetValue(item.Constructor.Name, out LayerConstructorDisposition? disposition) && disposition.Kind == LayerDispositionKind.Omit; } private object? TransformValue(LayerType sourceType, LayerType targetType, object? value) { if (!sourceType.HasSameWireType(targetType)) { throw new LayerUncoveredException("Mechanical value types differ."); } if (value == null) { return null; } if (sourceType.Kind == LayerWireKind.Object) { return TransformObject((LayerObjectValue)value); } if (sourceType.Kind == LayerWireKind.Vector) { var sourceValues = (IReadOnlyList)value; var targetValues = new List(sourceValues.Count); foreach (object? sourceValue in sourceValues) { if (!IsOmitted(sourceValue)) { targetValues.Add(TransformValue(sourceType.ElementType!, targetType.ElementType!, sourceValue)); } } return targetValues.AsReadOnly(); } return value; } }