refactor: split into indexers and generators

This commit is contained in:
Peter Kurfer 2022-12-05 11:34:00 +01:00
parent 82c2edce0e
commit 79c9b848c6
No known key found for this signature in database
9 changed files with 224 additions and 56 deletions

View file

@ -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<string> Values) : SpecBase(Namespace, TargetName)
{
public IEnumerable<string> 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<string> MapFromAny(IEnumerable<IOpenApiAny> enumValues) => enumValues.SelectMany(v =>
{
if (v is OpenApiString s)
{
return new[]
{
s.Value.SanitizeTypeName()
};
}
return Enumerable.Empty<string>();
});
}

View file

@ -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<PropertySpec> Properties { get; set; } = new List<PropertySpec>();
}
internal class ModelGenerator
{
private readonly Template _modelTemplate;
private readonly EnumGenerator _enumGenerator;
private readonly IDictionary<SchemaKind, ISchemaIndexer> _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, ISchemaIndexer> { { SchemaKind.Enum, new EnumSchemaIndexer() } };
}
public void GenerateModelSchema(GeneratorContext context, IDictionary<string, OpenApiSchema> schemata, string namespaceName)
public void GenerateModelSchema(GeneratorContext context, IDictionary<string, OpenApiSchema> 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<string> location, OpenApiSchema schema)
private void GenerateProperties(GeneratorContext context, ModelSpec modelSpec, IEnumerable<string> 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;
}

View file

@ -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;
}

View file

@ -0,0 +1,9 @@
using Bismarck.CodeGenerator.Models;
namespace Bismarck.CodeGenerator;
public interface ISchemaGenerator
{
SchemaKind SupportedSchemaKind { get; }
void Generate(GeneratorContext context, IGeneratorSpec spec);
}

View file

@ -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);
}

View file

@ -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<string> MapFromAny(IEnumerable<IOpenApiAny> enumValues) => enumValues.SelectMany(v =>
{
if (v is OpenApiString s)
{
return new[] { s.Value.SanitizeTypeName() };
}
return Enumerable.Empty<string>();
});
}

View file

@ -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<SchemaKind, ISchemaIndexer> _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<string> 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))
};
}
}

View file

@ -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<PropertySpec> Properties { get; set; } = new List<PropertySpec>();
public override SchemaKind Kind => SchemaKind.Object;
}
internal record EnumSpec(string Namespace, string TargetName, IEnumerable<string> Values, string? LookupKey = null)
: SpecBase(Namespace, TargetName, LookupKey)
{
public IEnumerable<string> Values { get; } = Values;
public override SchemaKind Kind => SchemaKind.Enum;
}

View file

@ -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<SchemaKind, ISchemaGenerator> _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<SchemaKind, IGeneratorSpec>? specs) in specsByKind)
{
if (!_generators.ContainsKey(kindToGenerate))
{
continue;
}
var generator = _generators[kindToGenerate];
foreach (var generatorSpec in specs)
{
generator.Generate(context, generatorSpec);
}
}
}
}