m.y.m
m.y.m

Reputation: 367

swagger read documentation from properties file


I am trying to get Swagger to read the API documentation from a properties file swagger.properties but can't. In @ApiOperation annotation there is an error saying: Attribute value must be constant. Any suggestions about how to solve this and be able to read the documentation from a properties file?
Here is the Controller code:

package com.demo.student.demo.controller;

import com.demo.student.demo.entity.Student;
import com.demo.student.demo.service.StudentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping(value = "/v1/students")
@Api(description = "Set of endpoints for Creating, Retrieving, Updating and Deleting of Students.")
public class StudentController {
    private final String message;

    public StudentController(@Value("${test.swagger.message}") String message){
        this.message=message;
    }

    @Autowired
    private StudentService studentService;

    @GetMapping
    @ApiOperation(message)
    public List<Student> findAll(){
        return studentService.findAl();
    }

}

And also, how can I inject a value at the class level in the @API(description)?

Upvotes: 2

Views: 5266

Answers (2)

Vladas Maier
Vladas Maier

Reputation: 2152

There is a workaround. But you need an additional dependency for that - springfox.

You can write a plugin which will inject a text from an external file into you the @ApiOperation description field. I am using it in my project for injecting markdown files. It comes very handy since Swagger supports markdown and having a separate file for each endpoint provides you an opportunity to write extensive API descriptions (also within a markdown editor if you are using IntelliJ IDEA or similar).

Here is the code you need for it:

  1. Custom annotation (@ApiDescription) for each endpoint you want to provide a description. The value of the annotation will be the file path to your markdown or properties file. Later the plugin will look for the file at the provided filepath and set the description to the content of the file.

    @Target({ ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ApiDescription {
        String value() default "";
    }
    
  2. Plugin itself. It is an extensibility point. In this case we want to swap or set the value of description of @ApiOperation annotation later on. Check out the Springfox Plugins.

    ...
    
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.OperationBuilderPlugin;
    import springfox.documentation.spi.service.contexts.OperationContext;
    import springfox.documentation.spring.web.DescriptionResolver;
    
    ...
    
    @Component
    public class ApiDescriptionPlugin implements OperationBuilderPlugin {
    
        private final DescriptionResolver resolver;
    
        @Autowired
        public ApiDescriptionPlugin(DescriptionResolver resolver) {
            this.resolver = resolver;
        }
    
        @Override
        public void apply(OperationContext context) {
    
            Optional<ApiDescription> descOptional = context.findAnnotation(ApiDescription.class);
            boolean hasText = descOptional.isPresent() && StringUtils.hasText(descOptional.get().value());
            if(!hasText) {
                return;
            }
    
            final String file = descOptional.get().value();
            final URL url = Resources.getResource(file);
    
            String description;
            try {
                description = Resources.toString(url, StandardCharsets.UTF_8);
            } catch(IOException e) {
                log.error("Error while reading markdown description file {}", file, e);
                description = String.format("Markdown file %s not loaded", file);
            }
            context.operationBuilder().notes(resolver.resolve(description));
        }
    
        @Override
        public boolean supports(DocumentationType type) {
            return true;
        }
    }
    
  3. Simply annotate the endpoints with @ApiDescription("/notes/auth/login.md") (the file must be in resources folder)

You can adapt this example to work with properties file(s) (I don't know how your structure looks like and how you separate different API descriptions). This workaround with markdown files is useful for writing extensive descriptions and keeping them away from the actual code.

It works with Swagger 2.0.

Give it a try.

Upvotes: 5

Janar
Janar

Reputation: 2701

As the error message says the attribute values must be constants and Spring can't inject values to static final fields. Similarly, it is also impossible to inject values outside the class level (ie. the description for the @Api annotation)

A workaround would be creating a class with only constants which are all final static Strings like this

public final class Constants {
  public static final String API_DESCRIPTION = "description";
  public static final String FIND_ALL_MESSAGE= "message";
}

and using it in the controller

@Api(description = Constants.API_DESCRIPTION)
public class StudentController {
   @ApiOperation(Constants.FIND_ALL_MESSAGE)
    public List<Student> findAll(){...}
}

for Swagger however, for some of the fields it is possible using ${key} syntax. According to the documentation at https://springfox.github.io/springfox/docs/current/#property-file-lookup since version 2.7 it is possible for the following fields:

@ApiParam#value()
@ApiImplicitParam#value()
@ApiModelProperty#value()
@ApiOperation#value()
@ApiOperation#notes()
@RequestParam#defaultValue()
@RequestHeader#defaultValue()

Upvotes: 2

Related Questions