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

Issues with auto-generated operationId #300

Open
domaindrivendev opened this issue Apr 27, 2015 · 11 comments
Open

Issues with auto-generated operationId #300

domaindrivendev opened this issue Apr 27, 2015 · 11 comments
Milestone

Comments

@domaindrivendev
Copy link
Owner

Currently, the operationId is generated as a combination of controller name and action:

public static string FriendlyId(this ApiDescription apiDescription)
{
    return String.Format("{0}_{1}",
        apiDescription.ActionDescriptor.ControllerDescriptor.ControllerName,
        apiDescription.ActionDescriptor.ActionName);
}

In certain scenarios (e.g. overloading the action name for different routes) this can result in the same operationId for multiple operations, thus breaking the Swagger 2.0 constraint that operation Id's must be unique within a Swagger document.

The obvious solution would be to instead use a combination of HTTP method and route template. However, this presents a challenge related to the second Swagger 2.0 constraint - namely that operationId should be "friendly" as it MAY be used as a method name in auto-generated clients. For example, the following would not be possible in a JavaScript client:

swaggeClient.pets.GET_pets/{id}

The following would be preferable:

swaggerClient.pets.GetPetById

So, we need to come up with a deterministic (consider complex, nested routes) approach for generating an operationId that is both "unique" and "friendly".

Suggestions welcome?

@domaindrivendev domaindrivendev added this to the v5.2.2 milestone Aug 13, 2015
@domaindrivendev domaindrivendev modified the milestones: v6.0.0, v5.2.2 Aug 27, 2015
@wbreza
Copy link

wbreza commented Dec 14, 2015

+1

@whippet-rider
Copy link

@gerektoolhy
Copy link
Contributor

gerektoolhy commented Apr 16, 2016

@domaindrivendev , we faced similar issue, so what we've come up was to generate as friendly operation IDs as we can by following a pattern: <verb><context><resource><action> :

  1. <resource> is the resource name for which the operation is applied, e.g. document, client, company, etc
  2. <action> is any action that is applied on the resource, such as share, upload, etc
  3. <verb> is http action verb:
    • GET (single)-> Get
    • GET (colleciton) -> List
    • POST -> Create,
    • Delete -> Delete
    • Put -> Update
    • Head -> Exists
  4. <context> is usually any parent resources under which resource is nested.

This ends up nicely in the following auto-generated operation IDs. See below table for an example. We have this implemented and working, all covered 100% with unit tests. Obviously, there is an attribute that can be applied to override this auto-generated ID.

Url Verb Operation ID
/v1/documents/{documentId}/share post ShareDocument
/v1/documents/{documentId}/content get GetDocumentContent
/v1/documents/{documentId}/content put UpdateDocumentContent
/v1/documents/{documentId} get GetDocument
/v1/documents/{documentId} post CreateDocument
/v1/documents/{documentId} delete DeleteDocument
/v1/documents/{documentId} put UpdateDocument
/v1/documents/statistics get GetDocumentStatistics
/v1/documents get ListDocuments
/v1/documents post CreateDocuments
/v1/clients/{clientId}/documents/{documentId}/content get GetClientDocumentContent
/v1/clients/{clientId}/documents/{documentId} get GetClientDocument
/v1/clients/{clientId}/documents get ListClientDocuments

@sjmelia
Copy link

sjmelia commented May 10, 2016

Great project and thanks for your hard work!

I am in a position where I am consuming an API, over which I have limited control; that does this, and the non-unique operationIds are causing difficulty using a swagger-js-codegen client library. I've added a pull request #756 that resolves this by appending a number if the operationId has been previously used and therefore satisfies the requirement for uniqueness.

This doesn't necessarily invalidate the above two suggestions around improving friendliness; but it's a simple solution that goes to ensuring Swashbuckle always generates valid swagger.

@RobThree
Copy link

RobThree commented Jun 7, 2016

@dariusdamalakas Any chance you're willing to share the implementation (e.g. sourcecode)?

@gerektoolhy
Copy link
Contributor

@RobThree , we have that as an internal project at the moment, it's quite a bit of a task to get the operation ids right, so its not just a single file that could be shared. Maybe we'll be able to publish this as an open source project, but i wouldn't count on this to be done quickly.

@RobThree
Copy link

RobThree commented Jun 7, 2016

@dariusdamalakas ok, no problem. I'll have a look at implementing this myself then.

@moander
Copy link

moander commented Aug 18, 2016

operationId MUST be unique according to the Swagger 2.0 specification.

  • Unique string used to identify the operation. The id MUST be unique among all operations described in the API. Tools and libraries MAY use the operationId to uniquely identify an operation, therefore, it is recommended to follow common programming naming conventions.

https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object

@sjmelia
Copy link

sjmelia commented Aug 19, 2016

@moander yep; i've got a PR #756 that resolves this by appending an incrementing number to the name of any overloaded methods.

@RobThree
Copy link

For those interested; another option is just to get rid of the OperationId:

public class NoOperationIdFilter : IOperationFilter
{
    public void Apply(Operation operation, OperationFilterContext context)
    {
        operation.OperationId = null;
    }
}

This will get rid of the OperationId element in the swagger document.

@gmariano
Copy link

gmariano commented May 3, 2021

I recently ran into the same problem and I solved it using Swashbuckle Operation Filter.
The following code generates operation ids based on controller and action names, adding a numeric suffix to the operationId if one with the same id already exists:

using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace Api.Swagger
{
    [UsedImplicitly]
    public class SwaggerOperationIdFilter : IOperationFilter
    {
        private readonly Dictionary<string, string> _swaggerOperationIds = new Dictionary<string, string>();

        public void Apply(OpenApiOperation operation, OperationFilterContext context)
        {
            if (!(context.ApiDescription.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor))
            {
                return;
            }

            if (_swaggerOperationIds.ContainsKey(controllerActionDescriptor.Id))
            {
                operation.OperationId = _swaggerOperationIds[controllerActionDescriptor.Id];
            }
            else
            {
                var operationIdBaseName = $"{controllerActionDescriptor.ControllerName}_{controllerActionDescriptor.ActionName}";
                var operationId = operationIdBaseName;
                var suffix = 2;
                while (_swaggerOperationIds.Values.Contains(operationId))
                {
                    operationId = $"{operationIdBaseName}{suffix++}";
                }

                _swaggerOperationIds[controllerActionDescriptor.Id] = operationId;
                operation.OperationId = operationId;
            }
        }
    }
}
services.AddSwaggerGen(options =>
            {
                options.OperationFilter<SwaggerOperationIdFilter>();
            ...

https://g-mariano.medium.com/generate-readable-apis-clients-by-setting-unique-and-meaningful-operationid-in-swagger-63d404f32ff8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants