swagger @ApiParam ignores certain properties
Asked Answered
C

4

13

I have a Spring Boot Project with springfox-swagger2 2.7.0 And I have the following controller:

@Api(tags = { "Some" }, description = "CRUD for Some Stuff")
@RestController
@RequestMapping(path = "/some")
public class SomeController {

  @ApiOperation(value = "Get some")
  @GetMapping(value = "{someId}", produces = MediaType.APPLICATION_JSON_VALUE)
  public Response getSomeById(@PathVariable("someId") Id someId) {
    return ...;
  }
...
}

I want to control what is displayed in the docs by annotating the Id class, and this is working only for some parts of the annotation, but not all. The Id class (which has a registered converter from String to Id):

public class Id {

  @ApiParam(value = "This is the description", defaultValue = "1f1f1f",required = true, name = "someId", type = "string")
  private final Long id;

  public Id(Long id) {
    this.id = id;
  }

  public Long getId() {
    return id;
  }
}

Now the Swagger JSON returned looks as follows:

"parameters":[{
  "name":"id",
  "in":"query",
  "description":"This is the description",
  "required":true,
  "type":"integer",
  "default":"1f1f1f",
  "format":"int64"
}]

My question (or possibly bug report) is: why are some parts of the @ApiParam annotation used (like value, defaultValue and required), but others aren't, like name and type? Why does it seem like I cannot change the name or type here? For my particular use case, the latter is the one I would want to change to string.

Update

I've settled on adding the following component with the help from skadya.

@Component
public class OverrideSwaggerApiParamBuilder implements 
ExpandedParameterBuilderPlugin {

  @Override
  public boolean supports(DocumentationType type) {
    return DocumentationType.SWAGGER_2 == type;
  }

  @Override
  public void apply(ParameterExpansionContext context) {
    Optional<ApiParam> apiParamOptional = findApiParamAnnotation(context.getField().getRawMember());
    if (apiParamOptional.isPresent()) {
      ApiParam param = apiParamOptional.get();
      context.getParameterBuilder()
          .name(param.name())
          .modelRef(new ModelRef(param.type()))
          .build();
    }
  }
}

The Authors of springfox feel this could be a bug: https://github.com/springfox/springfox/issues/2107

Constitutionally answered 15/11, 2017 at 21:29 Comment(0)
B
6

By default, @ApiParam attribute 'name' and 'type' are used to override the parameter name and detected type of direct parameters specified in the API method. When you use @ApiParam on an field, the type and name are deduced by the field's name and its declared type and overriden value for name and type are not considered. (It looks by design in springfox, you may have a look at implementation springfox.documentation.swagger.readers.parameter.SwaggerExpandedParameterBuilder)

If you still wish to alter this behavior, you may register an custom implementation of springfox.documentation.spi.service.ExpandedParameterBuilderPlugin interlace.

For e.g.

@Component
public class OverrideSwaggerApiParamNameBuilder implements ExpandedParameterBuilderPlugin {

    @Override
    public boolean supports(DocumentationType type) {
        return DocumentationType.SWAGGER_2 == type;
    }

    @Override
    public void apply(ParameterExpansionContext context) {
        Optional<ApiParam> apiParamOptional = findApiParamAnnotation(context.getField().getRawMember());
        if (apiParamOptional.isPresent()) {
            fromApiParam(context, apiParamOptional.get());
        }
    }

    private void fromApiParam(ParameterExpansionContext context, ApiParam apiParam) {
        context.getParameterBuilder()
                .name(emptyToNull(apiParam.name()));
    }

    private String emptyToNull(String str) {
        return StringUtils.hasText(str) ? str : null;
    }
}

Hope it helps.

Biogen answered 21/11, 2017 at 22:59 Comment(1)
Eventually this did the trick, I changed the code a bit to make it a bit more simple and used the modelRef() to set the type. I've updated my question to reflect his.Constitutionally
O
0

A more complete solution that actually compiles, and takes into account the setting of the type from both ApiParam type property or Model dataType property:

@Component
@Order(SwaggerPluginSupport.SWAGGER_PLUGIN_ORDER + 1000)
public class OverrideSwaggerApiParamTypeBuilder extends 
SwaggerExpandedParameterBuilder implements ExpandedParameterBuilderPlugin {

public OverrideSwaggerApiParamTypeBuilder(DescriptionResolver descriptions, EnumTypeDeterminer enumTypeDeterminer) {
    super(descriptions, enumTypeDeterminer);
}

@Override
public boolean supports(DocumentationType type) {
    return DocumentationType.SWAGGER_2 == type;
}

public void apply(ParameterExpansionContext context) {
    super.apply(context);
    Optional<ApiModelProperty> apiModelPropertyOptional = context.findAnnotation(ApiModelProperty.class);
    if (apiModelPropertyOptional.isPresent()) {
        if(!StringUtils.isAllEmpty(apiModelPropertyOptional.get().dataType())) {
            context.getParameterBuilder().modelRef(new ModelRef(apiModelPropertyOptional.get().dataType()));
        }
    }

    Optional<ApiParam> apiParamOptional = context.findAnnotation(ApiParam.class);
    if (apiParamOptional.isPresent()) {
        if(!StringUtils.isAllEmpty(apiParamOptional.get().type())) {
            context.getParameterBuilder().modelRef(new ModelRef(apiParamOptional.get().type()));
        }
    }

}

}

Orchidectomy answered 12/9, 2019 at 8:11 Comment(0)
M
-1

Ideally you need to use @ApiParam with the method parameters whereas @ApiModelProperty with the model properties.

public @interface ApiParam {
    /**
     * The parameter name.
     * The name of the parameter will be derived from the field/method/parameter name,
     * however you can override it.
     * Path parameters must always be named as the path section they represent.
     */
    String name() default "";

Not sure if type attribute is there but below is the way for dealing with types:

public @interface ApiModelProperty {

    /**
     * The data type of the parameter.
     * This can be the class name or a primitive. The value will override the data type as read from the class
     * property.
     */
    String dataType() default "";

......
Marthamarthe answered 20/11, 2017 at 12:59 Comment(1)
As I mentioned, some parts of the @ApiParam are being picked up (like value that becomes description, but other aren't.Constitutionally
M
-1

I am using version 2.6.1 and unable to find "type" attribute in @ApiParam whereas I can see you are using "type" with this. So make sure it is available to use. I also mentioned @ApiModelProperty provides dataType() to handle the scenario you mentioned.

Marthamarthe answered 21/11, 2017 at 8:22 Comment(1)
The type attribute is available, otherwise my code would not compile. It can be seen here: github.com/swagger-api/swagger-core/blob/v1.5.13/modules/…Constitutionally

© 2022 - 2024 — McMap. All rights reserved.