SteveDunn / Vogen

A semi-opinionated library which is a source generator and a code analyser. It Source generates Value Objects
Apache License 2.0
782 stars 46 forks source link

OpenAPI Support Extension #514

Closed Aragas closed 3 months ago

Aragas commented 10 months ago

Describe the feature

Add support for OpenAPI/Swagger schema generation

public class VogenSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        if (context.Type.GetCustomAttribute<ValueObjectAttribute>() is not { } attribute)
            return;

        // Since we don't hold the actual type, we ca only use the generic attribute
        var type = attribute.GetType();
        if (!type.IsGenericType || type.GenericTypeArguments.Length != 1)
            return;
        var schemaValueObject = context.SchemaGenerator.GenerateSchema(type.GenericTypeArguments[0], context.SchemaRepository, context.MemberInfo, context.ParameterInfo);
        CopyPublicProperties(schemaValueObject, schema);

        // By adding `public Type UnderlyingType { get; }` to `ValueObjectAttribute` this can be simplified
        var schemaValueObject = context.SchemaGenerator.GenerateSchema(attribute.UnderlyingType, context.SchemaRepository, context.MemberInfo, context.ParameterInfo);
        CopyPublicProperties(schemaValueObject, schema);
    }

    private static void CopyPublicProperties<T>(T oldObject, T newObject) where T : class
    {
        const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

        if (ReferenceEquals(oldObject, newObject)) return;

        var type = typeof(T);
        var propertyList = type.GetProperties(flags);
        if (propertyList.Length <= 0) return;

        foreach (var newObjProp in propertyList)
        {
            var oldProp = type.GetProperty(newObjProp.Name, flags)!;
            if (!oldProp.CanRead || !newObjProp.CanWrite) continue;

            var value = oldProp.GetValue(oldObject);
            newObjProp.SetValue(newObject, value);
        }
    }
}

Usage:

services.AddSwaggerGen(opt =>
{
    opt.SchemaFilter<VogenSchemaFilter>();
}
SteveDunn commented 10 months ago

Thanks for another great suggestion. I think for this one too, we'll need a new NuGet package as wouldn't want the default package to take on extra dependencies (assuming aspnet core and Microsoft.OpenApi?)

JoshuaNitschke commented 9 months ago

+1

SteveDunn commented 4 months ago

Hi @Aragas - looking at this one, are you saying that you want Vogen to generate the implementation above (the class that you provided), or are you suggesting that it generates an implementation of ISchemaFilter for every value object that it generates?

Aragas commented 4 months ago

Hi @Aragas - looking at this one, are you saying that you want Vogen to generate the implementation above (the class that you provided), or are you suggesting that it generates an implementation of ISchemaFilter for every value object that it generates?

Yea, it's a global extension. We don't need a per-object implementation here

SteveDunn commented 3 months ago

Implemented in 4.0.5