Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I manually document Swagger datamodel for a JAX-RS parameter?

Tags:

java

swagger

Context

Let's say that I have a simple data class in Java:

public class Person {
    private final String name;
    private final int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    int String getAge() {
        return age;
    }
}

Note: in practice, I use Immutables to generate this, but am showing a POJO here for the sake of simplicity.

To document the model of the GET response, even if the return type is Response, I can refer to the class in @ApiOperation:

@GET
@ApiOperation(response = Person.class)
public Response getPerson() {
    return Response.ok(new Person("Julien", 28)).build();
}

Based on that, Swagger will document this correctly:

Model:

Person {
  name (string),
  age (number)
}

Example value:

{
  "name": "string",
  "age": 0
}

To document the model of the POST body, I use the class directly in the code, Swagger finds it and documents it as desired:

@POST
@ApiOperation(response = Person.class)
public Response addPerson(Person newPerson) {
    return Response.ok(store.insert(newPerson)).build();
}

Problem

I want to support partial updates as well. I cannot use the POJO itself, because all fields are mandatory in the POJO, I rely on that to get safe checks and clear error messages when invalid JSON is sent to e.g. the POST method.

In my actual use-case, my datamodel contains maps. I want users to be able to specify a certain key in the update and set the value to null, to delete elements from an existing maps.

I chose to support PATCH requests where the body is typed as a plain JsonNode. That allows any JSON to be received by my server and I can apply the updates as I wish.

@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
public Response updatePerson(@PathParam("name") String name, JsonNode update) {
    return Response.ok(store.update(name, update)).build();
}

I'm happy with the result, except that Swagger now documents the partial update's model with the properties of the JsonNode Java object:

Model:

JsonNode {
  array (boolean, optional),
  null (boolean, optional),
  number (boolean, optional),
  float (boolean, optional),
  pojo (boolean, optional),
  valueNode (boolean, optional),
  containerNode (boolean, optional),
  object (boolean, optional),
  missingNode (boolean, optional),
  nodeType (string, optional) = ['ARRAY', 'BINARY', 'BOOLEAN', 'MISSING', 'NULL', 'NUMBER', 'OBJECT', 'POJO', 'STRING'],
  integralNumber (boolean, optional),
  floatingPointNumber (boolean, optional),
  short (boolean, optional),
  int (boolean, optional),
  long (boolean, optional),
  double (boolean, optional),
  bigDecimal (boolean, optional),
  bigInteger (boolean, optional),
  textual (boolean, optional),
  boolean (boolean, optional),
  binary (boolean, optional)
}

Example value:

{
  "array": true,
  "null": true,
  "number": true,
  "float": true,
  "pojo": true,
  "valueNode": true,
  "containerNode": true,
  "object": true,
  "missingNode": true,
  "nodeType": "ARRAY",
  "integralNumber": true,
  "floatingPointNumber": true,
  "short": true,
  "int": true,
  "long": true,
  "double": true,
  "bigDecimal": true,
  "bigInteger": true,
  "textual": true,
  "boolean": true,
  "binary": true
}

I would like to specify in my code that the model is like Person, so that the example given in the Swagger UI is more relevant. I did try @ApiImplicitParams:

@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
@ApiImplicitParams({
  @ApiImplicitParam(name = "update", dataTypeClass = Person.class)
})
public Response updatePerson(@PathParam("name") String name, JsonNode update) {
    return Response.ok(store.update(name, update)).build();
}

That did not make any difference. Swagger still documents JsonNode itself. The documentation for @ApiImplicitParams mentions:

While ApiParam is bound to a JAX-RS parameter, method or field, this allows you to manually define a parameter in a fine-tuned manner. This is the only way to define parameters when using Servlets or other non-JAX-RS environments.

Since I am using JAX-RS, this might mean that I cannot use @ApiImplicitParams, but @ApiParam does not provide anything to override the class.

How can I specify manually the datamodel of a JAX-RS parameter that is detected by Swagger automatically?

like image 373
snooze92 Avatar asked May 31 '19 15:05

snooze92


2 Answers

Adding this answer to make it somewhat generic for better understanding of @ApiImplicitParams.

You would have to use @ApiImplicitParams for wrapping the parameters as you wish to save in the documentation. @ApiImplicitParam has many not-so-obvious benefits, like to pass in extra header param without adding it as a method param or in your case wrap the the params so as to make them some sense.

For your problem, you would have to use @ApiImplicitParam along with paramType = "body" as you want to make changes in the body, similarly paramType = "head" if you wanted changes in header.

You can also control the mandatory fields in @ApiImplicitParam with property required = true/false.

As stated earlier, you can pass a param without having it in the method param, you can control its value using the property value = "required value".

You can also control the allowable values in @ApiImplicitParam using comma seperated values. Eg.allowableValues = "no-cache, no-store".

like image 167
Shubham Kadlag Avatar answered Nov 17 '22 01:11

Shubham Kadlag


I was on the right track with @ApiImplicitParam and I think I got it working the way I want, now:

@PATCH
@Path("/{name}")
@ApiOperation(response = Person.class)
@ApiImplicitParams({
  @ApiImplicitParam(paramType = "body", dataTypeClass = ExchangeConfiguration.class)
})
public Response updatePerson(@PathParam("name") String name, @ApiParam(hidden = true) JsonNode update) {
    return Response.ok(store.update(name, update)).build();
}

Configuring a name was not mandatory, but paramType seems to be required. Using paramType = "body" makes Swagger document the implicit parameter correctly. That would cause the body to be documented twice; we can hide the automatically generated version with the wrong model, using @ApiParam(hidden = true).

With the code above, the documentation looks exactly as I wanted, and the code continues to behave correctly.

Thanks all for the help!

like image 37
snooze92 Avatar answered Nov 17 '22 00:11

snooze92