I was trying to inject a global header to my suite of requests. While studying the solution I found the annotation #ApiImplicitParam that has #Target(value=METHOD)
example:
#DeleteMapping(value = "/token/{version}", produces = "application/json; charset=UTF-8")
#ApiOperation(value = "delete actual token", produces = "application/json", response = Application.class)
#ApiImplicitParams(
{
#ApiImplicitParam(name = "My-custom-header", required = true, paramType = "header",
dataType = "string", value = "custom header", defaultValue = "GOOD")
}
)
public Application deleteAuthenticationToken(
#ApiParam(required = true, value = "version", defaultValue = "v1") #PathVariable("version") String version,
HttpServletRequest request) {
here the docs
https://docs.swagger.io/swagger-core/current/apidocs/io/swagger/annotations/ApiImplicitParam.html
This means that this annotation can't be set to the resource and being automatically applied to all the methods.
I need that every call has this custom header, is there a way of doing it globally?
EDIT1: I added a HeaderParam in the swagger config but it doesn't work
private void inizializeSwagger(){
BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion(PropertiesConfig.getConfig().getString(PropertiesConst.P_SWAGGER_API_VERSION));
beanConfig.setSchemes(PropertiesConfig.getConfig().getStringArray(PropertiesConst.P_SWAGGER_PROTOCOL));
beanConfig.setHost(PropertiesConfig.getConfig().getString(PropertiesConst.P_SWAGGER_HOST));
beanConfig.setResourcePackage(PropertiesConfig.getConfig().getString(PropertiesConst.P_SWAGGER_RESOURCES_PACKAGE));
Swagger swagger = new Swagger();
swagger.securityDefinition("basic", new BasicAuthDefinition());
Parameter globalOperationParameter = new HeaderParameter();
globalOperationParameter.setName("System");
globalOperationParameter.setRequired(Boolean.TRUE);
globalOperationParameter.setDescription("definition of the calling system");
swagger.parameter("System", globalOperationParameter);
new SwaggerContextService().updateSwagger(swagger);
beanConfig.setScan(true);
}
EDIT2:
It adds the parameters but SwaggerUI 2.0 doesn't show it
EDIT3:
I had to change the swaggerUI library implementation:
in the cycle of // operations (row 4695) I added the following
for(name in spec.parameters) {
var actualParam = spec.parameters[name];
if(actualParam.in === 'header') {
parameters.unshift({"name":actualParam.name,
"in":actualParam.in,
"description":actualParam.description,
"required":actualParam.required,
"type":"string",
"default":""})
}
}
In other words I added the params I needed in the cycling operation.
I leave this ticket opened for better and cleaner solutions
Related
I'm new to MockMVC. I've successfully written some basic tests, but I got stuck on trying to test an use case with the endpoint that requires a POST request with two parameters - a POJO and an array of MultipartFile. The test is written as such:
#Test
public void vytvorPodnetTest() throws Exception {
var somePojo = new SomePojo();
somePojo.setSomeVariable("test_value");
var roles = List.of("TEST_USER");
var uid = "00000000-0000-0000-0000-000000000001";
MockMultipartFile[] attachments = {new MockMultipartFile("file1.txt", "file1.txt", "text/plain", "file1 content".getBytes()),
new MockMultipartFile("file2.txt", "file2.txt", "text/plain", "file2 content".getBytes())};
MockMultipartHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart("/some-pojo/create");
builder.with(req - {
req.setMethod("POST");
return req;
});
MvcResult result = mockMvc.perform(builder.file(attachments[0]).file(attachments[1])
.param("SomePojo", new ObjectMapper().writeValueAsString(somePojo))
.file(attachment[0])
.with(TestUtils.generateJWTToken(uid, roles)))
.andExpect(status.isOk())
.andReturn();
}
The controller method is as follows:
#PostMapping(value = "/create", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public UUID createPojo(
#RequestPart(value = "SomePojo") SomePojo somePojo,
#RequestPart(value = "attachments", required = false) MultipartFile[] attachments) {
return pojoService.create(somePojo, attachments);
}
It stops here, before reaching the service. I've tried adding the files both as a param "attachments" and like shown above, but all I get is "400 Bad Request"
Finally found the way to send the parameters as MockMultipartFile from MockMVC to the controller:
MockMultipartFile pojoJson = new MockMultipartFile("SomePojo", null,
"application/json", JsonUtils.toJSON(podnet).getBytes());
mockMvc.perform(MockMvcRequestBuilders.multipart("/some-pojo/create")
.file(pojoJson)
.contentType(MediaType.MULTIPART_FORM_DATA_VALUE)
.with(new TestUtils().generateJWTToken(uid, roles)))
.andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
I have the below code in Swagger,
#Path("/v1")
#ApiOperation(value = "POST - Some Value", nickname = "post-funtion", consumes = "application/json", produces = "text/html; charset=UTF-8", tags = {
"Some Controller" })
#ApiImplicitParams({
#ApiImplicitParam(name = "Authorization", paramType = "header", dataType = "string", format = "JWT", required = false, value = "A User Service JWT"),
#ApiImplicitParam(name = "Request", value = "Request Object", paramType = "body", dataType = "org.pkg.SomeRequest", required = true) })
#ApiResponses({
#ApiResponse(code = 200, message = "Value Added", response = SomeResponse.class) })
private Object retrieveByName(Request request, Response response)
{
return new RetrieveByNameRqstHandler(catalogService, request, response).handle();
}
The code is supposed to automatically generate default json request depending upon the datatype which in this case is "org.pkg.SomeRequest" but there is nothing generated. On the contrary if I change the "org.pkg.SomeRequest" with "org.pkg.SomeResponse" there is a default JSON generated for this. Can anybody help me please?
Consider both classes SomeRequest,SomeResponse have the same code.
This is the image where I use "org.pkg.SomeRequest" in the dataType
This is the image where I use "org.pkg.SomeResponse" in the dataType
According to this GitHub issue on Swagger core project, if you add the annotation #ApiImplicitParam should resolve your problem.
#ApiImplicitParams({
#ApiImplicitParam(
required = true,
dataType = "com.example.SomeObjectDto",
paramType = "body"
)
})
But normally if you just add the class on your method signature it'll work.
private Object retrieveByName(SomeObjectDto someObjectDto) {
someCode();
}
Also SomeObjectDto class should contain "get" methods for your variables like.
class SomeObjectDto {
private String info;
getInfo(){
return info;
}
}
Will produce the following JSon.
{ info: "string" }
ApiImplicitParam can map a parameter to a correct type, but the type must be detected by swagger, so must be a valid reference.
The only way I could make this working is by using additionalModels method.
Example in spring-boot:
configure swagger
import springfox.documentation.spring.web.plugins.Docket;
import com.fasterxml.classmate.TypeResolver;
...
#Bean
public Docket api(TypeResolver typeResolver) {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("your-group-rest-api")
.select()
.apis(RequestHandlerSelectors.basePackage("your.package"))
.paths(PathSelectors.any())
.build()
.additionalModels(typeResolver.resolve(YourModel.class))
.apiInfo(apiInfo());
}
controller
#ApiOperation...
#ApiImplicitParams(
#ApiImplicitParam(dataType = "YourModel", name = "requestJson", paramType = "body"))
#ApiResponses...
#RequestMapping...
public void yourMethod(#RequestBody String requestJson,...)
Of course, you could have an InputStream parameter for the request and map that to your model.
We are using spring controllers to handle file uploads:
For example:
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
But we are not using any multipart resolver, we are streaming the files from the servlet request input stream. We need to start processing the file immediately for performance reasons.
When doing this this way, we can't seem to use the typical detection/configuration for multipart files. I know Springfox (which we use to generate our swagger docs) will generate the appropriate swagger controls if it sees a MultipartFile as a controller parameter, which will not be the case for us.
Are there any other config options available to hint to springfox that we want a file upload here?
Regarding breaking changes in Springfox v2.7.0:
You need to use dataType = "__file" instead of file as commented in https://github.com/springfox/springfox/issues/1285
Found my answer here: https://github.com/springfox/springfox/issues/1285
The following implicit params give me what I need:
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "file", name = "file", required = true,paramType = "form")}
#RequestMapping(value = "/scan", method = RequestMethod.POST, consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ScanResult scan(HttpServletRequest request) throws IOException, FileUploadException {
return scanService.scanFile(parseMultipart(request));
}
This adds a simple file picker to the API. To make things more confusing, turns out this functionality was broken in Springfox 2.4 - the version I was using. Adding that annotation and updating versions was all I needed to do.
That's right
https://stackoverflow.com/a/44385675/3810914
In Controller It should be:
#ApiOperation(value = "Upload file", response = String.class)
#ApiResponses({
#ApiResponse(code = 500, message = "Internal Server Error"),
#ApiResponse(code = 400, message = "Bad request")
})
#ApiImplicitParams (value = {
#ApiImplicitParam(dataType = "__file", name = "fileData", required = true,paramType = "form")})
#PostMapping(value = "/upload", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<?> uploadFileSimple(UploadFile form) {
// Create folder to save file if not exist
File uploadDir = new File(UPLOAD_DIR);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}
MultipartFile fileData = form.getFileData();
String name = fileData.getOriginalFilename();
if (name != null && name.length() > 0) {
try {
// Create file
File serverFile = new File(UPLOAD_DIR + "/" + name);
BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(serverFile));
stream.write(fileData.getBytes());
stream.close();
return ResponseEntity.ok("/file/" + name);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error when uploading");
}
}
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Bad request");
}
And In Model:
package com.xxx.xxx.request;
import lombok.*;
import org.springframework.web.multipart.MultipartFile;
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
public class UploadFile {
private MultipartFile fileData;
}
I am triing to document a rest service with swagger. After build and deploy I get a json, that is lacking the tags (in my case "tags" : ["databases"] is missing) and completely ignores the annotations on the parameters of the webservice interface methods (only the parameters, other documetation is provided correctly). Instead of listing the parameters it contains this:
"parameters" : [{
"in" : "body",
"name" : "body",
"required" : false,
"schema" : {
"type" : "string"
}
}
]
Most likely it has someting to do with the configuration or the process of loading the webapplication, since after a reload of the webapplication with the tomcat manager webapp it sends the correct json.
EDIT: Simply stopping and restarting the application has the same effect. In addion the json after a reload or restart is not deterministic (random). This implies the problem should be with the classloader.
I am using apache tomcat version 8.0.33, the resteasy version is 3.0.14.Final and the swagger is:
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-jaxrs</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.8</version>
</dependency>
The application class looks like:
#ApplicationPath("/")
public class ServiceApplication extends Application {
final Set<Object> singletons = new HashSet<Object>();
public ServiceApplication() throws IOException {
final InputStream inStream = getClass().getClassLoader()
.getResourceAsStream("swagger.properties");
final Properties props = new Properties();
props.load(inStream);
final BeanConfig beanConfig = new BeanConfig();
beanConfig.setVersion("1.0.0");
beanConfig.setHost(props.getProperty("host"));
beanConfig.setBasePath("/rest-service");
beanConfig.setResourcePackage("com.example.package");
beanConfig.setScan(true);
final CorsFilter filter = new CorsFilter();
filter.getAllowedOrigins().add("*");
singletons.add(filter);
}
#Override
public Set<Class<?>> getClasses() {
final Set<Class<?>> resources = new HashSet<Class<?>>();
// add WS classes
resources.add(DatabasesImpl.class);
// add provider classes
resources.add(ExcMapper1.class);
resources.add(ExcMapper2.class);
resources.add(ResponseInterceptor.class);
// add swagger classes
resources.add(io.swagger.jaxrs.listing.ApiListingResource.class);
resources.add(io.swagger.jaxrs.listing.SwaggerSerializers.class);
return resources;
}
#Override
public Set<Object> getSingletons() {
return singletons;
}
}
The annotations on the webservice:
#SwaggerDefinition(info = #io.swagger.annotations.Info(description = "<DESCRIPTION>",//
version = "0.0.2-SNAPSHOT", title = "Search", termsOfService = "<TOS>",//
contact = #io.swagger.annotations.Contact(name = "<CONTACT_NAME>", email = "<CONTACT_EMAIL>", url = "<CONTACT_URL>"),//
license = #io.swagger.annotations.License(name = "<LICENCE_NAME>", url = "<LICENCE_URL>")), consumes = {}, produces = { MediaType.APPLICATION_XML },//
schemes = { SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS },//
externalDocs = #io.swagger.annotations.ExternalDocs(value = "<EXTERNAL_DOCS>", url = "<EXTERNAL_DOCS_URL>"))
#Api(value = "/databases")
#Path("/databases/")
public interface Databases {
#ApiOperation(value = "Searches the database")
#ApiResponses(value = { #ApiResponse(code = 200, message = "Success"),
#ApiResponse(code = 404, message = "Database not found") })
#GET
#Path("{database}/search")
#Produces(MediaType.APPLICATION_XML)
public Response searchExport(
#ApiParam(value = "the database name", required = true, defaultValue = "default") #PathParam("database") #Nonnull final String database,
#ApiParam(value = "query string defaults to match all", defaultValue = "title:foo") #QueryParam("q") #DefaultValue("*:*") final String query,
#ApiParam(value = "starting element index defaults to 0") #QueryParam("start") #DefaultValue("0") final int start,
#ApiParam(value = "number of elements to return defaults to 10") #QueryParam("rows") #DefaultValue("10") final int rows,
#ApiParam(value = "sorting field") #QueryParam("sort") #DefaultValue("title") final String sort,
#ApiParam(value = "sorting order defaults to ascending") #QueryParam("sortOrder") #DefaultValue("ascending") final SortDirection sortOrder)
throws ParseException;
I'm having trouble with Swagger understanding Play! 2.0 routing wildcard routing, so the swagger ui ends up with broken URL.
My routes file has this route:
GET /settings/api/:project/*path #controllers.API.getParams(project, path)
Then my Controller has the following code:
#ApiOperation(value = "Returns settings for given project and path.", response = String.class, httpMethod = "GET")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "Request completed successfully."),
#ApiResponse(code = 500, message = "Internal error while processing request")
})
#ApiImplicitParams({
#ApiImplicitParam(name = "project", value = "Project name", required = true, dataType = "String", paramType = "path"),
#ApiImplicitParam(name = "path", value = "path", required = true, dataType = "String", paramType = "path")
})
public Result getParams(String project, String path) {
return ok(path);
}
Then when Swagger UI gets rendered, I see the path for this action rendered as
POST /settings/api/{project}/{path<.+>
And when I do a call it turns into
/settings/api/test/{path<.+>
So basically the :project gets replaced but the *path remains broken/intact.
Please share if you know how to fix this. Thanks!
So turns out that swagger doesn't support wildcard routes.