Nova postagem

Pesquisar

Artigo
· 12 hr atrás 7min de leitura

How to easily add a validation against OpenAPI specifications to your REST APIs

In this article, I aim to demonstrate a couple of methods for easily adding validation to REST APIs on InterSystems IRIS Data Platform. I believe a specification-first approach is an excellent idea for API development. IRIS already has features for generating an implementation stub from a specification and publishing that specification for external developers (use it with iris-web-swagger-ui for the best results). The remaining important thing not yet implemented in the platform is the request validator. Let's fix it!

The task sounds as follows: all inbound requests must be validated against the schema of the API described in OpenAPI format. As you know, the request contains: method (GET, POST, etc.), URL with parameters, headers (Content-Type, for example), and body (some JSON). All of it can be checked. To solve this task, I will be using Embedded Python because the rich library of open source Python code already has 2 suitable projects: openapi-core, openapi-schema-validator. One limitation here is that the IRIS Data Platform is using Swagger 2.0, an obsolete version of OpenAPI. Most of the tools do not support this version, so the first implementation of our validator will be restricted to checking the request body only.

Solution based on the openapi-schema-validator

Key inputs:

  • Solution is fully compatible with the recommended InterSystems specification-first way in API development. No need for modifications in the generated API classes, except for the one small, more on this later
  • Checking the request body only
  • We need to extract the definition for the request type from the OpenAPI specification (class spec.cls
  • The matching request JSON to the spec definition is done by setting a vendor-specific content type

First, you need to set a vendor-specific content type in the consumes property of the OpenAPI specification for your endpoint. Which one must look something like this: vnd.<company>.<project>.<api>.<request_type>+json. For example, I will use:

"paths":{
      "post":{
        "consumes":[
          "application/vnd.validator.sample_api.test_post_req+json"
        ],
...

Next, we need a base class for our dispatch class. Here is the full code of this class; the code is also available on Git.

Class SwaggerValidator.Core.REST Extends %CSP.REST
{

Parameter UseSession As Integer = 1;
ClassMethod OnPreDispatch(pUrl As %String, pMethod As %String, ByRef pContinue As %Boolean) As %Status
{
	Set tSC = ..ValidateRequest()
    
    If $$$ISERR(tSC) {
        Do ..ReportHttpStatusCode(##class(%CSP.REST).#HTTP400BADREQUEST, tSC)
        Set pContinue = 0
    }

    Return $$$OK
}

ClassMethod ValidateRequest() As %Status
{
    Set tSC = ##class(%REST.API).GetApplication($REPLACE($CLASSNAME(),".disp",""), .spec)
    Return:$$$ISERR(tSC) tSC

    Set defName = $PIECE($PIECE(%request.ContentType, "+", 1), ".", *)
    Return:defName="" $$$ERROR($$$GeneralError, $$$FormatText("No definition name found in Content-Type = %1", %request.ContentType))
    
    Set type = spec.definitions.%Get(defName)
    Return:type="" $$$ERROR($$$GeneralError, $$$FormatText("No definition found in specification by name = %1", defName))
    
    Set schema = type.%ToJSON() 
    Set body = %request.Content.Read()

    Try {Set tSC = ..ValidateImpl(schema, body)} Catch ex {Set tSC = ex.AsStatus()}

    Return tSC
}

ClassMethod ValidateImpl(schema As %String, body As %String) As %Status [ Language = python ]
{
    try:
        validate(json.loads(body), json.loads(schema))
    except Exception as e:
        return iris.system.Status.Error(5001, f"Request body is invalid: {e}")

    return iris.system.Status.OK()
}

XData %import [ MimeType = application/python ]
{
import iris, json
from openapi_schema_validator import validate
}

}

We are doing the next things here:

  1. Overrides OnPreDispatch() for adding validation. This code will execute for each call of our API
  2. Uses ##class(%REST.API).GetApplication() to get the specification in a dynamic object (JSON)
  3. Extracts the definition name from the Content-Type header
  4. Takes the request schema by definition name: spec.definitions.%Get(defName)
  5. Sends request schema + request body to Python code for validation

As you see, it is all pretty simple. Now all you need to do is change the Extends section of your disp.cls to SwaggerValidator.Core.REST. And of course, install the openapi-schema-validator Python library to the server (as described here).

Solution based on the openapi-core

Key inputs:

  • This solution works with a hand-coded REST interface. We do not use API Management tools to generate the code from the OpenAPI specification. We only have a REST service as a %CSP.REST subclass
  • So, we are not attached to the 2.0/JSON version and will be using OpenAPI 3.0 in YAML format. This version offers more opportunities, and I find YAML more readable
  • The following elements will be checked: path and query parameters in the URL, Content-Type, and request body

For starters, let's take our specification located on <server>/api/mgmnt/v1/<namespace>/spec/<web-application>. Yes, we have a generated OpenAPI specification even for manually coded REST APIs. This is not a complete spec because it has no schemas of requests and responses (the generator does not know where to get them). But the platform has already done half the work for us. We need to convert this specification to OpenAPI 3.0/YAML format and add definitions for request/responses. You can use a converter or just ask Codex:

Please, convert spec in class @Spec.cls to Swagger version 3.0 and YAML format

In the same way, we can ask Codex to generate request/response schemas based on JSON samples.

BTW, vibe coding works pretty well in IRIS development, but it is a subject for a separate topic. Please, let me know if it is interesting for you!

As in the previous solution, we need a base class for our %CSP.REST. This class is very similar:

Class SwaggerValidator.Core.RESTv2 Extends %CSP.REST
{

Parameter UseSession As Integer = 1;
ClassMethod OnPreDispatch(pUrl As %String, pMethod As %String, ByRef pContinue As %Boolean) As %Status
{
	Set tSC = ..ValidateRequest()
    
    If $$$ISERR(tSC) {
        Do ..ReportHttpStatusCode(##class(%CSP.REST).#HTTP400BADREQUEST, tSC)
        Set pContinue = 0
    }

    Return $$$OK
}

ClassMethod ValidateRequest() As %Status
{
    Set tSC = ..GetSpec(.swagger) 
    Return:$$$ISERR(tSC)||(swagger="") tSC

    Set canonicalURI = %request.CgiEnvs("REQUEST_SCHEME")_"://"_%request.CgiEnvs("HTTP_HOST")_%request.CgiEnvs("REQUEST_URI")
    Set httpBody = $SELECT($ISOBJECT(%request.Content)&&(%request.Content.Size>0):%request.Content.Read(), 1:"")
    Set httpMethod = %request.CgiEnvs("REQUEST_METHOD")
    Set httpContentType = %request.ContentType
    Try {
        Set tSC = ..ValidateImpl(swagger, canonicalURI, httpMethod, httpBody, httpContentType)
    } Catch ex {
        Set tSC = ex.AsStatus()
    }

    Return tSC
}

/// The class Spec.cls must be located in the same package as the %CSP.REST implementation
/// The class Spec.cls must contain an XData block named 'OpenAPI' with swagger 3.0 specification (in YAML format) 
ClassMethod GetSpec(Output specification As %String, xdataName As %String = "OpenAPI") As %Status
{
    Set specification = ""
    Set specClassName = $CLASSNAME()
    Set $PIECE(specClassName, ".", *) = "Spec"
    Return:'##class(%Dictionary.ClassDefinition).%Exists($LISTBUILD(specClassName)) $$$OK
    Set xdata = ##class(%Dictionary.XDataDefinition).%OpenId(specClassName_"||"_xdataName,,.tSC)
    If $$$ISOK(tSC),'$ISOBJECT(xdata)||'$ISOBJECT(xdata.Data)||(xdata.Data.Size=0) {
		Set tSC = $$$ERROR($$$RESTNoRESTSpec, xdataName, specClassName)
	}
    Return:$$$ISERR(tSC) tSC
    
    Set specification = xdata.Data.Read()
    Return tSC
}

ClassMethod ValidateImpl(swagger As %String, url As %String, method As %String, body As %String, contentType As %String) As %Status [ Language = python ]
{
    spec = Spec.from_dict(yaml.safe_load(swagger))
    data = json.loads(body) if (body != "") else None
    headers = {"Content-Type": contentType}
    
    req = requests.Request(method=method, url=url, json=data, headers=headers).prepare()
    openapi_req = RequestsOpenAPIRequest(req)

    try:
        validate_request(openapi_req, spec=spec)
    except Exception as ex:
        return iris.system.Status.Error(5001, f"Request validation failed: {ex.__cause__ if ex.__cause__ else ex}")

    return iris.system.Status.OK()
}

XData %import [ MimeType = application/python ]
{
import iris, json, requests, yaml
from openapi_core import Spec, validate_request
from openapi_core.contrib.requests import RequestsOpenAPIRequest
}

}

What to look out for: a class that contains a specification must be named Spec.cls and located in the same package as your %CSP.REST implementation. Specification class looks like:

Class Sample.API.Spec Extends %RegisteredObject
{

XData OpenAPI [ MimeType = application/yaml ]
{
    ... your YAML specification ...
}
}

To enable validation, you just need to extend your API class by inheriting from SwaggerValidator.Core.RESTv2 and place the Spec.cls file next to it.

That is all that I wanted to tell you about Swagger validation. Please feel free to ask me questions.

Discussão (0)1
Entre ou crie uma conta para continuar
Pergunta
· 16 hr atrás

isc-rest dependency error

I want to implement isc-rest in my new project iris-budget/module.xml at master · oliverwilms/iris-budget

I get this error:

#8 11.30 Building dependency graph...
#8 12.69 ERROR! Could not find satisfactory version of isc.rest in any repositories. Required by: iris-budget 0.0.1: ^2.0.0
#8 ERROR: process "/bin/sh -c iris start IRIS && \tiris session IRIS < iris.script &&     ([ $TESTS -eq 0 ] || iris session iris -U $NAMESPACE \"##class(%ZPM.PackageManager).Shell(\\\"test $MODULE -v -only\\\",1,1)\") &&     iris stop IRIS quietly" did not complete successfully: exit code: 1
 

2 novos comentários
Discussão (2)2
Entre ou crie uma conta para continuar
Anúncio
· 21 hr atrás

[Video] Optimizing Query Performance in Health Insight

Hey Community!

We're happy to share a new video from our InterSystems Developers YouTube:

     ⏯  Optimizing Query Performance in Health Insight @ Ready 2025

Boost the efficiency of your data queries with the latest Health Insight updates. Learn how new features can streamline analytics and deliver faster, more actionable insights.

 Presenters:
🗣 @Kandy Rathinasamy, Senior Product Manager at InterSystems
🗣 Sofia Lis, Principal Developer at InterSystems

Enjoy watching, and subscribe for more videos! 👍

Discussão (0)1
Entre ou crie uma conta para continuar
Pergunta
· Fev. 14

Error when starting container

I am working on a new project oliverwilms/iris-budget
based on intersystems-community/iris-fullstack-template: This template shows you how to build, test and deploy a simple full-stack application using InterSystems IRIS REST API

I do not understand why I get this error:

 

[INFO] ...started InterSystems IRIS instance IRIS
[INFO] Executing command /docker-entrypoint.sh iris-after-start ...
[INFO] Create namespace: USER
[ERROR] module 'iris' has no attribute 'system'
[ERROR] Command "/docker-entrypoint.sh iris-after-start " exited with status 256
[FATAL] Error executing post-startup command
[INFO] Shutting down InterSystems IRIS instance IRIS...

1 novo comentário
Discussão (1)1
Entre ou crie uma conta para continuar
Artigo
· Fev. 14 2min de leitura

Opere o banco de dados por meio de diálogo

Prompt

Primeiramente, precisamos entender o que são palavras de prompt e quais são suas funções.

Engenharia de Prompt

A engenharia de palavras de prompt é um método especificamente projetado para otimizar modelos de linguagem.
Seu objetivo é orientar esses modelos a gerar textos de saída mais precisos e direcionados por meio do design e ajuste das palavras de prompt de entrada.

Funções principais dos prompts

  • Melhorar a correspondência de conteúdo: Ao expressar suas necessidades com precisão, a IA pode gerar conteúdo de alta qualidade que atende melhor às expectativas
  • Eficiência e otimização de tempo: Um prompt claro pode obter diretamente a resposta desejada, reduzindo o tempo gasto em ajustes repetidos e aumentando significativamente a eficiência.
  • Facilidade de programação: A IA pode retornar o conteúdo correspondente de acordo com suas próprias necessidades, facilitando o processamento

Introdução à Aplicação

A aplicação iris-data-analysis é na verdade uma demonstração simples que utiliza palavras de prompt. Ela pode processar banco de dados por meio de diálogo, usando a configuração AI AgentBO.OpenAI com URL, APIKEY e o nome do Modelo, para chamar a interface da IA. Ao usar palavras de prompt para instruir a IA a retornar o SQL necessário, podemos processar e executar o SQL retornado pela IA para obter os dados que precisamos.

Exemplo

Por exemplo, eu gostaria de saber na tabela AI_Agent_DB.UserInfo quantos usuários do sexo masculino e quantos do sexo feminino existem. Posso escrever as palavras de prompt assim:

texto

Como especialista em SQL, por favor, gere instruções SQL apropriadas com base nos seguintes requisitos:
demand: {"message"}
Por favor, retorne apenas as instruções SQL, sem qualquer explicação adicional. Se for criar uma tabela, inclua os tipos de campos apropriados e as restrições necessárias.

 

 

 

A IA retornará apenas SQL na conversa com você para operações subsequentes, e também vetorizará a mensagem solicitada para verificar a similaridade do problema por meio de recuperação vetorial.

and also vectorize the requested message to view the similarity of the problem through vector retrieval.

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