Skip to content

Wrong operationid generated #675

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

Closed
tezine opened this issue May 18, 2020 · 8 comments
Closed

Wrong operationid generated #675

tezine opened this issue May 18, 2020 · 8 comments

Comments

@tezine
Copy link

tezine commented May 18, 2020

Hello,
This bug is probably the same as bug 96.
OperationID continues to be generated with a suffix, even when there's no other method with the same operationid in the same class.

This issue is easy to reproduce:

  1. Create two controllers classes.
  2. Create one method with the same name in both classes.
  3. http://yoursite/v3/api-docs will produce one operationid correct and the other will have the 1 suffix.

Ex:
MyFirstClass.java:

    @Operation(operationId = "save")
    @PostMapping(path = "/")
    @ApiResponse(responseCode = "200", description = "Success")
    public ResponseEntity save(@RequestBody String request) {
      return null;
    }
MySecondClass.java:
    @Operation(operationId = "save")
    @PostMapping(path = "/")
    @ApiResponse(responseCode = "200", description = "Success")
    public ResponseEntity save(@RequestBody String request) {
      return null;
    }

After generation, one method will have the correct operationid (save), but the other one will have a wrong operationid(save_1)
This bug is happening with SpringDoc 1.3.0, SpringBoot 2.2.6, openJDK 11 64bits on Windows.

@bnasslahsen
Copy link
Collaborator

Hi @tezine,

This is not a bug, but a normal behaviour as we don't want to generate incorrect spec:

  • The operationId should be unique
  • When it not the case (for example in case of polymorphism), springdoc generates a unique id

If you have two operations with the same id, your spec will be incorrect.

Please make sure you use the last stable version: v1.3.9 indeed.

@tezine
Copy link
Author

tezine commented May 19, 2020

Hi @tezine,

This is not a bug, but a normal behaviour as we don't want to generate incorrect spec:

  • The operationId should be unique
  • When it not the case (for example in case of polymorphism), springdoc generates a unique id

If you have two operations with the same id, your spec will be incorrect.

Please make sure you use the last stable version: v1.3.9 indeed.

Thank you @bnasslahsen for the quick response. It just think it doesn't make any sense. For instance, I'm generating REST calls to Angular in typescript. In this situation, I have two calls:

MyFirstClassService.save() and MySecondClassService.save_1(). //It doesn't make sense. 

@bnasslahsen
Copy link
Collaborator

@tezine,

I invite you to read the spec: https://swagger.io/docs/specification/paths-and-operations/

operationId is an optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API.

@jdbranham
Copy link

In my case, I've got lots of rest controllers that extend a generalized abstract crud controller.

Rather than generating operations with _n after, it would be cool if we could use a class level annotation to do a pre/post fix on the generated operation id. Otherwise I've got to override all the superclass methods and re-add all the annotations in the subclass.
Is that something PR worthy?

@jdbranham
Copy link

Nvm - I just realized there is an OperationCustomizer class while browsing the source code -

operationCustomizers.ifPresent(customizers -> customizers.forEach(customizer -> customizer.customize(operation, handlerMethod)));

I was able to provide a bean and customize my generated names =]

Thanks for a great project!!

@Bean
public OperationCustomizer operationIdCustomizer() {
  OperationCustomizer c = new OperationCustomizer() {
    @Override
    public Operation customize(Operation operation, HandlerMethod handlerMethod) {
	Class<?> superClazz = handlerMethod.getBeanType().getSuperclass();
	if (Objects.nonNull(superClazz) && superClazz.isAssignableFrom(CrudController.class)) {
		String beanName = handlerMethod.getBeanType().getSimpleName();
		operation.setOperationId(String.format("%s_%s", beanName, handlerMethod.getMethod().getName()));
	}
	return operation;
    }
  };
  return c;
}

@i3130002
Copy link

i3130002 commented Mar 31, 2021

This does not require additional dependency and Is also easy to customize naming. (Main idea from here)

app/api/src/main/java/com/observatory/api/config/SwaggerIncludeMissingNicknameIntoUniqueIdReader.java

package com.observatory.api.config;

import com.google.common.base.Optional;
import io.swagger.annotations.ApiOperation;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import springfox.documentation.service.Operation;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.OperationBuilderPlugin;
import springfox.documentation.spi.service.contexts.OperationContext;
import springfox.documentation.swagger.common.SwaggerPluginSupport;

import java.util.Locale;

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public class SwaggerIncludeMissingNicknameIntoUniqueIdReader implements OperationBuilderPlugin {

    @Override
    public void apply(OperationContext context) {
        Optional<ApiOperation> methodAnnotation = context.findControllerAnnotation(ApiOperation.class);
        Operation operationBuilder = context.operationBuilder().build();

        String uniqueId = operationBuilder.getUniqueId();
        if(operationBuilder.getTags().stream().findFirst().get().isEmpty())
            throw new RuntimeException("operationBuilder.getTags().stream().findFirst()");
        uniqueId = uniqueId.substring(0,1).toUpperCase(Locale.ROOT)+ uniqueId.substring(1);
        uniqueId = uniqueId.replaceAll("[_].+","");
        String tag = operationBuilder.getTags().stream().findFirst().get();
        tag = tag.replace("-controller","s");
        int index = tag.indexOf("-");
        while (index >= 0) {
            tag = tag.substring(0,index) + tag.substring(index+1,index+2).toUpperCase(Locale.ROOT)+ tag.substring(index+2);
            index = tag.indexOf("-");
        }
        uniqueId = tag + uniqueId;

        // If nickname exists, populate the value of nickname annotation into uniqueId
        String fillId = methodAnnotation.transform(ApiOperation::nickname).or(uniqueId);
        context.operationBuilder().uniqueId(fillId);
        context.operationBuilder().codegenMethodNameStem(fillId);
    }

    @Override
    public boolean supports(DocumentationType delimiter) {
        return SwaggerPluginSupport.pluginDoesApply(delimiter);
    }
}

@kozla13
Copy link

kozla13 commented Oct 8, 2024

How can i handle 2 paths

@GetMapping({"/app-statistic", "/members/app-statistic"}) //other security rules
public AppStatistic getAppStatistic() {

}

@bnasslahsen
Copy link
Collaborator

@kozla13 ,

You will have unique operation ids generated by default.
If you want use your custom operation ids, use OperationCustomizer.

@springdoc springdoc locked as resolved and limited conversation to collaborators Oct 8, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants