Artigo
· 6 hr atrás 17min de leitura

OpenAPI Suite - Parte 1

Olá Comunidade,

Gostaria de apresentar meu último pacote OpenAPI-Suite. Este é um conjunto de ferramentas para gerar código ObjectScript a partir de uma especificação OpenAPI versão 3.0..  IEm resumo, estes pacotes permitem:

  • Gerar classes de servidor. É bem parecido com o código gerado por ^%REST mas o valor adicionado é o suporte à versão 3.0.
  • Gerar classes de cliente HTTP.
  • Gerar classes de produção de cliente (business services, business operation, business process, Ens.Request, Ens.Response).
  • Uma interface web para gerar e baixar o código ou gerar e compilar diretamente no servidor.
  • Converter especificações das versões 1.x, 2.x para a versão 3.0.

Visão Geral

O OpenAPI Suite é dividido em vários pacotes e utiliza diferentes bibliotecas da comunidade de desenvolvedores e também serviços REST públicos. Você pode ver no esquema abaixo todos os pacotes que foram desenvolvidos, e as bibliotecas e serviços web utilizados:

Observação: Em caso de problemas ao usar serviços REST públicos, é possível iniciar uma instância Docker do serviço de conversão e validação.

O que cada pacote faz?

O OpenAPI Suite foi projetado em diferentes pacotes para facilitar a manutenção, melhoria e extensão futura. Cada pacote tem um papel. Vamos dar uma olhada!

openapi-common-lib

Isto contém todo o código comum aos outros pacotes. Por exemplo openapi-client-gen e openapi-server-gen aceitam a seguinte entrada para uma especificação OpenAPI:

  • URL
  • Caminho de arquivo
  • %Stream.Object 
  • %DynamicObject
  • Formato YAML
  • Formato JSON
  • OpenAPI versão1.x, 2.x, 3.0.x.

No entanto, apenas uma especificação 3.0.x em um %DynamicObject pode ser processada. O código para a transformação está localizado neste pacote. Ele também contém várias utilidades.

swagger-converter-cli

É uma dependência do openapi-common-lib. Este é um cliente HTTP que utiliza o serviço REST públicoconverter.swagger.iopara converter OpenAPI versão 1.x ou 2.x para a versão 3.0.

swagger-validator-cli

É também uma dependência do openapi-common-lib, mesmo que seu nome seja "validator", ele não é usado para validar a especificação. O  converter.swagger.iofornece o serviço "parse" que permite simplificar a estrutura de uma especificação OpenAPI. Por exemplo: criar uma definição para uma "nested object definition" e substituí-la por um "$ref". Isso reduz o número de casos a serem tratados no algoritmo de geração de código.

openapi-client-gen

Este pacote é dedicado à geração de código do lado do cliente para ajudar os desenvolvedores a consumir serviços REST.

Ele inclui um cliente HTTP simples ou um cliente de Produção(business services, process, operation, Production classes). Originalmente criado para suportar OpenAPI 2.x, foi completamente refatorado para suportar a versão 3.x.

openapi-server-gen

O oposto do openapi-client-gen, é dedicado à geração de código do lado do servidor. Não há interesse na versão 2.0 da especificação porque o ^%RESTexiste, mas o objetivo deste pacote é o suporte à versão 3.0.  

openapi-suite

Ele reúne todos os pacotes acima e fornece uma API REST para:

  • Gerar o código e compilar o código na instância IRIS.
  • Gerar código sem compilar para download apenas.

Uma interface web também é fornecida para consumir esta API REST e, assim, explorar as funcionalidades do OpenAPI Suite.

E as bibliotecas?

Aqui estão algumas das bibliotecas existentes no DC que foram úteis neste desenvolvimento:

objectscript-openapi-definition

Uma biblioteca útil para gerar classes de modelo a partir de uma especificação OpenAPI. Esta é uma parte muito importante deste projeto e eu também sou um contribuidor.

ssl-client

Permite criar configurações SSL. Usado principalmente para criar um nome de configuração "DefaultSSL" para requisições HTTPS.

yaml-utils

No caso de especificação em formato YAML, esta biblioteca é usada para converter para o formato JSON. Essencial neste projeto. A propósito, foi desenvolvida inicialmente para testar a especificação YAML com a versão 1 do openapi-client-gen.

io-redirect

Esta é uma das minhas bibliotecas. Ela permite redirecionar a saída de "write" para um stream, arquivo, global ou variável string. É usada pelo serviço REST para manter um rastreamento dos logs. É inspirada nesta postagem da comunidade.

Instalação IPM

Para instalar a suíte, seu melhor amigo é oIPM (zpm)Existem muitos pacotes e dependências, e usar o IPM é definitivamente conveniente.

zpm "install openapi-suite"
; opcional
; zpm "install swagger-ui"

 

Instalação Docker

Não há nada de especial, este projeto utiliza o intersystems-iris-dev-template.

git clone git@github.com:lscalese/openapi-suite.git
cd openapi-suite
docker-compose up -d

Se você tiver um erro ao iniciar o Iris, talvez seja um problema de permissão no arquivo iris-main.log.

Você pode tentar:

touch iris-main.log && chmod 777 iris-main.log

Observação: Adicionar permissão de leitura e escrita (RW) para o usuário irisowner deve ser suficiente.

Como Usar

O OpenAPI-Suite fornece uma interface web para "Gerar e baixar" ou "Gerar e instalar" código.

A interface está disponível em http://localhost:52796/openapisuite/ui/index.csp (*adapte com o seu número de porta, se necessário).

É muito simples, preencha o formulário:

  1. Nome do pacote da aplicação: este é o pacote usado para as classes geradas. Deve ser um nome de pacote inexistente.
  2. Selecione o que você deseja gerar: Cliente HTTP, Produção Cliente ou Servidor REST.
  3. Selecione o namespace onde o código será gerado. Isso faz sentido apenas se você clicar em "Install On Server"; caso contrário, este campo será ignorado.
  4. Nome da Aplicação Web é opcional e está disponível apenas se você selecionar "Servidor REST" para gerar. Deixe vazio se você não quiser criar uma Aplicação Web relacionada à classe de despacho REST gerada.
  5. O campo Especificação OpenAPI pode ser uma URL apontando para a especificação ou uma cópia/cola da própria especificação (neste caso, a especificação deve estar em formato JSON).

Se você clicar no botão "Download Only", o código será gerado e retornado em um arquivo XML, e então as classes serão excluídas do servidor. O namespace usado para armazenar temporariamente as classes geradas é o namespace onde o OpenAPI-Suite está instalado (por padrão IRISAPP se você usar uma instalação Docker).

No entanto, se você clicar no botão "Install On Server", o código será gerado e compilado, e o servidor retornará uma mensagem JSON com o status da geração/compilação do código e também os logs.

Por padrão, este recurso está desabilitado. Para habilitá-lo, basta abrir um terminal IRIS e:

Set ^openapisuite.config("web","enable-install-onserver") = 1

Explore a API REST do OpenAPI-Suite

O formulário CSP utiliza serviços REST disponíveis emhttp://localhost:52796/openapisuite.

Abra o swagger-ui http://localhost:52796/swagger-ui/index.html  e explore http://localhost:52796/openapisuite/_spec 

Este é o primeiro passo para criar uma aplicação front-end mais avançada com o framework Angular.

Gere código programaticamente

Claro, não é obrigatório usar a interface do usuário. Nesta seção, veremos como gerar o código programaticamente e como usar os serviços gerados.

Todos os trechos de código também estão disponíveis na classe dc.openapi.suite.samples.PetStore.

 

cliente HTTP

Set features("simpleHttpClientOnly") = 1
Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreclient", "https://petstore3.swagger.io/api/v3/openapi.json", .features)

 

O primeiro argumento é o pacote onde as classes serão geradas, portanto, certifique-se de passar um nome de pacote válido. O segundo argumento pode ser uma URL apontando para a especificação, um nome de arquivo, um stream ou um %DynamicObject. "features" é um array, atualmente apenas os seguintes subscritos estão disponíveis:

simpleHttpClientOnly: se for 1, apenas um cliente HTTP simples será gerado; caso contrário, uma produção também será gerada (comportamento padrão).

compile: se for 0, o código gerado não será compilado. Isso pode ser útil se você quiser gerar código apenas para fins de exportação. Por padrão, compile = 1.

Abaixo está um exemplo de como usar o serviço "addPet" com o cliente HTTP recém-gerado:

Set messageRequest = ##class(petstoreclient.requests.addPet).%New()
Set messageRequest.%ContentType = "application/json"
Do messageRequest.PetNewObject().%JSONImport({"id":456,"name":"Mittens","photoUrls":["https://static.wikia.nocookie.net/disney/images/c/cb/Profile_-_Mittens.jpg/revision/latest?cb=20200709180903"],"status":"available"})
  
Set httpClient = ##class(petstoreclient.HttpClient).%New("https://petstore3.swagger.io/api/v3","DefaultSSL")
; MessageResponse será uma instância de petstoreclient.responses.addPet
Set sc = httpClient.addPet(messageRequest, .messageResponse)
If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc) Quit sc
  
Write !,"Http Status code : ", messageResponse.httpStatusCode,!
Do messageResponse.Pet.%JSONExport()

 

 
Click to show generated classes.

 

Produção de cliente HTTP

Set sc = ##class(dc.openapi.client.Spec).generateApp("petstoreproduction", "https://petstore3.swagger.io/api/v3/openapi.json")

O primeiro argumento é o nome do pacote. Se você testar a geração de código de um cliente HTTP simples e de uma produção de cliente, certifique-se de usar um nome de pacote diferente. O segundo e o terceiro seguem as mesmas regras do cliente HTTP.

Antes de testar, inicie a produção através do portal de gerenciamento usando este comando:

Do ##class(Ens.Director).StartProduction("petstoreproduction.Production")

Abaixo está um exemplo de como usar o serviço "addPet", mas desta vez com a produção gerada:

Set messageRequest = ##class(petstoreproduction.requests.addPet).%New()
Set messageRequest.%ContentType = "application/json"
Do messageRequest.PetNewObject().%JSONImport({"id":123,"name":"Kitty Galore","photoUrls":["https://www.tippett.com/wp-content/uploads/2017/01/ca2DC049.130.1264.jpg"],"status":"pending"})
; MessageResponse será uma instâncoa de petstoreclient.responses.addPet
Set sc = ##class(petstoreproduction.Utils).invokeHostSync("petstoreproduction.bp.SyncProcess", messageRequest, "petstoreproduction.bs.ProxyService", , .messageResponse)
Write !, "Take a look in visual trace (management portal)"
If $$$ISERR(sc) Do $SYSTEM.Status.DisplayError(sc)
Write !,"Http Status code : ", messageResponse.httpStatusCode,!
Do messageResponse.Pet.%JSONExport()

Agora, você pode abrir o rastreamento visual para ver os detalhes:

As classes geradas nos pacotes model, requests e responses são bem semelhantes ao código gerado para um cliente HTTP simples. As classes do pacote requests herdam de Ens.Request, e as classes do pacote responses herdam de Ens.Response. A implementação padrão da operação de negócio é muito simples, veja este trecho de código:

Class petstoreproduction.bo.Operation Extends Ens.BusinessOperation [ ProcedureBlock ]
{

Parameter ADAPTER = "EnsLib.HTTP.OutboundAdapter";
Property Adapter As EnsLib.HTTP.OutboundAdapter;
/// Implementar operationId : addPet
/// post /pet
Method addPet(requestMessage As petstoreproduction.requests.addPet, Output responseMessage As petstoreproduction.responses.addPet) As %Status
{
    Set sc = $$$OK, pHttpRequestIn = ##class(%Net.HttpRequest).%New(), responseMessage = ##class(petstoreproduction.responses.addPet).%New()
    $$$QuitOnError(requestMessage.LoadHttpRequestObject(pHttpRequestIn))
    $$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "post", pHttpRequestIn, , , ..Adapter.URL_requestMessage.%URL))
    $$$QuitOnError(responseMessage.LoadFromResponse(pHttpResponse, "addPet"))
    Quit sc
}
...
}
}

 

Serviço REST HTTP do lado do servidor

Set sc = ##class(dc.openapi.server.ServerAppGenerator).Generate("petstoreserver", "https://petstore3.swagger.io/api/v3/openapi.json", "/petstore/api")

O primeiro argumento é o nome do pacote para gerar as classes. O segundo segue as mesmas regras do cliente HTTP. O terceiro argumento não é obrigatório, mas se presente, uma aplicação web será criada com o nome fornecido (tenha cuidado ao fornecer um nome de aplicação web válido).

A classe petstoreserver.disp (dispatch, despacho, %CSP.REST class) parece um código gerado por ^%REST, realizando muitas verificações para aceitar ou rejeitar a requisição e chamando a implementação do ClassMethod de serviço correspondente em petstoreserver.impl. A principal diferença é o argumento passado para o método de implementação, que é um objeto petstoreserver.requests. Exemplo:

Class petstoreserver.disp Extends %CSP.REST [ ProcedureBlock ]
{

Parameter CHARSET = "utf-8";
Parameter CONVERTINPUTSTREAM = 1;
Parameter IgnoreWrites = 1;
Parameter SpecificationClass = "petstoreserver.Spec";
/// Processar request post /pet
ClassMethod addPet() As %Status
{
    Set sc = $$$OK
    Try{
        Set acceptedMedia = $ListFromString("application/json,application/xml,application/x-www-form-urlencoded")
        If '$ListFind(acceptedMedia,$$$LOWER(%request.ContentType)) {
             Do ##class(%REST.Impl).%ReportRESTError(..#HTTP415UNSUPPORTEDMEDIATYPE,$$$ERROR($$$RESTContentType,%request.ContentType)) Quit
        }
        Do ##class(%REST.Impl).%SetContentType($Get(%request.CgiEnvs("HTTP_ACCEPT")))
        If '##class(%REST.Impl).%CheckAccepts("application/xml,application/json") Do ##class(%REST.Impl).%ReportRESTError(..#HTTP406NOTACCEPTABLE,$$$ERROR($$$RESTBadAccepts)) Quit
        If '$isobject(%request.Content) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR($$$RESTRequired,"body")) Quit
        Set requestMessage = ##class(petstoreserver.requests.addPet).%New()
        Do requestMessage.LoadFromRequest(%request)
        Set scValidateRequest = requestMessage.RequestValidate()
        If $$$ISERR(scValidateRequest) Do ##class(%REST.Impl).%ReportRESTError(..#HTTP400BADREQUEST,$$$ERROR(5001,"Invalid requestMessage object.")) Quit
        Set response = ##class(petstoreserver.impl).addPet(requestMessage)
        Do ##class(petstoreserver.impl).%WriteResponse(response)
    } Catch(ex) {
        Do ##class(%REST.Impl).%ReportRESTError(..#HTTP500INTERNALSERVERERROR,ex.AsStatus(),$parameter("petstoreserver.impl","ExposeServerExceptions"))
    }
    Quit sc
}
...
}

Como você pode ver, a classe de despacho chama “LoadFromRequest” e “RequestValidate” antes de invocar o método de implementação. Esses métodos possuem uma implementação padrão, mas o gerador de código não consegue cobrir todos os casos. Atualmente, os casos mais comuns são tratados automaticamente como parâmetros em "query" (consulta), "headers" (cabeçalhos), "path" (caminho) e no corpo (body) com os tipos de conteúdo “application/json”, “application/octet-stream” ou “multipart/form-data”. O desenvolvedor precisa verificar a implementação para checar/completar se necessário (por padrão, o gerador de código define $$$ThrowStatus($$$ERROR($$$NotImplemented)) para casos não suportados).

 

 
Exemplo de classe de requisição:

Assim como no uso de ^%REST, a classe "petstoreserver.impl" contém todos os métodos relacionados aos serviços que o desenvolvedor precisa implementar.

Class petstoreserver.impl Extends %REST.Impl [ ProcedureBlock ]
{

Parameter ExposeServerExceptions = 1;
/// Implementação do serviço para post /pet
ClassMethod addPet(messageRequest As petstoreserver.requests.addPet) As %Status
{
    ; Implemente seu serviço aqui
    ; Retorne {}
    $$$ThrowStatus($$$ERROR($$$NotImplemented))
}

...
}

 

Breve descrição dos pacotes gerados:

Nome do Pacote \ Nome da Classe

Tipo

Descrição

petstoreclient.model


petstoreproduction.model

Client-side e server-side

Ele contém todos os modelos. Essas classes estendem %JSON.Adaptor para facilitar o carregamento de objetos a partir de JSON.

Se uma produção for gerada, essas classes também estenderão %Persistent.

petstoreclient.requests

 


petstoreproduction.requests

Client-side e server-side

Objeto usado para inicializar facilmente %Net.HttpRequest. Existe uma classe por operação definida na especificação.

Se a produção for gerada, essas classes estenderão Ens.Request.

Nota: A implementação desta classe é diferente se for gerada para propósitos do lado do servidor ou do lado do cliente. No caso do lado do cliente, todas as classes contêm um método "LoadHttpRequestObject" permitindo carregar um "%Net.HttpRequest" a partir das propriedades desta classe.

Se as classes forem geradas para propósitos do lado do servidor, cada classe contém um método "LoadFromRequest" para carregar a instância a partir do objeto "%request".

petstoreclient.responses


petstoreproduction.responses

Client-side e server-side

É o oposto de petstoreclient.requests. Ele permite manipular a resposta de um %Net.HttpRequest.

Se a produção for gerada, essas classes estenderão Ens.Response.

petstoreclient.HttpClient

Client-side

Contém todos os métodos para executar requisições HTTP; existe um método por operação definida na especificação OpenAPI.

petstoreproduction. bo.Operation

Client-side

A classe de Operação possui um método por operação definida na especificação OpenAPI.

petstoreproduction.bp

Client-side

Dois processos de negócio padrão são definidos: síncrono e assíncrono.

petstoreproduction.bs

Client-side

Contém todos os serviços de negócio vazios para implementar.

petstoreproduction.Production

Client-side

Configuração de produção.

petstoreserver.disp

Server-side

Classe de despacho %CSP.REST.

petstoreserver.Spec

Server-side

Esta classe contém a especificação OpenAPI em um bloco XData.

petstoreserver.impl

Server-side

Ele contém todos os métodos vazios relacionados às operações definidas na especificação OpenAPI. Esta é a classe (que estende %REST.Impl) onde os desenvolvedores precisam implementar os serviços.

Status de desenvolvimento

O OpenAPI-Suite ainda é um produto muito novo e precisa ser mais testado e então aprimorado. O suporte ao OpenAPI 3 é parcial, mais possibilidades poderiam ser suportadas.

Os testes foram realizados com a especificação públicahttps://petstore3.swagger.io/api/v3/openapi.json e outras duas relativamente simples. Obviamente, isso não é suficiente para cobrir todos os casos. Se você tiver alguma especificação para compartilhar, ficaria feliz em usá-las para meus testes.

Acredito que a base do projeto é boa e ele pode evoluir facilmente, por exemplo, ser estendido para suportar AsyncAPI.

Não hesite em deixar feedback.

Espero que você goste desta aplicação e que ela mereça seu apoio para o concurso de ferramentas para desenvolvedores.

Obrigado por ler.

Discussão (0)1
Entre ou crie uma conta para continuar