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:
- Nome do pacote da aplicação: este é o pacote usado para as classes geradas. Deve ser um nome de pacote inexistente.
- Selecione o que você deseja gerar: Cliente HTTP, Produção Cliente ou Servidor REST.
- 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.
- 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.
- 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()
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).
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 |
|
|
|
|
Ele contém todos os modelos. Essas classes estendem Se uma produção for gerada, essas classes também estenderão |
|
|
Objeto usado para inicializar facilmente Se a produção for gerada, essas classes estenderão 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 " Se as classes forem geradas para propósitos do lado do servidor, cada classe contém um método " |
|
|
É o oposto de Se a produção for gerada, essas classes estenderão |
|
|
Contém todos os métodos para executar requisições HTTP; existe um método por operação definida na especificação OpenAPI. |
|
|
A classe de Operação possui um método por operação definida na especificação OpenAPI. |
|
|
Dois processos de negócio padrão são definidos: síncrono e assíncrono. |
|
|
Contém todos os serviços de negócio vazios para implementar. |
|
|
Configuração de produção. |
|
|
Classe de despacho %CSP.REST . |
|
|
Esta classe contém a especificação OpenAPI em um bloco XData. |
|
|
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.