Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow IDocument to be modified before rendering #344

Merged
merged 7 commits into from
Mar 21, 2022

Conversation

ThomasBleijendaal
Copy link
Contributor

This PR allows for adding IDocumentFilters for modifying the generated IDocument just before it is rendered. Sometimes modifying the IDocument at runtime is the only way to implement certain functionality in systems that depend on the generated documentation.

Unit tests and documentation are still missing and I will add those once I know this approach is a good one.

After adding the unit tests I'll probably remove the new VersionPrefixDocumentFilter, I used that filter to test the functionality, but I think including this in the samples is distracting.

--

I'm aware that #280 exists but I wrote most code before I discovered that PR. My approach is similar, but is applicable for all use cases (in-process & out-of-process and static & IoC). I can also merge this PR with that one, let me know.

Copy link
Contributor

@justinyoo justinyoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasBleijendaal Thanks for the PR! As the other PRs have been merged, yours have got some conflicts. Would you please resolve those conflicts? Then, we can start discussion.

@ThomasBleijendaal
Copy link
Contributor Author

@justinyoo I've fixed the conflicts.

Copy link
Contributor

@justinyoo justinyoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since v1.1.0 release, the sample apps have been significantly updated. Therefore, your changes have also been affected. Would you please check that out?

@ThomasBleijendaal
Copy link
Contributor Author

@justinyoo I've rebased this PR on the latest changes on main.

@justinyoo
Copy link
Contributor

@ThomasBleijendaal It might take time for review because there are many files involved in this PR. Thanks for your patience!

Copy link
Contributor

@justinyoo justinyoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasBleijendaal Thanks for the PR! It looks promising. As far as I understand, this approach mimics the OpenApiDocument built from the Build() method. I think both #367 and #368 can be updated with this approach.

Would you please add relevant test codes and integration testing codes to validate your approach?

@justinyoo justinyoo added this to the Release 202203 milestone Feb 25, 2022
@justinyoo justinyoo added documentation Improvements or additions to documentation enhancement New feature or request v1.3.0 labels Feb 25, 2022
@ThomasBleijendaal
Copy link
Contributor Author

I've updated the PR and added unit tests and added one integration test to validate the working of the document filter.

Copy link

@alefranz alefranz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work @ThomasBleijendaal ! This would be a fantastic addition! I was looking for something like this in #376
I've left a couple comments, but of course feel free to ignore as I am not involved in the project.
🤞 it can be released soon. Happy to help if needed!

@ThomasBleijendaal
Copy link
Contributor Author

Great work @ThomasBleijendaal ! This would be a fantastic addition! I was looking for something like this in #376 I've left a couple comments, but of course feel free to ignore as I am not involved in the project. 🤞 it can be released soon. Happy to help if needed!

Thanks. I've addressed your suggestions. I hope it can be released soon, this feature is the only thing that prevents us from fully migrating to this package.

@morrisonbrett
Copy link

Very much want this PR to get merged. It's holding us up as well with our SwaggerUI documentation.

Copy link
Contributor

@justinyoo justinyoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ThomasBleijendaal Thanks for waiting! It all LGTM.

@justinyoo justinyoo merged commit 3c9a761 into Azure:main Mar 21, 2022
@tyson-benson
Copy link

This was a very useful addition. I was looking at using it to add fluent validation to the generated schema. Similar to MicroElements.Swashbuckle.FluentValidation.

The only thing that seems to be blocking this is the absence of support for dependency injection in instanciating the IOpenApiConfigurationOptions or IDocumentFilter. I wanted to get a reference to FluentValidation.IValidatorFactory so that I could augment the generated OpenAPI schemas with validation info, but I'm not sure the best way to get it without DI. I don't really want to use a service locator pattern but I might have to resort to it if DI support isn't on the cards.

@tyson-benson
Copy link

Ah, I think i've answered my own question, just had to sit with the problem a little longer. I decided to create a class that inherits from OpenApiHttpTriggerContext, add my own constructor with the dependencies I wanted, and override the OpenApiConfigurationOptions property to construct another class that inherits DefaultOpenApiConfigurationOptions, passing in the dependencies I wanted to flow through to my implementation of IDocumentFIlter.

That was a mouthful. Just in case that was confusing, here's a couple code snippets for anyone who comes across this with the same requirements as me.

Program.cs

public class Program
{
    public static async Task Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults(worker =>
            {
                // ... other middleware and stuff ...

                //Recommended serialisation settings for OpenAPI extensions, extending default settings from:
                //https://github.com/Azure/azure-functions-openapi-extension/blob/main/src/Microsoft.Azure.Functions.Worker.Extensions.OpenApi/Extensions/FunctionsWorkerApplicationBuilderExtensions.cs
                var settings = NewtonsoftJsonObjectSerializer.CreateJsonSerializerSettings();
                settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                settings.NullValueHandling = NullValueHandling.Ignore;
                
                //with one additional preference of mine
                settings.Converters.Add(new StringEnumConverter());
                worker.UseNewtonsoftJson(settings);
            })
            .ConfigureOpenApi()
            .ConfigureServices(services =>
            {
                //...other config and service stuff...

                //Override OpenApi context to support DI
                services.AddTransient<IValidatorFactory, ServiceProviderValidatorFactory>();
                services.AddSingleton<IOpenApiHttpTriggerContext, OpenApi.HomesOpenApiHttpTriggerContext>();
            })
            .Build().Run();
    }
}

MyOpenApiConfig.cs

public class MyOpenApiHttpTriggerContext : OpenApiHttpTriggerContext
{
    private readonly ILogger<HomesOpenApiHttpTriggerContext> _logger;
    private readonly IValidatorFactory _validatorFactory;
    private IOpenApiConfigurationOptions _configOptions;

    public MyOpenApiHttpTriggerContext(ILogger<MyOpenApiHttpTriggerContext> logger, IValidatorFactory validatorFactory) : base()
    {
        _logger = logger;
        _validatorFactory = validatorFactory;
    }
    public override IOpenApiConfigurationOptions OpenApiConfigurationOptions
    {
        get
        {
            if (_configOptions.IsNullOrDefault())
            {
                _configOptions = new OpenApiConfig(_logger, _validatorFactory);
            }

            return _configOptions;
        }
    }
}

public class MyOpenApiConfig : DefaultOpenApiConfigurationOptions
{
    public MyOpenApiConfig(ILogger logger, IValidatorFactory validatorFactory)
    {
        OpenApiVersion = OpenApiVersionType.V3;
        
        Info.Title = "My API";
        // etc...
        
        DocumentFilters.Add(new MyOpenApiDocumentFilter(logger, validatorFactory));
    }
}

internal class MyOpenApiDocumentFilter : IDocumentFilter
{
    private readonly IValidatorFactory _validatorFactory;
    private readonly ILogger _logger;

    public MyOpenApiDocumentFilter(ILogger logger, IValidatorFactory validatorFactory)
    {
        _logger = logger;
        _validatorFactory = validatorFactory;
    }

    public void Apply(IHttpRequestDataObject req, OpenApiDocument document)
    {
        using (_logger?.BeginScope(nameof(MyOpenApiDocumentFilter)))
        {
            var schemasWithValidaation = document.Components.Schemas.Where(kvp => (kvp.Key.StartsWith("create") || kvp.Key.StartsWith("update")) && kvp.Key.EndsWith("Command"));
            foreach (var schema in schemasWithValidaation)
            {
                //apply fluent validation to openapi schemas...
            }
        }
    }
}

Derich367 pushed a commit to Derich367/azure-functions-openapi-extension that referenced this pull request Jan 30, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation enhancement New feature or request v1.3.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature request: Add support for manipulating operations/schemas generation Support for IDocumentFilter
6 participants