Рубрики
Без рубрики

Реализация Сервера Открытого API С Использованием Генератора OpenAPI

Автор оригинала: baeldung.

1. Обзор

Как следует из названия, генератор Open API генерирует код из спецификации Open API . Он может создавать код для клиентских библиотек, заглушек серверов, документации и конфигурации.

Он поддерживает различные языки и фреймворки. Примечательно, что есть поддержка C++, C#, Java, PHP, Python, Ruby, Scala – почти все широко используемые .

В этом уроке мы узнаем как реализовать заглушку сервера на основе Spring с помощью генератора OpenAPI через его плагин maven . Другие способы использования генератора-через его CLI или онлайн-инструменты .

2. Файл YAML

Для начала нам понадобится файл YAML с указанием API. Мы дадим его в качестве входных данных для нашего генератора, чтобы создать заглушку сервера.

Вот фрагмент нашего зоомагазина.yml :

openapi: "3.0.0"
paths:
  /pets:
    get:
      summary: List all pets
      operationId: listPets
      tags:
        - pets
      parameters:
        - name: limit
          in: query
          ...
      responses:
        ...
    post:
      summary: Create a pet
      operationId: createPets
      ...
  /pets/{petId}:
    get:
      summary: Info for a specific pet
      operationId: showPetById
      ...
components:
  schemas:
    Pet:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: integer
          format: int64
        name:
          type: string
        tag:
          type: string
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: integer
          format: int32
        message:
          type: string

3. Зависимости Maven

3.1. Плагин для генератора открытых API

Далее, давайте добавим зависимость Maven для плагина генератора:


    org.openapitools
    openapi-generator-maven-plugin
    5.1.0
    
        
            
                generate
            
            
                
                    ${project.basedir}/src/main/resources/petstore.yml
                
                spring
                com.baeldung.openapi.api
                com.baeldung.openapi.model
                
                    ApiUtil.java
                
                
                    true
                
            
        
    

Как мы видим, мы передали файл YAML как input Spec . После этого, поскольку нам нужен сервер на основе Spring, мы использовали имя генератора как spring .

Затем api Package указывает имя пакета, в котором будет сгенерирован API. Далее у нас есть пакет model , в котором генератор размещает модели данных. С шаблоном делегата , установленным в true , мы просим создать интерфейс, который может быть реализован как настроенный @Service класс.

Важно отметить, что параметры для Open API Generator одинаковы независимо от того, используете ли вы CLI, плагины Maven/Gradle или параметры онлайн-генерации .

3.2. Пружинные зависимости

Поскольку мы будем генерировать сервер Spring, нам также нужны его зависимости ( Spring Boot Starter Web и Spring Data JPA ), чтобы сгенерированный код компилировался и выполнялся должным образом :


    
        org.springframework.boot
        spring-boot-starter-web
        2.4.4
    
    
        org.springframework.data
        spring-data-jpa
        2.4.6
    

4. Генерация кода

Чтобы сгенерировать заглушку сервера, нам просто нужно запустить:

mvn clean install

В результате мы получаем:

Теперь давайте взглянем на код, начиная с содержимого пакета api .

Во-первых, мы получаем интерфейс API под названием PetsApi , который содержит все сопоставления запросов, определенные в спецификации YAML. Вот фрагмент:

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
  date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Validated
@Api(value = "pets", description = "the pets API")
public interface PetsApi {
    /**
     * GET /pets : List all pets
     *
     * @param limit How many items to return at one time (max 100) (optional)
     * @return A paged array of pets (status code 200)
     *         or unexpected error (status code 200)
     */
    @ApiOperation(value = "List all pets", nickname = "listPets", notes = "", 
      response = Pet.class, responseContainer = "List", tags={ "pets", })
    @ApiResponses(value = { @ApiResponse(code = 200, message = "A paged array of pets", 
      response = Pet.class, responseContainer = "List"),
      @ApiResponse(code = 200, message = "unexpected error", response = Error.class) })
    @GetMapping(value = "/pets", produces = { "application/json" })
    default ResponseEntity listPets(@ApiParam(
      value = "How many items to return at one time (max 100)") 
      @Valid @RequestParam(value = "limit", required = false) Integer limit) {
        return getDelegate().listPets(limit);
    }

    // other generated methods
}

Во-вторых, поскольку мы используем шаблон делегата, OpenAPI также генерирует для нас интерфейс делегатора с именем PetsApiDelegate . В частности, методы, объявленные в этом интерфейсе, возвращают состояние HTTP 501, не реализованное по умолчанию :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
  date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public interface PetsApiDelegate {
    /**
     * GET /pets : List all pets
     *
     * @param limit How many items to return at one time (max 100) (optional)
     * @return A paged array of pets (status code 200)
     *         or unexpected error (status code 200)
     * @see PetsApi#listPets
     */
    default ResponseEntity> listPets(Integer limit) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    String exampleString = "{ \"name\" : \"name\", \"id\" : 0, \"tag\" : \"tag\" }";
                    ApiUtil.setExampleResponse(request, "application/json", exampleString);
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }

    // other generated method declarations
}

После этого мы видим есть класс ApiController , который просто подключается к делегатору :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
  date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
@Controller
@RequestMapping("${openapi.swaggerPetstore.base-path:}")
public class PetsApiController implements PetsApi {

    private final PetsApiDelegate delegate;

    public PetsApiController(
      @org.springframework.beans.factory.annotation.Autowired(required = false) PetsApiDelegate delegate) {
        this.delegate = Optional.ofNullable(delegate).orElse(new PetsApiDelegate() {});
    }

    @Override
    public PetsApiDelegate getDelegate() {
        return delegate;
    }
}

В пакете model генерируется пара POJO модели данных , называемых Error и Pet , на основе схем , определенных во входных данных YAML.

Давайте посмотрим на один из них – Домашнее животное :

@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", 
  date = "2021-03-22T23:26:32.308871+05:30[Asia/Kolkata]")
public class Pet {
  @JsonProperty("id")
  private Long id;

  @JsonProperty("name")
  private String name;

  @JsonProperty("tag")
  private String tag;

  // constructor

  @ApiModelProperty(required = true, value = "")
  @NotNull
  public Long getId() {
    return id;
  }

  // other getters and setters

  // equals, hashcode, and toString methods
}

5. Тестирование сервера

Теперь все, что требуется для того, чтобы заглушка сервера функционировала как сервер, – это добавить реализацию интерфейса делегатора.

Чтобы все было просто, здесь мы не будем этого делать и только проверим заглушку. Более того, перед этим нам понадобится пружина Приложение :

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

5.1. Тест С использованием локона

После запуска приложения мы просто выполним команду:

curl -I http://localhost:8080/pets/

И вот ожидаемый результат:

HTTP/1.1 501 
Content-Length: 0
Date: Fri, 26 Mar 2021 17:29:25 GMT
Connection: close

5.2. Интеграционные тесты

В качестве альтернативы мы можем написать простой интеграционный тест для того же самого:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class OpenApiPetsIntegrationTest {
    private static final String PETS_PATH = "/pets/";

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void whenReadAll_thenStatusIsNotImplemented() throws Exception {
        this.mockMvc.perform(get(PETS_PATH)).andExpect(status().isNotImplemented());
    }

    @Test
    public void whenReadOne_thenStatusIsNotImplemented() throws Exception {
        this.mockMvc.perform(get(PETS_PATH + 1)).andExpect(status().isNotImplemented());
    }
}

6. Заключение

В этом уроке мы видели, как создать заглушку сервера на основе Spring из спецификации YAML с помощью плагина maven генератора OpenAPI .

В качестве следующего шага мы также можем использовать его для создания клиента .

Как всегда, исходный код доступен на GitHub .