Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong behaviour when using mime type api versioning #965

Closed
codependent opened this issue Sep 10, 2015 · 2 comments
Closed

Wrong behaviour when using mime type api versioning #965

codependent opened this issue Sep 10, 2015 · 2 comments

Comments

@codependent
Copy link

We are starting with our REST API versioning and after having read a lot about the different options (URI versioning, mime type versioning) have decided to use the latter approach.

We were expecting that Springfox generates the following doc:

v1:

get /api/architecture/mails - application/vnd.arch.mails.v1+json 
get /api/architecture/services - application/vnd.arch.service.v1+json

v2:

get /api/architecture/services - application/vnd.arch.service.v2+json

However, in the v2 I also get this:

get /api/architecture/services - application/vnd.arch.service.v1+json

It shouldn't be there since I configured the v2 Docklet with .produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"}))) so that it filters the services according to the versioned mime type.

Here is our springfox config:

@Bean
    public Docket arqV1Api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
            .produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v1+json","application/vnd.arch.mail.v1+json"})))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v1 group");
    }
    @Bean
    public Docket arqV2Api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.any())
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
            .produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v2 group");
    }

Spring MVC Rest Controller:

private static final String serviceArqV1MediaType = "application/vnd.arch.service.v1+json";
private static final String serviceArqV2MediaType = "application/vnd.arch.service.v2+json";
private static final String mailsArqV1MediaType   = "application/vnd.arch.mail.v1+json";

@ApiOperation(value = "Gets architecture services", 
              notes = "",
              produces = serviceArqV1MediaType)
@ApiResponses(value = {
        @ApiResponse(code = 200, message = "Request OK"),
        @ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET, 
                produces = serviceArqV1MediaType)
public List<ServicioArquitectura> getServices() {
    return Arrays.asList(new ServiceArch[]{new ServicioArquitectura("Support"), new ServicioArquitectura("Kickoff")});
}

@ApiOperation(value = "Gets architecture services", 
          notes = "",
          produces = serviceArqV2MediaType)
@ApiResponses(value = {
      @ApiResponse(code = 200, message = "Request OK"),
      @ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/services"}, method = RequestMethod.GET, 
                produces = {serviceArqV2MediaType})
public List<ServicioArquitecturaV2> getServicesV2() {
    return Arrays.asList(new ServiceArchV2[]{new ServiceArchV2("Support", Boolean.TRUE), new ServiceArchV2("Kickoff", Boolean.FALSE)});
}

@ApiOperation(value = "Gets mails", 
          produces = mailsArqV1MediaType)
@ApiResponses(value = {
      @ApiResponse(code = 200, message = "Request OK"),
      @ApiResponse(code = 400, message = "Bad Request")})
@RequestMapping(value = {"/mails"}, method = RequestMethod.GET, 
                produces = {mailsArqV1MediaType})
public List<String> getMails() {
    return Arrays.asList(new String[]{"xxxcompany.com"});
}
@dilipkrish
Copy link
Member

Couple of things

  • the assumption that setting produces is also going to select the apis is incorrect. Setting the produces will add a global media type to all requests. Will add a note to fix that in the docs!
  • Now on to what you need to do to filter your api...
@Bean
    public Docket arqV1Api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
// create a Predicate<RequestHandler> predicate that fill detect presence of a media type support in 
// RequestHandler.getRequestMapping().getProducesCondition() in this case we want apis that DO NOT 
// support v2 media types
            .apis(not(withMediaType("application/vnd.arch.service.v2+json"))) 
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
            .produces(new HashSet<String>(Arrays.asList(new String[{"application/vnd.arch.mail.v1+json"})))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v1 group");
    }
    @Bean
    public Docket arqV2Api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
// create a Predicate<RequestHandler> predicate that fill detect presence of a media type support in 
// RequestHandler.getRequestMapping().getProducesCondition() in this case we want apis that DO NOT 
// support v1 media types
            .apis(not(withMediaType("application/vnd.arch.service.v1+json"))) 
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
            .produces(new HashSet<String>(Arrays.asList(new String[]{"application/vnd.arch.service.v2+json"})))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v2 group");
    }

@codependent
Copy link
Author

@dilipkrish thanks for clearing it up! In case someone needs it, this is how I implemented it:

Helper static methods:

     public static Predicate<RequestHandler> withMediaType(final MediaType[] mediaTypes){
        return new Predicate<RequestHandler>() {
            @Override
            public boolean apply(RequestHandler input) {
                if(mediaTypes!=null){
                    ProducesRequestCondition producesCondition = input.getRequestMapping().getProducesCondition();
                    Set<MediaType> producibleMediaTypes = producesCondition.getProducibleMediaTypes();
                    for (MediaType mt : producibleMediaTypes) {
                        for (int i = 0; i < mediaTypes.length; i++) {
                            if(mt.equals(mediaTypes[i])){
                                return true;
                            }
                        }
                    }
                }
                return false;
            }
        };
    }

    public static Set<String> mediaTypesToStringSet(MediaType[] mediaTypes){
        Set<String> mediaTypesSet = new HashSet<String>();
        if(mediaTypes!=null){
            for (int i = 0; i < mediaTypes.length; i++) {
                mediaTypesSet.add(mediaTypes[i].toString());
            }
        }
        return mediaTypesSet;
    }

Docket definitions:

    @Bean
    public Docket arqV1Api() {
        MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v1+json"),
                                                      new MediaType("application","vnd.arch.mails.v1+json")};
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(withMediaType(validMediaTypes))
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v1","","","",""))
            .produces(mediaTypesToStringSet(validMediaTypes))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v1 group");
    }
    @Bean
    public Docket arqV2Api() {
        MediaType[] validMediaTypes = new MediaType[]{new MediaType("application","vnd.arch.service.v2+json")};
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(withMediaType(validMediaTypes))
            .paths(PathSelectors.regex("/api/architecture/.*"))               
            .build()
            .apiInfo(new ApiInfo("Architecture Rest Api","Architecture REST Services","v2","","","",""))
            .produces(mediaTypesToStringSet(validMediaTypes))
            .securitySchemes(newArrayList(apiKey()))
            .securityContexts(newArrayList(securityContext()))
            .groupName("Arq v2 group");
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants