Criando uma produção de interoperabilidade IRIS a partir do Swagger
Olá comunidade,
O OpenAPI-Client Gen acaba de ser lançado, este é um aplicativo para criar um cliente de produção de interoperabilidade IRIS a partir da especificação Swagger 2.0.
Em vez da ferramenta existente ^%REST que cria um aplicativo REST do lado do servidor, o OpenAPI-Client Gen cria um modelo de cliente de produção de interoperabilidade REST completo.
Instalação por ZPM:
zpm "install openapi-client-gen"
Como gerar produção a partir de um documento Swagger? É muito simples.
Abra um terminal e execute:
Set sc = ##class(dc.openapi.client.Spec).generateApp(<applicationName>, <Your Swagger 2.0 document>>)
O primeiro argumento é o pacote de destino onde as classes de produção serão geradas. Ele deve ser um nome de pacote válido e não existente.
O segundo, é o documento Swagger. Estes valores são aceitos:
1) File path.
2) %DynamicObject.
3) URL.
A especificação deve estar no formato JSON.
Se sua especificação usa o formato YAML, ela pode ser facilmente convertida em JSON com ferramentas on-line como onlineyamltools.com
Exemplo:
Set sc = ##class(dc.openapi.client.Spec).generateApp("petshop", "https://petstore.swagger.io:443/v2/swagger.json")
Write "Status : ", $SYSTEM.Status.GetOneErrorText(sc)
Dê uma olhada no código gerado, podemos ver muitas classes, divididas em muitos subpacotes:
- Business Services: petshop.bs
- Business Operations: petshop.bo
- Business Processes: petshop.bp
- Aplicação REST Proxy: petshop.rest
- Ens.Request e Ens.Response: petshop.msg
- Objeto de entrada ou saída analisado: petshop.model.Definition
- Classe de configuração de produção: petshop.Production
Classe de operação de negócios
Para cada serviço definido no documento Swagger, existe um método relacionado denominado por <VERB><ServiceId>
.
Aprofunde-se em um simples método gerado GETgetPetById
/// Retorna um único animal de estimação
Method GETgetPetById(pRequest As petshop.msg.getPetByIdRequest, pResponse As petshop.msg.GenericResponse) As %Status
{
Set sc = $$$OK, pURL = "/v2/pet/{petId}"
Set pHttpRequestIn = ..GetRequest(pRequest)
Set pHttpRequestIn.ContentType = pRequest.consume
Set pURL = $Replace(pURL, "{petId}", pRequest.pathpetId)
$$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "get", pHttpRequestIn , , , pURL))
Set pResponse = ##class(petshop.msg.GenericResponse).%New()
Set sc = ..genericProcessResponse(pRequest, pResponse, "GETgetPetById", sc, $Get(pHttpResponse),"petshop.msg.getPetByIdResponse")
Return sc
}
* Em primeiro lugar, o objeto %Net.HttpRequest é sempre criado pelo método GetRequest
, fique à vontade para editar e adicionar alguns cabeçalhos, se necessário.
* Em segundo lugar, o objeto HttpRequest é preenchido usando pRequest petshop.msg.getPetByIdRequest' (Ens.Request subclass).
EnsLib.HTTP.OutboundAdapter
* Em terceiro lugar,é usado para enviar solicitação http.
genericProcessResponse`:
* E, finalmente, há um processamento de resposta genérico pelo método
Method genericProcessResponse(pRequest As Ens.Request, pResponse As petshop.msg.GenericResponse, caller As %String, status As %Status, pHttpResponse As %Net.HttpResponse, parsedResponseClassName As %String) As %Status
{
Set sc = $$$OK
Set pResponse.operation = caller
Set pResponse.operationStatusText = $SYSTEM.Status.GetOneErrorText(status)
If $Isobject(pHttpResponse) {
Set pResponse.httpStatusCode = pHttpResponse.StatusCode
Do pResponse.body.CopyFrom(pHttpResponse.Data)
Set key = ""
For {
Set key = $Order(pHttpResponse.Headers(key),1 , headerValue)
Quit:key=""
Do pResponse.headers.SetAt(headerValue, key)
}
Set sc = ##class(petshop.Utils).processParsedResponse(pHttpResponse, parsedResponseClassName, caller, pRequest, pResponse)
}
Return sc
}
Então, podemos analisar um método um pouco mais complexo POSTuploadFile
Method POSTuploadFile(pRequest As petshop.msg.uploadFileRequest, pResponse As petshop.msg.GenericResponse) As %Status
{
Set sc = $$$OK, pURL = "/v2/pet/{petId}/uploadImage"
Set pHttpRequestIn = ..GetRequest(pRequest)
Set pHttpRequestIn.ContentType = pRequest.consume
Set pURL = $Replace(pURL, "{petId}", pRequest.pathpetId)
If pHttpRequestIn.ContentType = "multipart/form-data" {
Set valueStream = ##class(%Stream.GlobalBinary).%New()
Do:$Isobject(pRequest.formDataadditionalMetadata) valueStream.CopyFrom(pRequest.formDataadditionalMetadata)
Do:'$Isobject(pRequest.formDataadditionalMetadata) valueStream.Write($Zcvt(pRequest.formDataadditionalMetadata,"I","UTF8"))
Set:'$ISOBJECT($Get(mParts)) mParts = ##class(%Net.MIMEPart).%New()
Set mimePart = ##class(%Net.MIMEPart).%New(valueStream)
Do mimePart.SetHeader("Content-Disposition", "form-data; name=""additionalMetadata""; filename=""additionalMetadata""")
Do mParts.Parts.Insert(mimePart)
} Else {
Do pHttpRequestIn.InsertFormData("additionalMetadata", pRequest.formDataadditionalMetadata)
}
If pHttpRequestIn.ContentType = "multipart/form-data" {
Set valueStream = ##class(%Stream.GlobalBinary).%New()
Do:$Isobject(pRequest.formDatafile) valueStream.CopyFrom(pRequest.formDatafile)
Do:'$Isobject(pRequest.formDatafile) valueStream.Write($Zcvt(pRequest.formDatafile,"I","UTF8"))
Set:'$ISOBJECT($Get(mParts)) mParts = ##class(%Net.MIMEPart).%New()
Set mimePart = ##class(%Net.MIMEPart).%New(valueStream)
Do mimePart.SetHeader("Content-Disposition", "form-data; name=""file""; filename=""file""")
Do mParts.Parts.Insert(mimePart)
} Else {
Do pHttpRequestIn.InsertFormData("file", pRequest.formDatafile)
}
If $ISOBJECT($Get(mParts)) {
Set mimeWriter = ##class(%Net.MIMEWriter).%New()
Do mimeWriter.OutputToStream(.stream)
Do mimeWriter.WriteMIMEBody(mParts)
Set pHttpRequestIn.EntityBody = stream
Set pHttpRequestIn.ContentType = "multipart/form-data; boundary=" _ mParts.Boundary
}
$$$QuitOnError(..Adapter.SendFormDataArray(.pHttpResponse, "post", pHttpRequestIn , , , pURL))
Set pResponse = ##class(petshop.msg.GenericResponse).%New()
Set sc = ..genericProcessResponse(pRequest, pResponse, "POSTuploadFile", sc, $Get(pHttpResponse),"petshop.msg.uploadFileResponse")
Return sc
}
Como você pode ver, é exatamente a mesma lógica: GetRequest, preenchendo %Net.HttpRequest, enviar solicitação, processamento de resposta genérica.
Classe Proxy REST
Uma aplicação proxy REST também é gerada.
Esta classe REST usa um Projection para criar automaticamente a aplicação web relacionada (ex: "/petshoprest", consulte petshop.rest.REST e petshop.rest.Projection). Este proxy REST cria a mensagem Ens.Request e envia-a para o Business.Process.
Class petshop.rest.REST Extends %CSP.REST [ ProcedureBlock ]
{
Projection WebApp As petshop.rest.Projection;
...
ClassMethod POSTaddPet() As %Status
{
Set ensRequest = ##class(petshop.msg.addPetRequest).%New()
Set ensRequest.consume = %request.ContentType
Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
Set ensRequest.bodybody = ##class(petshop.model.Definition.Pet).%New()
Do ensRequest.bodybody.%JSONImport(%request.Content)
Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}
ClassMethod GETgetPetById(petId As %String) As %Status
{
Set ensRequest = ##class(petshop.msg.getPetByIdRequest).%New()
Set ensRequest.consume = %request.ContentType
Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
Set ensRequest.pathpetId = petId
Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}
...
ClassMethod POSTuploadFile(petId As %String) As %Status
{
Set ensRequest = ##class(petshop.msg.uploadFileRequest).%New()
Set ensRequest.consume = %request.ContentType
Set ensRequest.accept = $Get(%request.CgiEnvs("HTTP_ACCEPT"),"*/*")
Set ensRequest.pathpetId = petId
Set ensRequest.formDataadditionalMetadata = $Get(%request.Data("additionalMetadata",1))
set mime = %request.GetMimeData("file")
Do:$Isobject(mime) ensRequest.formDatafile.CopyFrom(mime)
Return ##class(petshop.Utils).invokeHostAsync("petshop.bp.Process", ensRequest, "petshop.bs.ProxyService")
}
...
}
Então, vamos testar a produção com este proxy REST.
Abra e inicie o petshop.Production
Crie um animal de estimação
Altere o número da porta, se necessário:
curl --location --request POST 'http://localhost:52795/petshoprest/pet' \
--header 'Content-Type: application/json' \
--data-raw '{
"category": {
"id": 0,
"name": "string"
},
"id" : 456789,
"name": "Kitty_Galore",
"photoUrls": [
"string"
],
"tags": [
{
"id": 0,
"name": "string"
}
],
"status": "available"
}'
A produção é executada no modo assíncrono, portanto, a aplicação proxy restante não espera pela resposta. Este comportamento pode ser editado, mas normalmente, a produção de interoperabilidade usa o modo assíncrono. Veja o resultado no visualizador de mensagens e no traço visual
EDIÇÃO: desde a versão 1.1.0, a aplicação Proxy Rest funciona em modo de sincronização
Se tudo estiver bem, podemos observar um código http de status 200. Como você pode ver, recebemos uma resposta do corpo e ela não está analisada.
O que isso significa?
Isso ocorre quando resposta da aplicação/json ou a resposta 200 da especificação Swagger não é preenchida.
Nesse caso, a resposta 200 não está preenchida.
Obtenha uma animal de estimação
Agora, tente obter o animal de estimação criado:
curl --location --request GET 'http://localhost:52795/petshoprest/pet/456789'
Verifique o traço visual:
Desta vez, esta é uma resposta da aplicação/json (a resposta 200 está completa nas especificações Swagger). Podemos ver um objeto de resposta analisado.
API REST - Gerar e baixar
Além disso, essa ferramenta pode ser hospedada em um servidor para permitir que os usuários gerem e baixem o código. Uma API REST e um formulário básico estão disponíveis:
* Aplicação web API REST: /swaggerclientgen/api
* Formulário básico: http://localhost:52795/csp/swaggerclientgen/dc.openapi.client.api.cspdem... O gerador está disponível on-line no meu servidor em nuvem aqui.
Nesse caso, o código é simplesmente gerado sem compilar, exportado e, em seguida, tudo é excluído.
Este recurso pode ser útil para centralização de ferramentas.
Veja o README.md para informações atualizadas. Obrigado pela leitura.