Skip to content

Schema has both $ref and attributes #944

@Sam-Kruglov

Description

@Sam-Kruglov

Describe the bug
My config (api-docs.yaml.txt):

openapi: 3.0.1
paths:
  /api/users/self:
    get:
      tags:
      - users
      operationId: getMe
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                type: string
        default:
          $ref: '#/components/responses/default'
components:
  schemas:
    ErrorResponse:
      type: object
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string
  responses:
    default:
      description: other error responses
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
      $ref: '#/components/responses/default'

I noticed that componenets.responses[0].$ref is included in the yaml which is not supposed to be there.

Here's my java code (Spring Boot 2.4.0, Springdoc 1.5.0):

    public static final String JWT_SECURITY_SCHEME = "jwt";

    private final List<CommonResponse> commonResponses = new LinkedList<>();

    @Bean
    public OpenAPI openApi() {
        val components = new Components();
        components.addSecuritySchemes(JWT_SECURITY_SCHEME,
                new SecurityScheme()
                        .type(Type.HTTP)
                        .scheme("bearer")
                        .bearerFormat("JWT")
        );
        addCommonSecurityResponses(components);
        addDefaultErrorResponse(components);
        return new OpenAPI()
                .components(components)
                .addSecurityItem(new SecurityRequirement().addList(JWT_SECURITY_SCHEME));
    }

    private void addDefaultErrorResponse(Components components) {
        val schema = createSchema(components, ErrorResponse.class);
        val key = ApiResponses.DEFAULT;
        val response = new ApiResponse()
                .$ref(key)
                .description("other error responses")
                .content(new Content().addMediaType(
                        org.springframework.http.MediaType.APPLICATION_JSON_VALUE,
                        new MediaType().schema(schema)
                ));
        components.addResponses(key, response);
        commonResponses.add(new CommonResponse(key, response.get$ref()));
    }

    private Schema<?> createSchema(Components components, Class<?> clazz) {
        val type = new AnnotatedType(clazz).resolveAsRef(true);
        val context = new ModelConverterContextImpl(ModelConverters.getInstance().getConverters());
        val schema = context.resolve(type);
        context.getDefinedModels().forEach(components::addSchemas);
        return schema;
    }

    @Value
    private static class CommonResponse {
        String httpStatusCode;
        String reference;
    }

    private void addCommonSecurityResponses(Components components) {
        Stream.of(HttpStatus.UNAUTHORIZED, HttpStatus.FORBIDDEN).forEach(securityError -> {
                    val httpCodeStr = String.valueOf(securityError.value());
                    val response = new ApiResponse()
                            .$ref(httpCodeStr)
                            .description(securityError.getReasonPhrase())
                            .addHeaderObject(
                                    HttpHeaders.WWW_AUTHENTICATE,
                                    new Header().description("https://tools.ietf.org/html/rfc6750#section-3")
                            )
                            .content(new Content());
                    components.addResponses(httpCodeStr, response);
                    commonResponses.add(new CommonResponse(httpCodeStr, response.get$ref()));
                }
        );
    }

    

    /**
     * see https://swagger.io/docs/specification/describing-responses/#reuse
     */
    @Bean
    OperationCustomizer defaultResponsesOperationCustomizer() {
        return ((operation, handlerMethod) -> {
            commonResponses.forEach(commonResponse -> {
                val response = new ApiResponse().$ref(commonResponse.getReference());
                operation.getResponses().addApiResponse(commonResponse.getHttpStatusCode(), response);
            });
            return operation;
        });
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions