5

In MVC, you would expect that controller will receive models as input and produce models as output. In Swagger, the latter is not a problem but I have troubles with former. I can't understand how to make Swagger build an input model from incoming GET parameters.

Consider:

"paths": {
    "/search": {
        "get": {
            "consumes": [],
            "produces": [
                "application/json"
            ],
            "parameters": [
                // What goes here?
            ],
            "responses": {
                "200": {
                    "description": "Success",
                    "schema": { 
                        "$ref": "#/definitions/SearchResponse"
                    }
                },
            }
        }
    }
}

How do I make the generated controller's method to have a signature like:

    public ResponseEntity<ResultModel> controllerGet(ModelFromParameters input);

where ModelFromParameters will have several fields corresponding to different GET parameters.

Examples mostly focus either on POST requests, or GET requests where every of 20-odd parameters are stuffed in the arguments list of method, which is obviously anti-MVC.

The API in question is a complex stateless querying system with a lot of parameters.

alamar
  • 18,729
  • 4
  • 64
  • 97

4 Answers4

6

Assumption: you want to create an object as parameter to your service method but still pass each of the fields of the object as query parameter in the actual http request and be able to document individual fields in swagger.

e.g. GET http://localhost:8080/search?parameter1=value1&parameter2=value2

Typical Service Method Definition where each query param is defined as a parameter in the actual method

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(@RequestParam parameter1, @RequestParam parameter2) {
...
}

Modified service method with single parameter using Object (a.k.a Bean) annotated with @ModelAttribute. Though it is an object, as far as the REST API is concerned it is still the same as above.

@RequestMapping(method = RequestMethod.GET)
public ResponseEntity<List<String>> search(@ModelAttribute FormParam formParam) {
...
}

FormParam class. You can document each field using @ApiParam

public class FormParam {
    @ApiParam(value = "parameter1 - description here", required = true)
    private String parameter1;
    @ApiParam(value = "parameter2 - description here", required = true)
    private String parameter2;
    //define getter setters below
}

This is how it appears in swagger This is how it appears in swagger

This is the generated swagger.json snippet

"paths": {
    "/search": {
        "get": {
            "tags": ["search-service"],
            "summary": "Search with Object as parameter",
            "description": "Search with Object as parameter",
            "operationId": "searchUsingGET",
            "consumes": ["application/json"],
            "produces": ["*/*"],
            "parameters": [{
                "name": "parameter1",
                "in": "query",
                "description": "parameter1 - description here",
                "required": true,
                "type": "string"
            },
            {
                "name": "parameter2",
                "in": "query",
                "description": "parameter2 - description here",
                "required": true,
                "type": "string"
            }],
            "responses": {
                "200": {
                    "description": "OK",
                    "schema": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    }
                },
                "401": {
                    "description": "Unauthorized"
                },
                "403": {
                    "description": "Forbidden"
                },
                "404": {
                    "description": "Not Found"
                }
            }
        }
    },

If you are manually creating the swagger.json the query parameters can be documented like

"parameters": [{
    "name": "parameter1",
    "in": "query",
    "description": "parameter1 - description here",
    "required": true,
    "type": "string"
},
{
    "name": "parameter2",
    "in": "query",
    "description": "parameter2 - description here",
    "required": true,
    "type": "string"
}],
Itherael
  • 193
  • 3
  • 10
  • And how would I model that in Swagger's `swagger.json`? So that code will be generated from that, JSON-first? – alamar Jul 14 '17 at 09:16
  • @alamar, I have updated the answer with the generated swagger.json. – Itherael Jul 14 '17 at 15:20
  • I don't see how this swagger.json will not generate controller method with two primitive parameters. – alamar Jul 14 '17 at 20:05
  • What version of swagger are you using? Is it auto-generated from the annotations or you are manually editing the swagger.json? – Itherael Jul 14 '17 at 20:48
  • Swagger is just a documentation. It has nothing to do with generating the controller method with two primitive parameters. Spring basically resolves that through the @ModelAttribute annotation and maps those request parameters to the bean instead of individual parameters. – Itherael Jul 14 '17 at 21:15
  • `java -jar ./swagger-codegen-cli.jar generate` will happily generate code so you're wrong thinking that Swagger is just documentation. – alamar Jul 17 '17 at 07:03
  • Oh I see what you are saying. I'll take it back, yes you can generate code from swagger documentation. I was thinking more of a run-time environment not code time. I don't think the swagger code generation supports it that. You will have to check the swagger code gen for that – Itherael Jul 17 '17 at 08:04
2

I think you can't. I had also a similar problem, for list page + search, using GET with body. No way to make swagger represent this, also if it works in Tomcat; also Elasticsearch supports it. It looks like there is no plan to change this aspect in swagger. I resorted to split the two in swagger: list-without-search as GET and list+search as POST, just to put the page in the swagger documentation, also if the latter actually works also as GET.

I have no experience with swagger code generation but, if your configuration generates what you expect with POST but it doesn't with GET, then you are probably hitting the same limitation.

https://github.com/swagger-api/swagger-ui/issues/2867

https://github.com/swagger-api/swagger-ui/issues/2136

https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#operationRequestBody

The requestBody is only supported in HTTP methods where the HTTP 1.1 specification RFC7231 has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, requestBody SHALL be ignored by consumers.

Testo Testini
  • 2,200
  • 18
  • 29
  • I am not using GET with body. Sigh. – alamar Jul 17 '17 at 14:58
  • I thought it was in body because AFAIK is not possible to send complex-type values through parameters, only in request body. https://stackoverflow.com/a/39194484/1536382 – Testo Testini Jul 17 '17 at 15:30
  • Sigh. I don't need to send complex type values. I want to send simple values as simple query parameters and make a simple plain model object out of those. There's nothing complicated here, except that I have more than query 10 parameters so it makes total sense to not have those as method parameters. – alamar Jul 17 '17 at 15:32
  • Ah, you want swagger generator for spring mvc have an option to implement binding for all request parameters (defined in swagger conf) to an object instead than to method parameters, using the spring feature described also in https://stackoverflow.com/a/16942352/1536382 (and probably also have swagger generate the object) ? – Testo Testini Jul 17 '17 at 16:11
  • Exactly that! I've ended up removing controller generation by Swagger because I never made it work. – alamar Jul 17 '17 at 17:48
-1

In usual, http server(eg. tomcat, jetty) will not accept a body message within a get request. If client needs pass parameters to server by http get method, it should use query string . Last part of the url after '?' character. Query string details you can see query string

But with spring mvc help, your http request parameters in query string will be bound to your controller method parameter`s fields.So it seems like client passed a pojo parameter to server side.

Any way the parameter part should look like this:

"parameters" : [ {
      "name" : "age",
      "in" : "query",
      "required" : false,
      "type" : "integer"
    }, {
      "name" : "firstName",
      "in" : "query",
      "required" : false,
      "type" : "string"
    }]

Please note that "in" filed's value is "query". That means parameter is passed by query string.

Jian
  • 19
  • 3
  • Where in the question description have you seen anything about body message? It only talks about parameters repeatedly. Having a long unstructured "beard" of parameters is *exactly* what I'm trying to avoid here, instead making a Model object out of all those parameters is desired. – alamar Jul 12 '17 at 11:28
  • Sorry abort the long and useless description.But please read the whole words patiently. The answer your are wanted is right there. – Jian Jul 12 '17 at 11:59
  • how to generate that in Swagger? Because by default it will generate a method with two parameters for this case. Like `serviceGet(int age, String firstName)`, which is not what is desired. – alamar Jul 12 '17 at 16:07
-1

I supose what you require is posting your arguments in request body, it goes like this:

...
{
  "name": "files",
  "in": "body",
  "required": true,
  "schema": {
    "type": "array",
    "items": {
      "$ref": "#/definitions/FileRequest"
    }
  }
}

which translates to:

public Response storageFilePost(
            @NotEmpty(message = "systemID is required!") @QueryParam("systemID") final String systemID,
            @NotNull(message = "qParam is required!") @QueryParam("qParam") final Long qParam,
            @NotNull(message = "files are required!") final List<FileRequest> files) {
PanBrambor
  • 89
  • 9
  • No it doesn't require posting my arguments in request body. I was writing about GET and query parameters all over the question. – alamar Jul 13 '17 at 09:29
  • After re-reading the question a few times, i think there is no easy way to solve this. Best bet would be a factory class that you feed all your query input. – PanBrambor Jul 13 '17 at 09:42
  • @PanBramor the fundamental question if it's possible to model using Swagger – alamar Jul 13 '17 at 09:48
  • 1
    Then the anwer is no. swagger only permits primitive types in query, and generating an object from that is nowhere near its capabilities, what you are proposing is basicly generating a client in between. – PanBrambor Jul 13 '17 at 10:04