新しい投稿

検索

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
記事
· 17 hr 前 7m read

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.

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
質問
· 21 hr 前

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件の新着コメント
ディスカッション (2)2
続けるにはログインするか新規登録を行ってください
お知らせ
· 2026年2月14日

[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! 👍

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
質問
· 2026年2月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件の新着コメント
ディスカッション (1)1
続けるにはログインするか新規登録を行ってください