Skip to content

Duplicated operation parameters generated for endpoints with multiple consuming media types #708

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
valentinabojan opened this issue Jun 2, 2020 · 1 comment
Labels
question Further information is requested

Comments

@valentinabojan
Copy link

Hi,

I have 2 endpoints that differ only by the media type they consume. From #71 I understand that this kind of scenario is well supported by springdoc-openapi. I use the latest 1.4.0 version on springdoc-openapi.

@PostMapping(value = "/search", consumes = APPLICATION_JSON_VALUE)
public ResponseDto search(@RequestBody RequestDto searchRequestDto) {
    ...
}

@PostMapping(value = "/search", consumes = MULTIPART_FORM_DATA_VALUE)
public ResponseDto searchByFile(@RequestParam(value = "file") MultipartFile file) {
    ...
}

Indeed, looking in the generated swagger-ui I see the /search operation with a select box containing the 2 media types, which is what I wanted to get.

However, because I have a configuration that adds 2 header parameters to every operation, for the above /search endpoint I will have the 2 header parameters duplicated in the generated docs.

@Bean
public OperationCustomizer customize() {
    return (operation, handlerMethod) -> operation
        .addParametersItem(
            new Parameter()
                .in("header")
                .required(true)
                .name("My header param 1")
                .schema(new Schema<String>().type("string")))
        .addParametersItem(
            new Parameter()
                .in("header")
                .required(false)
                .name("My header param 2")
                .schema(new Schema<String>().type("string")));
}

This is how the generated json looks like:

{
  "paths": {
    "/search": {
      "post": {
        "operationId": "search_1",
        "parameters": [
          {
            "name": "My header param 1",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "My header param 2",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "My header param 1",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "My header param 2",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
        }
      }
    }
  }
}

Is this a known issue? Am I missing something?
Thank you!

@bnasslahsen
Copy link
Collaborator

Hi @valentinabojan,

The Operation type from swagger-api contains a List<Parameter> (And not a Set) which explains the behaviour your are facing.

OperationCustomizer is called just after operation processing is finished, and for each operation (search, searchByFile) tjhe parameters are concatenated inside the List.

First solution:

@Bean
public OperationCustomizer customize() {
	return (operation, handlerMethod) -> {
		Parameter parameter1 = new Parameter()
				.in("header")
				.required(true)
				.name("My header param 1")
				.schema(new Schema<String>().type("string"));
		Parameter parameter2 = new Parameter()
				.in("header")
				.required(false)
				.name("My header param 2")
				.schema(new Schema<String>().type("string"));
		List<Parameter> exisitingParameters = operation.getParameters();
		if (CollectionUtils.isEmpty(exisitingParameters)) {
			operation.addParametersItem(parameter1).addParametersItem(parameter2);
		}
		else {
			if (!exisitingParameters.contains(parameter1))
				operation.addParametersItem(parameter1);
			if (!exisitingParameters.contains(parameter2))
				operation.addParametersItem(parameter1);
		}
		return operation;
	};
	}

A Better option, is to use OpenApiCustomiser, where you can have access to the final image of the OpenAPI object before being returned:

@Bean
public OpenApiCustomiser customerGlobalHeaderOpenApiCustomiser() {
	Parameter parameter1 = new Parameter()
			.in("header")
			.required(true)
			.name("My header param 1")
			.schema(new Schema<String>().type("string"));
	Parameter parameter2 = new Parameter()
			.in("header")
			.required(false)
			.name("My header param 2")
			.schema(new Schema<String>().type("string"));

	return openApi -> openApi.getPaths().values().stream().flatMap(pathItem -> pathItem.readOperations().stream())
			.forEach(operation -> operation.addParametersItem(parameter1).addParametersItem(parameter2));
}

@bnasslahsen bnasslahsen added the question Further information is requested label Jan 9, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants