diff --git a/src/main/Yardarm.Client/Serialization/Literals/LiteralConverterRegistry.cs b/src/main/Yardarm.Client/Serialization/Literals/LiteralConverterRegistry.cs index 940eda3d..b26a6ff8 100644 --- a/src/main/Yardarm.Client/Serialization/Literals/LiteralConverterRegistry.cs +++ b/src/main/Yardarm.Client/Serialization/Literals/LiteralConverterRegistry.cs @@ -147,10 +147,10 @@ public LiteralConverterRegistry Add(LiteralConverter converter) } /// - /// Returns a new with the all default converters registered. + /// Returns a new with the built-in converters registered. /// /// A new . - public static LiteralConverterRegistry CreateDefaultRegistry() => + public static LiteralConverterRegistry CreateBasicRegistry() => new LiteralConverterRegistry() .Add(new BooleanLiteralConverter()) .Add(new DateTimeLiteralConverter()) @@ -175,4 +175,15 @@ public static LiteralConverterRegistry CreateDefaultRegistry() => .Add(new TimeOnlyLiteralConverter()) #endif ; + + /// + /// Returns a new with the all default converters registered. + /// + /// A new . + public static LiteralConverterRegistry CreateDefaultRegistry() + { + LiteralConverterRegistry registry = CreateBasicRegistry(); + // Enrichment point + return registry; + } } diff --git a/src/main/Yardarm.Client/Serialization/TypeSerializerRegistry.cs b/src/main/Yardarm.Client/Serialization/TypeSerializerRegistry.cs index 05589013..928cca79 100644 --- a/src/main/Yardarm.Client/Serialization/TypeSerializerRegistry.cs +++ b/src/main/Yardarm.Client/Serialization/TypeSerializerRegistry.cs @@ -114,7 +114,11 @@ public static ITypeSerializerRegistry CreateBasicRegistry() => /// Returns a new with the all default serializers registered. /// /// A new . - public static ITypeSerializerRegistry CreateDefaultRegistry() => - CreateBasicRegistry(); + public static ITypeSerializerRegistry CreateDefaultRegistry() + { + ITypeSerializerRegistry registry = CreateBasicRegistry(); + // Enrichment point + return registry; + } } } diff --git a/src/main/Yardarm.NewtonsoftJson/JsonCreateDefaultRegistryEnricher.cs b/src/main/Yardarm.NewtonsoftJson/JsonCreateDefaultRegistryEnricher.cs index 6e17600a..54273928 100644 --- a/src/main/Yardarm.NewtonsoftJson/JsonCreateDefaultRegistryEnricher.cs +++ b/src/main/Yardarm.NewtonsoftJson/JsonCreateDefaultRegistryEnricher.cs @@ -1,13 +1,12 @@ using System; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Yardarm.Enrichment; +using Yardarm.Enrichment.Registration; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Yardarm.NewtonsoftJson { - public class JsonCreateDefaultRegistryEnricher : ICreateDefaultRegistryEnricher + public class JsonCreateDefaultRegistryEnricher : ReturnValueRegistrationEnricher, ICreateDefaultRegistryEnricher { private readonly IJsonSerializationNamespace _jsonSerializationNamespace; @@ -18,10 +17,10 @@ public JsonCreateDefaultRegistryEnricher(IJsonSerializationNamespace jsonSeriali _jsonSerializationNamespace = jsonSerializationNamespace; } - public ExpressionSyntax Enrich(ExpressionSyntax target) => + protected override ExpressionSyntax EnrichReturnValue(ExpressionSyntax expression) => InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - target.WithTrailingTrivia(TriviaList(CarriageReturnLineFeed, Whitespace(" "))), + expression, GenericName( Identifier("Add"), TypeArgumentList(SingletonSeparatedList(_jsonSerializationNamespace.JsonTypeSerializer)))), diff --git a/src/main/Yardarm.SystemTextJson/JsonCreateDefaultRegistryEnricher.cs b/src/main/Yardarm.SystemTextJson/JsonCreateDefaultRegistryEnricher.cs index 40506f86..3a090c46 100644 --- a/src/main/Yardarm.SystemTextJson/JsonCreateDefaultRegistryEnricher.cs +++ b/src/main/Yardarm.SystemTextJson/JsonCreateDefaultRegistryEnricher.cs @@ -1,14 +1,13 @@ using System; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Yardarm.Enrichment; +using Yardarm.Enrichment.Registration; using Yardarm.SystemTextJson.Internal; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; namespace Yardarm.SystemTextJson { - public class JsonCreateDefaultRegistryEnricher : ICreateDefaultRegistryEnricher + public class JsonCreateDefaultRegistryEnricher : ReturnValueRegistrationEnricher, ICreateDefaultRegistryEnricher { private readonly IJsonSerializationNamespace _jsonSerializationNamespace; @@ -19,16 +18,16 @@ public JsonCreateDefaultRegistryEnricher(IJsonSerializationNamespace jsonSeriali _jsonSerializationNamespace = jsonSerializationNamespace; } - public ExpressionSyntax Enrich(ExpressionSyntax target) => + protected override ExpressionSyntax EnrichReturnValue(ExpressionSyntax target) => // Don't use the Add overload here because it will cause trimming to retain // all constructors. This will then cause IL2026 warnings if trimming is enabled. // Instead create a new instance directly using the default constructor and add it. InvocationExpression( MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, - target.WithTrailingTrivia(TriviaList(CarriageReturnLineFeed, Whitespace(" "))), + target, IdentifierName("Add")), - ArgumentList(SeparatedList(new[] - { + ArgumentList(SeparatedList( + [ Argument(MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, _jsonSerializationNamespace.JsonTypeSerializer, IdentifierName("SupportedMediaTypes"))), @@ -38,6 +37,6 @@ public ExpressionSyntax Enrich(ExpressionSyntax target) => QualifiedName(_jsonSerializationNamespace.Name, IdentifierName(JsonSerializerContextGenerator.TypeName)), IdentifierName("Default"))))), null)) - }))); + ]))); } } diff --git a/src/main/Yardarm/Enrichment/Compilation/DefaultLiteralConvertersEnricher.cs b/src/main/Yardarm/Enrichment/Compilation/DefaultLiteralConvertersEnricher.cs index 01b06cc2..de4fb553 100644 --- a/src/main/Yardarm/Enrichment/Compilation/DefaultLiteralConvertersEnricher.cs +++ b/src/main/Yardarm/Enrichment/Compilation/DefaultLiteralConvertersEnricher.cs @@ -1,15 +1,18 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Extensions.DependencyInjection; +using Yardarm.Enrichment.Registration; namespace Yardarm.Enrichment.Compilation; public class DefaultLiteralConvertersEnricher( - IEnumerable createDefaultRegistryEnrichers) + [FromKeyedServices(DefaultLiteralConvertersEnricher.RegistrationEnricherKey)] IEnumerable createDefaultRegistryEnrichers) : IResourceFileEnricher { + public const string RegistrationEnricherKey = "DefaultLiteralConverters"; + public bool ShouldEnrich(string resourceName) => resourceName == "Yardarm.Client.Serialization.Literals.LiteralConverterRegistry.cs"; @@ -25,11 +28,10 @@ public CompilationUnitSyntax Enrich(CompilationUnitSyntax target, ResourceFileEn .OfType() .FirstOrDefault(p => p.Identifier.ValueText == "CreateDefaultRegistry"); - if (methodDeclaration?.ExpressionBody != null) + if (methodDeclaration?.Body is { } body) { - MethodDeclarationSyntax newMethodDeclaration = methodDeclaration.WithExpressionBody( - methodDeclaration.ExpressionBody.WithExpression( - methodDeclaration.ExpressionBody.Expression.Enrich(createDefaultRegistryEnrichers))); + MethodDeclarationSyntax newMethodDeclaration = methodDeclaration.WithBody( + body.Enrich(createDefaultRegistryEnrichers)); target = target.ReplaceNode(methodDeclaration, newMethodDeclaration); } diff --git a/src/main/Yardarm/Enrichment/Compilation/DefaultTypeSerializersEnricher.cs b/src/main/Yardarm/Enrichment/Compilation/DefaultTypeSerializersEnricher.cs index c3783082..f4c802e6 100644 --- a/src/main/Yardarm/Enrichment/Compilation/DefaultTypeSerializersEnricher.cs +++ b/src/main/Yardarm/Enrichment/Compilation/DefaultTypeSerializersEnricher.cs @@ -2,13 +2,17 @@ using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Extensions.DependencyInjection; +using Yardarm.Enrichment.Registration; namespace Yardarm.Enrichment.Compilation; public class DefaultTypeSerializersEnricher( - IEnumerable createDefaultRegistryEnrichers) : + [FromKeyedServices(DefaultTypeSerializersEnricher.RegistrationEnricherKey)] IEnumerable createDefaultRegistryEnrichers) : IResourceFileEnricher { + public const string RegistrationEnricherKey = "DefaultTypeSerializers"; + public bool ShouldEnrich(string resourceName) => resourceName == "Yardarm.Client.Serialization.TypeSerializerRegistry.cs"; @@ -24,11 +28,10 @@ public CompilationUnitSyntax Enrich(CompilationUnitSyntax target, ResourceFileEn .OfType() .FirstOrDefault(p => p.Identifier.ValueText == "CreateDefaultRegistry"); - if (methodDeclaration?.ExpressionBody != null) + if (methodDeclaration?.Body is { } body) { - MethodDeclarationSyntax newMethodDeclaration = methodDeclaration.WithExpressionBody( - methodDeclaration.ExpressionBody.WithExpression( - methodDeclaration.ExpressionBody.Expression.Enrich(createDefaultRegistryEnrichers))); + MethodDeclarationSyntax newMethodDeclaration = methodDeclaration.WithBody( + body.Enrich(createDefaultRegistryEnrichers)); target = target.ReplaceNode(methodDeclaration, newMethodDeclaration); } diff --git a/src/main/Yardarm/Enrichment/EnricherServiceCollectionExtensions.cs b/src/main/Yardarm/Enrichment/EnricherServiceCollectionExtensions.cs index f250610a..dea5d91d 100644 --- a/src/main/Yardarm/Enrichment/EnricherServiceCollectionExtensions.cs +++ b/src/main/Yardarm/Enrichment/EnricherServiceCollectionExtensions.cs @@ -1,7 +1,9 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Yardarm.Enrichment.Authentication; using Yardarm.Enrichment.Compilation; using Yardarm.Enrichment.Packaging; +using Yardarm.Enrichment.Registration; using Yardarm.Enrichment.Requests; using Yardarm.Enrichment.Responses; using Yardarm.Enrichment.Schema; @@ -21,13 +23,22 @@ public static IServiceCollection AddDefaultEnrichers(this IServiceCollection ser .AddDefaultResponseEnrichers() .AddDefaultTagEnrichers(); + public static IServiceCollection AddRegistrationEnricher(this IServiceCollection services, string registrationType) + where T : class, IRegistrationEnricher + { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(registrationType); + + return services.AddKeyedTransient(registrationType); + } + public static IServiceCollection AddCreateDefaultRegistryEnricher(this IServiceCollection services) where T : class, ICreateDefaultRegistryEnricher => - services.AddTransient(); + services.AddRegistrationEnricher(DefaultTypeSerializersEnricher.RegistrationEnricherKey); public static IServiceCollection AddDefaultLiteralConverterEnricher(this IServiceCollection services) where T : class, IDefaultLiteralConverterEnricher => - services.AddTransient(); + services.AddRegistrationEnricher(DefaultLiteralConvertersEnricher.RegistrationEnricherKey); public static IServiceCollection AddOpenApiSyntaxNodeEnricher(this IServiceCollection services) where T : class, IOpenApiSyntaxNodeEnricher => diff --git a/src/main/Yardarm/Enrichment/ICreateDefaultRegistryEnricher.cs b/src/main/Yardarm/Enrichment/ICreateDefaultRegistryEnricher.cs deleted file mode 100644 index acc8f895..00000000 --- a/src/main/Yardarm/Enrichment/ICreateDefaultRegistryEnricher.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Yardarm.Enrichment -{ - public interface ICreateDefaultRegistryEnricher : IEnricher - { - } -} diff --git a/src/main/Yardarm/Enrichment/IDefaultLiteralConverterEnricher.cs b/src/main/Yardarm/Enrichment/IDefaultLiteralConverterEnricher.cs deleted file mode 100644 index 617706f0..00000000 --- a/src/main/Yardarm/Enrichment/IDefaultLiteralConverterEnricher.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Yardarm.Enrichment; - -/// -/// Enriches the body of the CreateDefaultRegistry method in the LiteralConverterRegistry class. -/// -public interface IDefaultLiteralConverterEnricher : IEnricher -{ -} diff --git a/src/main/Yardarm/Enrichment/Registration/ICreateDefaultRegistryEnricher.cs b/src/main/Yardarm/Enrichment/Registration/ICreateDefaultRegistryEnricher.cs new file mode 100644 index 00000000..a2ed8be3 --- /dev/null +++ b/src/main/Yardarm/Enrichment/Registration/ICreateDefaultRegistryEnricher.cs @@ -0,0 +1,6 @@ +namespace Yardarm.Enrichment.Registration +{ + public interface ICreateDefaultRegistryEnricher : IRegistrationEnricher + { + } +} diff --git a/src/main/Yardarm/Enrichment/Registration/IDefaultLiteralConverterEnricher.cs b/src/main/Yardarm/Enrichment/Registration/IDefaultLiteralConverterEnricher.cs new file mode 100644 index 00000000..65829ab7 --- /dev/null +++ b/src/main/Yardarm/Enrichment/Registration/IDefaultLiteralConverterEnricher.cs @@ -0,0 +1,8 @@ +namespace Yardarm.Enrichment.Registration; + +/// +/// Enriches the body of the CreateDefaultRegistry method in the LiteralConverterRegistry class. +/// +public interface IDefaultLiteralConverterEnricher : IRegistrationEnricher +{ +} diff --git a/src/main/Yardarm/Enrichment/Registration/IRegistrationEnricher.cs b/src/main/Yardarm/Enrichment/Registration/IRegistrationEnricher.cs new file mode 100644 index 00000000..5f71e7dd --- /dev/null +++ b/src/main/Yardarm/Enrichment/Registration/IRegistrationEnricher.cs @@ -0,0 +1,10 @@ +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Yardarm.Enrichment.Registration; + +/// +/// An expression enricher used by extensions to register themselves in the client. +/// +public interface IRegistrationEnricher : IEnricher +{ +} diff --git a/src/main/Yardarm/Enrichment/Registration/ReturnValueRegistrationEnricher.cs b/src/main/Yardarm/Enrichment/Registration/ReturnValueRegistrationEnricher.cs new file mode 100644 index 00000000..e5580119 --- /dev/null +++ b/src/main/Yardarm/Enrichment/Registration/ReturnValueRegistrationEnricher.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace Yardarm.Enrichment.Registration; + +/// +/// Common implementation for an that modifies the return value of a method. +/// +public abstract class ReturnValueRegistrationEnricher : StatementInsertingRegistrationEnricher +{ + protected override sealed IEnumerable GenerateStatements(ExpressionSyntax? returnExpression) + { + if (returnExpression is not IdentifierNameSyntax identifier) + { + return []; + } + + ExpressionSyntax newReturnValue = EnrichReturnValue(identifier); + if (newReturnValue == returnExpression) + { + // Was not changed + return []; + } + + ExpressionStatementSyntax statement = ExpressionStatement(AssignmentExpression( + Microsoft.CodeAnalysis.CSharp.SyntaxKind.SimpleAssignmentExpression, + identifier, + newReturnValue)); + + return [statement]; + } + + protected abstract ExpressionSyntax EnrichReturnValue(ExpressionSyntax expression); +} diff --git a/src/main/Yardarm/Enrichment/Registration/StatementInsertingRegistrationEnricher.cs b/src/main/Yardarm/Enrichment/Registration/StatementInsertingRegistrationEnricher.cs new file mode 100644 index 00000000..332db320 --- /dev/null +++ b/src/main/Yardarm/Enrichment/Registration/StatementInsertingRegistrationEnricher.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Yardarm.Enrichment.Registration; + +/// +/// Common implementation for an that inserts statements before the return +/// at the end of the method. +/// +public abstract class StatementInsertingRegistrationEnricher : IRegistrationEnricher +{ + public BlockSyntax Enrich(BlockSyntax target) + { + int returnStatementIndex = target.Statements.LastIndexOf(p => p is ReturnStatementSyntax); + ExpressionSyntax? returnExpression = returnStatementIndex >= 0 + ? ((ReturnStatementSyntax)target.Statements[returnStatementIndex]).Expression + : null; + + IEnumerable newStatements = GenerateStatements(returnExpression); + if (newStatements == Enumerable.Empty() || + newStatements is ICollection { Count: 0 }) + { + // No statements added, short-circuit + return target; + } + + return target.WithStatements( + target.Statements.InsertRange(returnStatementIndex, newStatements)); + } + + /// + /// Generates the statements to insert before the return statement. + /// + /// The expression being returned in the return statement. + /// + protected abstract IEnumerable GenerateStatements(ExpressionSyntax? returnExpression); +}