From 79c9b848c629a7abdec3b5dfdbce2c9591f2533f Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Mon, 5 Dec 2022 11:34:00 +0100 Subject: [PATCH] refactor: split into indexers and generators --- .../Generators/EnumGenerator.cs | 30 ++------ .../Generators/ModelGenerator.cs | 41 +++++----- .../Generators/SpecBase.cs | 8 -- .../ISchemaGenerator.cs | 9 +++ src/Bismarck.CodeGenerator/ISchemaIndexer.cs | 11 +++ .../Indexers/EnumSchemaIndexer.cs | 25 +++++++ .../Indexers/ObjectSchemaIndexer.cs | 75 +++++++++++++++++++ .../Models/GeneratorSpecs.cs | 38 ++++++++++ src/Bismarck.CodeGenerator/SchemaRegistry.cs | 43 +++++++++++ 9 files changed, 224 insertions(+), 56 deletions(-) delete mode 100644 src/Bismarck.CodeGenerator/Generators/SpecBase.cs create mode 100644 src/Bismarck.CodeGenerator/ISchemaGenerator.cs create mode 100644 src/Bismarck.CodeGenerator/ISchemaIndexer.cs create mode 100644 src/Bismarck.CodeGenerator/Indexers/EnumSchemaIndexer.cs create mode 100644 src/Bismarck.CodeGenerator/Indexers/ObjectSchemaIndexer.cs create mode 100644 src/Bismarck.CodeGenerator/Models/GeneratorSpecs.cs create mode 100644 src/Bismarck.CodeGenerator/SchemaRegistry.cs diff --git a/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs b/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs index 86bd43c..6eae361 100644 --- a/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs +++ b/src/Bismarck.CodeGenerator/Generators/EnumGenerator.cs @@ -1,20 +1,11 @@ using Bismarck.CodeGenerator.Extensions; using Bismarck.CodeGenerator.Models; -using Microsoft.CodeAnalysis; -using Microsoft.OpenApi.Any; -using Microsoft.OpenApi.Models; - using Scriban; namespace Bismarck.CodeGenerator.Generators; -internal record EnumSpec(string Namespace, string TargetName, IEnumerable Values) : SpecBase(Namespace, TargetName) -{ - public IEnumerable Values { get; } = Values; -} - -internal class EnumGenerator +internal class EnumGenerator : ISchemaGenerator { private readonly Template _enumTemplate; @@ -23,21 +14,10 @@ internal class EnumGenerator _enumTemplate = Template.Parse(typeof(ContractGenerator).LoadRelativeResource("templates", "Enum.tmplcs")); } - public void Generate(GeneratorContext context, OpenApiSchema schema, string namespaceName, string name) + public SchemaKind SupportedSchemaKind => SchemaKind.Enum; + + public void Generate(GeneratorContext context, IGeneratorSpec spec) { - context.AddSource(SchemaKind.Enum, name, _enumTemplate.Render(new EnumSpec(namespaceName, name, MapFromAny(schema.Enum)))); + context.AddSource(SchemaKind.Enum, spec.TargetName, _enumTemplate.Render(spec)); } - - private static IEnumerable MapFromAny(IEnumerable enumValues) => enumValues.SelectMany(v => - { - if (v is OpenApiString s) - { - return new[] - { - s.Value.SanitizeTypeName() - }; - } - - return Enumerable.Empty(); - }); } \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs b/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs index 6e3a049..fc38166 100644 --- a/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs +++ b/src/Bismarck.CodeGenerator/Generators/ModelGenerator.cs @@ -1,64 +1,59 @@ +using System.Collections.Immutable; +using System.Collections.ObjectModel; + using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Indexers; using Bismarck.CodeGenerator.Models; -using Microsoft.CodeAnalysis; using Microsoft.OpenApi.Models; using Scriban; namespace Bismarck.CodeGenerator.Generators; -public record PropertySpec(PropertyType Type, string Name) -{ - public PropertyType Type { get; } = Type; - - public string Name { get; } = Name; -} - -internal record ModelSpec(string Namespace, string TargetName) : SpecBase(Namespace, TargetName) -{ - public IList Properties { get; set; } = new List(); -} - internal class ModelGenerator { private readonly Template _modelTemplate; - private readonly EnumGenerator _enumGenerator; + private readonly IDictionary _schemaIndexers; public ModelGenerator() { - _modelTemplate = Template.Parse(typeof(ContractGenerator).LoadRelativeResource("templates", "SchemaModel.tmplcs")); - _enumGenerator = new EnumGenerator(); + _modelTemplate = + Template.Parse(typeof(ContractGenerator).LoadRelativeResource("templates", "SchemaModel.tmplcs")); + _schemaIndexers = new Dictionary { { SchemaKind.Enum, new EnumSchemaIndexer() } }; } - public void GenerateModelSchema(GeneratorContext context, IDictionary schemata, string namespaceName) + public void GenerateModelSchema(GeneratorContext context, IDictionary schemata, + string namespaceName) { foreach ((string? key, OpenApiSchema? schema) in schemata) { var type = Type(schema, key); var spec = new ModelSpec(namespaceName, key.SanitizeTypeName()); - GenerateProperties(context, spec, new[] - { - key - }, schema); + GenerateProperties(context, spec, new[] { key }, schema); context.AddSource(type.Kind, spec.TargetName, _modelTemplate.Render(spec)); } } - private void GenerateProperties(GeneratorContext context, ModelSpec modelSpec, IEnumerable location, OpenApiSchema schema) + private void GenerateProperties(GeneratorContext context, ModelSpec modelSpec, IEnumerable location, + OpenApiSchema schema) { var parentName = location.Last(); foreach ((string? key, OpenApiSchema? value) in schema.Properties) { var propertyType = Type(value, parentName); + if (_schemaIndexers.TryGetValue(propertyType.Kind, out var indexer)) + { + indexer.Index(value, modelSpec.Namespace, key.SanitizeTypeName()); + } + switch (propertyType.Kind) { case SchemaKind.Simple: modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); break; case SchemaKind.Enum: - _enumGenerator.Generate(context, value, modelSpec.Namespace, key.SanitizeTypeName()); modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); break; } diff --git a/src/Bismarck.CodeGenerator/Generators/SpecBase.cs b/src/Bismarck.CodeGenerator/Generators/SpecBase.cs deleted file mode 100644 index ca583f8..0000000 --- a/src/Bismarck.CodeGenerator/Generators/SpecBase.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Bismarck.CodeGenerator.Generators; - -public abstract record SpecBase(string Namespace, string TargetName) -{ - public string Namespace { get; } = Namespace; - - public string TargetName { get; } = TargetName; -} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/ISchemaGenerator.cs b/src/Bismarck.CodeGenerator/ISchemaGenerator.cs new file mode 100644 index 0000000..9ed0086 --- /dev/null +++ b/src/Bismarck.CodeGenerator/ISchemaGenerator.cs @@ -0,0 +1,9 @@ +using Bismarck.CodeGenerator.Models; + +namespace Bismarck.CodeGenerator; + +public interface ISchemaGenerator +{ + SchemaKind SupportedSchemaKind { get; } + void Generate(GeneratorContext context, IGeneratorSpec spec); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/ISchemaIndexer.cs b/src/Bismarck.CodeGenerator/ISchemaIndexer.cs new file mode 100644 index 0000000..c10dda7 --- /dev/null +++ b/src/Bismarck.CodeGenerator/ISchemaIndexer.cs @@ -0,0 +1,11 @@ +using Bismarck.CodeGenerator.Models; + +using Microsoft.OpenApi.Models; + +namespace Bismarck.CodeGenerator; + +public interface ISchemaIndexer +{ + SchemaKind SupportedSchemaKind { get; } + IGeneratorSpec Index(OpenApiSchema schema, string namespaceName, string name); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Indexers/EnumSchemaIndexer.cs b/src/Bismarck.CodeGenerator/Indexers/EnumSchemaIndexer.cs new file mode 100644 index 0000000..382e541 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Indexers/EnumSchemaIndexer.cs @@ -0,0 +1,25 @@ +using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Models; + +using Microsoft.OpenApi.Any; +using Microsoft.OpenApi.Models; + +namespace Bismarck.CodeGenerator.Indexers; + +public class EnumSchemaIndexer : ISchemaIndexer +{ + public SchemaKind SupportedSchemaKind => SchemaKind.Enum; + + public IGeneratorSpec Index(OpenApiSchema schema, string namespaceName, string name) + => new EnumSpec(namespaceName, name, MapFromAny(schema.Enum)); + + private static IEnumerable MapFromAny(IEnumerable enumValues) => enumValues.SelectMany(v => + { + if (v is OpenApiString s) + { + return new[] { s.Value.SanitizeTypeName() }; + } + + return Enumerable.Empty(); + }); +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Indexers/ObjectSchemaIndexer.cs b/src/Bismarck.CodeGenerator/Indexers/ObjectSchemaIndexer.cs new file mode 100644 index 0000000..c2497c8 --- /dev/null +++ b/src/Bismarck.CodeGenerator/Indexers/ObjectSchemaIndexer.cs @@ -0,0 +1,75 @@ +using System.Collections.Immutable; + +using Bismarck.CodeGenerator.Extensions; +using Bismarck.CodeGenerator.Models; + +using Microsoft.OpenApi.Models; + +namespace Bismarck.CodeGenerator.Indexers; + +public class ObjectSchemaIndexer : ISchemaIndexer +{ + private readonly IDictionary _schemaIndexers; + + public ObjectSchemaIndexer(params ISchemaIndexer[] indexers) + { + _schemaIndexers = indexers + .Select(i => (i.SupportedSchemaKind, i)) + .ToImmutableDictionary(t => t.SupportedSchemaKind, t => t.i); + } + + public SchemaKind SupportedSchemaKind => SchemaKind.Object; + + public IGeneratorSpec Index(OpenApiSchema schema, string namespaceName, string name) + { + throw new NotImplementedException(); + } + + private void GenerateProperties(GeneratorContext context, ModelSpec modelSpec, IEnumerable location, + OpenApiSchema schema) + { + var parentName = location.Last(); + foreach ((string? key, OpenApiSchema? value) in schema.Properties) + { + var propertyType = Type(value, parentName); + + if (_schemaIndexers.TryGetValue(propertyType.Kind, out var indexer)) + { + indexer.Index(value, modelSpec.Namespace, key.SanitizeTypeName()); + } + + switch (propertyType.Kind) + { + case SchemaKind.Simple: + modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); + break; + case SchemaKind.Enum: + modelSpec.Properties.Add(new PropertySpec(propertyType, key.SanitizeTypeName())); + break; + } + } + } + + private static PropertyType Type(OpenApiSchema schema, string parentName) + { + return schema.Type switch + { + "object" => PropertyType.Object(parentName), + "array" => PropertyType.Array(parentName), + "string" when schema.Enum.Count == 0 => PropertyType.Simple(nameof(String)), + "string" when schema.Enum.Count > 0 => PropertyType.Enum(parentName), + "boolean" => PropertyType.Simple(nameof(Boolean)), + "integer" => schema.Format switch + { + "int64" => PropertyType.Simple(nameof(Int64)), + _ => PropertyType.Simple(nameof(Int32)) + }, + "number" => schema.Format switch + { + "float" => PropertyType.Simple(nameof(Single)), + _ => PropertyType.Simple(nameof(Double)) + }, + _ => PropertyType.Object(nameof(Object)) + }; + } +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/Models/GeneratorSpecs.cs b/src/Bismarck.CodeGenerator/Models/GeneratorSpecs.cs new file mode 100644 index 0000000..ded97de --- /dev/null +++ b/src/Bismarck.CodeGenerator/Models/GeneratorSpecs.cs @@ -0,0 +1,38 @@ +namespace Bismarck.CodeGenerator.Models; + +public interface IGeneratorSpec +{ + string TargetName { get; } + string? LookupKey { get; } + string Namespace { get; } + SchemaKind Kind { get; } +} + +public abstract record SpecBase(string Namespace, string TargetName, string? LookupKey) : IGeneratorSpec +{ + public string TargetName { get; } = TargetName; + public string? LookupKey { get; } = LookupKey; + public string Namespace { get; } = Namespace; + public abstract SchemaKind Kind { get; } +} + +public record PropertySpec(PropertyType Type, string Name) +{ + public PropertyType Type { get; } = Type; + + public string Name { get; } = Name; +} + +internal record ModelSpec(string Namespace, string TargetName, string? LookupKey = null) : SpecBase(Namespace, TargetName, + LookupKey) +{ + public IList Properties { get; set; } = new List(); + public override SchemaKind Kind => SchemaKind.Object; +} + +internal record EnumSpec(string Namespace, string TargetName, IEnumerable Values, string? LookupKey = null) + : SpecBase(Namespace, TargetName, LookupKey) +{ + public IEnumerable Values { get; } = Values; + public override SchemaKind Kind => SchemaKind.Enum; +} \ No newline at end of file diff --git a/src/Bismarck.CodeGenerator/SchemaRegistry.cs b/src/Bismarck.CodeGenerator/SchemaRegistry.cs new file mode 100644 index 0000000..9011a3d --- /dev/null +++ b/src/Bismarck.CodeGenerator/SchemaRegistry.cs @@ -0,0 +1,43 @@ +using System.Collections.Immutable; + +using Bismarck.CodeGenerator.Generators; +using Bismarck.CodeGenerator.Models; + +namespace Bismarck.CodeGenerator; + +public class SchemaRegistry +{ + private readonly IDictionary<(SchemaKind, string), IGeneratorSpec> _registeredSpecs; + private readonly IDictionary _generators; + + public SchemaRegistry(params ISchemaGenerator[] generators) + { + _generators = generators + .ToImmutableDictionary(g => g.SupportedSchemaKind, g => g); + _registeredSpecs = new Dictionary<(SchemaKind, string), IGeneratorSpec>(); + } + + public bool AddSpec(IGeneratorSpec spec) => _registeredSpecs.TryAdd((spec.Kind, spec.TargetName), spec); + + public void GenerateSources(GeneratorContext context) + { + var specsByKind = _registeredSpecs.Values + .GroupBy(s => s.Kind) + .ToImmutableDictionary(grp => grp.Key, grp => grp); + + foreach ((SchemaKind kindToGenerate, IGrouping? specs) in specsByKind) + { + if (!_generators.ContainsKey(kindToGenerate)) + { + continue; + } + + var generator = _generators[kindToGenerate]; + + foreach (var generatorSpec in specs) + { + generator.Generate(context, generatorSpec); + } + } + } +} \ No newline at end of file