ballerina-platform / ballerina-library

The Ballerina Library
https://ballerina.io/learn/api-docs/ballerina/
Apache License 2.0
136 stars 58 forks source link

Migrate the OAS generation in the annotation to a static resource #6715

Closed TharmiganK closed 3 weeks ago

TharmiganK commented 1 month ago

Description

Current introspection and service contract type implementations uses code modifiers to add the generated OpenAPI specification to the annotation. Since we cannot ensure the order of execution for the code modifiers, it is advised to use the static resources to implement the above use cases. This static resource support will be available with Ballerina SwanLake Update 10.

Tasks

Introspection resources

Feature overview

Ballerina allows users to embed the OpenAPI specification in a service and expose it as introspection resources.

There are two ways of embedding the OpenAPI specification:

  1. Manually providing the path in the openapi:ServiceInfo annotation:

    @openapi:ServiceInfo {
       embed: true,
       contract: "resources/social-media.yaml"
    }
    service /socialMedia on new http:Listener(9090) {
       ...
    }
  2. Auto generation using the OpenAPI tool:

    @openapi:ServiceInfo {
       embed: true
    }
    service /socialMedia on new http:Listener(9090) {
       ...
    }

When embed is set to true we add the OpenAPI specification as a byte[] in the http:ServiceConfig annotation. This is done by a code modifier:

@http:ServiceConfig {
   openApiDefinition: <---byte-array-version-of-oas--->
}
service /socialMedia on new http:Listener(9090) {
   ...
}

At run-time we expose the following resources for OpenAPI introspection:

These resources are attached to the service at native level by reading the content of the openApiDefinition field in the http:ServiceConfig annotation.

Changes with static resources

Rather than adding the OpenAPI specification to the http:ServiceConfig using a code modifier, it should be added as a static resource in the target directory and should be read at runtime.

If the OpenAPI specification is manually added via the contract field in the openapi:ServiceInfo annotation, that path should be used to read the OpenAPI specification. Otherwise the OpenAPI specification should be generated and added as a resource in the <target-dir>/resources.

The static resource creation for OpenAPI should be performed in a code analyzer.

The path to the target directory can be obtained by the Ballerina platform APIs.

The resources in the target directory should be available in the class path and use the Java resources API to load them at runtime.

When generating the OpenAPI resource file, there should be a one to one mapping between the service node and the file name. We already have a unique id for all the service declarations from ballerina-lang. This is given in the constant annotation IntrospectionDocConfig:

@IntrospectionDocConfig {
    name: "246988090823769"
}
service /socialMedia on new http:Listener(9090) {
   ...
}

The name field in the IntrospectionDocConfig has the hash code of the ServiceDeclarationSymbol which is accessible via the compiler plugin. The hash code is generated with the combination of symbol name, ModuleId and the Location. Since the OpenAPI generation happens at the code analyzer, it is safe to assume that there will not be any modifications to the service declaration.

Service Contract Type

Feature overview

Developers can use a contract first approach to define the service type first and then they could implement that service type.

@http:ServiceConfig {
   basePath: "/socialMedia"
}
public type SocialMediaService service object {
   *http:ServiceContract;

   ...
}

service SocialMediaService on http:Listener(9090) {
   ...
}

When a service is implemented via the service contract, the OpenAPI specification should be obtained from the service contract type rather than the service declaration.

To support that, the OpenAPI specification is generated for the service contract type and added in a constant annotation: http:ServiceContractInfo. This is done via a code modifier.

@http:ServiceConfig {
   basePath: "/socialMedia"
}
@http:ServiceContractInfo {
   openApiDefinition: <---oas-in-encoded-string-format--->
}
public type SocialMediaService service object {
   *http:ServiceContract;

   ...
}

With this change, whenever we try to obtain the OpenAPI specification from the service declaration which is implemented via the service contract, we generate the OpenAPI specification by incorporating the listener information from the service declaration into the existing OpenAPI specification in the service contract type. This happens at compile time.

The service contract types can be public and they can be implemented in a different package by importing the type. Since we are adding a constant annotation we could read this value from the semantic APIs.

Changes with static resources

Rather than adding the OpenAPI specification in the http:ServiceContractInfo constant annotation using a code modifier, it should be added as a resource.

When adding the OpenAPI specification as a resource, the following should be considered:

  1. Single Ballerina file: The generated OpenAPI specification should be placed in the <target-dir>/resources. The target directory should be obtained from the platform APIs
  2. Ballerina project:

    1. The service contract type is not public: The generated OpenAPI specification should be placed in the <target-dir>/resources. The target directory should be obtained from the platform APIs

    2. The service contract type is public: The generated OpenAPI specification should be placed in the resources directory of the package. Since this public service type can be referred to in external projects, we need to pack the OpenAPI specification as a resource

The generated OpenAPI specification should have a unique name to access at compile time.

  1. Single Ballerina file: Use the service type name
  2. Ballerina project: Use the service type with the combination of org name, package name and module name

Clean up tasks

Related Issues

TharmiganK commented 1 month ago

The lang support issue: https://github.com/ballerina-platform/ballerina-lang/issues/43041

TharmiganK commented 1 month ago

Experienced the following issues:

  1. Since the files are generated in the code analyzer, they are getting generated when ever language server load the project.
  2. The OpenAPI specification created in the root resources are not packed in the bala since the project is loaded before generating the resources.

Due to the above issues the design has been altered as follows:

  1. Introspection resources:

    The file will be created in the <target-dir>/resources directory but the file generation will only happen when the HTTP code modifiers(payload annotation modifier) are executed.

    1. Service contract type:

      A service type visitor will run with OpenAPI code analyzer to get all the service contract types. If the service is implemented by a service contract in the same project then we use the information from the service type visitor to generate the OpenAPI specification. If the service is implemented by a service contract which is imported then load the OpenAPI template from the bala.

      For service contract types which are public, we need to generate and pack the OpenAPI template in the bala. For this, the file should be placed in the <target-dir>/resources directory.