ballerina-platform/ballerina-library

The OpenAPI introspection resource returns an incorrect specification

Closed this issue · 5 comments

Description:

We can add an introspection resource to an HTTP service by adding the following OpenAPI annotation:

@openapi:ServiceInfo {
    embed: true
}

When the OpenAPI contract path is not specified in the above annotation, OpenAPI compiler plugin checks that and we generate the OpenAPI specification by passing the service declaration node to the OpenAPI tool. The generate specification is added to the http:ServiceConfig annotation via a code modifier in the OpenAPI tool. Then the HTTP compiler plugin reads that field and generate a introspection resource and inject that to the existing service.

But the issue arises with the HTTP code modifier which actually adds the @http:Payload annotation to the request payload. Since this code modifier is engaged after the OpenAPI compiler plugin, the OpenAPI tool tries to generate the specification without this specific annotation which ends up in a incorrect specification.

Steps to reproduce:

Run the following service:

import ballerina/http;
import ballerina/openapi;

type Post record {
    string text;
};

@openapi:ServiceInfo {
    embed: true
}
service /text\-processing on new http:Listener(9098) {

    resource function post api/sentiment(Post post) {}
}

Use the introspection resource to retrieve the specification:

$ curl http://localhost:9098/text-processing/openapi-doc-dygixywsw
{
  "openapi" : "3.0.1",
  "info" : {
    "title" : "Text Processing",
    "version" : "0.1.0"
  },
  "servers" : [ {
    "url" : "{server}:{port}/text-processing",
    "variables" : {
      "server" : {
        "default" : "http://localhost"
      },
      "port" : {
        "default" : "9098"
      }
    }
  } ],
  "paths" : {
    "/api/sentiment" : {
      "post" : {
        "operationId" : "postApiSentiment",
        "parameters" : [ {
          "name" : "post",
          "in" : "query",
          "required" : true,
          "content" : {
            "application/json" : {
              "schema" : {
                "$ref" : "#/components/schemas/Post"
              }
            }
          }
        } ],
        "responses" : {
          "202" : {
            "description" : "Accepted"
          },
          "400" : {
            "description" : "BadRequest",
            "content" : {
              "application/json" : {
                "schema" : {
                  "$ref" : "#/components/schemas/ErrorPayload"
                }
              }
            }
          }
        }
      }
    }
  },
  "components" : {
    "schemas" : {
      "ErrorPayload" : {
        "required" : [ "message", "method", "path", "reason", "status", "timestamp" ],
        "type" : "object",
        "properties" : {
          "timestamp" : {
            "type" : "string"
          },
          "status" : {
            "type" : "integer",
            "format" : "int64"
          },
          "reason" : {
            "type" : "string"
          },
          "message" : {
            "type" : "string"
          },
          "path" : {
            "type" : "string"
          },
          "method" : {
            "type" : "string"
          }
        }
      },
      "Post" : {
        "required" : [ "text" ],
        "type" : "object",
        "properties" : {
          "text" : {
            "type" : "string"
          }
        }
      }
    }
  }
}

Note : the Post is considered as query parameter but it should be the request body.

Affected Versions:

Ballerina SwanLake Update 2201.9.0

AFAIR code modifiers execute before compiler plugin code analyzers, and those modifiers, as well as the analyzer, cannot preserve a specific order while executing. Since both HTTP and OpenAPI plugins are code modifiers, shall we check any mechanism to guarantee that the code modification has occurred? (For example, maintaining a cache or having a private flag to the service node)

Adding @azinneera for suggestions

ATM, we don't guarantee the order of execution of the modifier plugins. What is the behavior we expect here?

There is two imports openapi and http. Both have code modifiers. The requirement here is running the code modifier of the http module before running the openapi one.

We are planning to refactor the design using static resources. We need APIs to read and write the resources from compiler time. This has been tracked via: ballerina-platform/ballerina-lang#43041