ballerina-platform / ballerina-library

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

OpenAPI spec generation is not supported for HTTP service declarations which are defined using service object-types #6615

Closed ayeshLK closed 3 weeks ago

ayeshLK commented 2 months ago

Description:

$subject

Related to proposal: #6378

TharmiganK commented 2 months ago

With the above proposal, openapi-tool generates the following service type with all the HTTP annotation details:

@http:ServiceContractConfig {
    basePath: "/socialMedia"
}
@http:ServiceConfig {
    auth: [
        {
            fileUserStoreConfig: {},
            scopes: ["admin"]
        }
    ]
}
public type Service service object {
    *http:ServiceContract;
    @http:ResourceConfig {
        name: "users"
    }
    resource function get users() returns socialMedia:User[]|error;

    @http:ResourceConfig {
        name: "user",
        linkedTo: [
            {name: "user", method: http:DELETE, relation: "delete-user"},
            {name: "posts", method: http:POST, relation: "create-posts"},
            {name: "posts", method: http:GET, relation: "get-posts"}
        ]
    }
    resource function get users/[int id]() returns @http:Payload {mediaType: "application/org+json"} socialMedia:User|socialMedia:UserNotFound|error;

    @http:ResourceConfig {
        name: "users",
        linkedTo: [
            {name: "user", method: http:GET, relation: "get-user"},
            {name: "user", method: http:DELETE, relation: "delete-user"},
            {name: "posts", method: http:POST, relation: "create-posts"},
            {name: "posts", method: http:GET, relation: "get-posts"}
        ]
    }
    resource function post users(socialMedia:NewUser newUser) returns http:Created|error;

    @http:ResourceConfig {
        name: "user"
    }
    resource function delete users/[int id]() returns http:NoContent|error;

    @http:ResourceConfig {
        name: "posts"
    }
    resource function get users/[int id]/posts() returns socialMedia:PostWithMeta[]|socialMedia:UserNotFound|error;

    @http:ResourceConfig {
        name: "posts",
        linkedTo: [
            {name: "posts", method: http:POST, relation: "create-posts"}
        ]
    }
    resource function post users/[int id]/posts(socialMedia:NewPost newPost) returns http:Created|socialMedia:UserNotFound|socialMedia:PostForbidden|error;
};

The implementation will look like this: (The service type is imported via a separate package, the service type can be in the same package as well)

service socialMedia:Service "/social-media" on new http:Listener(8080) {

    resource function get users() returns socialMedia:User[]|error {
        return [];
    }

    resource function get users/[int id]() returns socialMedia:User|socialMedia:UserNotFound|error {
        return {id: 1, name: "John Doe", email: "johndoe@gmail.com"};
    }

    resource function post users(socialMedia:NewUser newUser) returns http:Created|error {
        return http:CREATED;
    }

    resource function delete users/[int id]() returns http:NoContent|error {
        return http:NO_CONTENT;
    }

    resource function get users/[int id]/posts() returns socialMedia:PostWithMeta[]|socialMedia:UserNotFound|error {
        return [];
    }

    resource function post users/[int id]/posts(socialMedia:NewPost newPost) returns http:Created|socialMedia:UserNotFound|socialMedia:PostForbidden|error {
        return http:CREATED;
    }
}

In the above service declaration,

  1. None of the HTTP annotations are allowed, all of them are inferred from the service type
  2. Additional resources which are not defined in the service type, are not allowed

So for this service declaration, the openapi should be generated from the service type rather than the service declaration. Please note that the service type is a subtype of http:ServiceContract, this makes sure that the normal service types are not going to break with the changes. So, we should only generate the OAS from the service type which is a subtypes of http:ServiceContract

TharmiganK commented 2 months ago

The HTTP related changes are WIP in this branch: https://github.com/ballerina-platform/module-ballerina-http/tree/service-type-support You can use this timestamped version for testing: 2.11.1-20240607-135600-e67eb21

TharmiganK commented 2 months ago

The current open-api spec generation is using the compiler syntax tree. But when we use a service contract type, we cannot access the syntax tree if the service type is outside the package (having service type in a separate package is a use case).

Even though we implement a generator using the semantic model, it is not possible for get the values of annotations using the existing APIs since the annotations are not constant.

If we consider the idea behind the service contract, then we actually know the open-api spec from the type itself. So, the open-api tool should be able to generate a spec from the service type as well(This is a non-goal of the proposal). If that is possible, we can actually generate and embed the open-api spec into the service contract type and whenever we need a open-api spec from the service implementation using a service contract type, we should be able to get it directly from the type.

This approach has two major issues:

  1. The open-api spec generation should be done by the http compiler plugin (This should ideally fix this issue as well: https://github.com/ballerina-platform/ballerina-library/issues/6511)
  2. If we add the open-api spec to the annotation, we cannot access it from the semantic model unless the annotation is constant
TharmiganK commented 1 month ago

We are planning to utilise the static resources rather than constant annotations. We need APIs to read and write the resources at compile time, this issue has been tracked via https://github.com/ballerina-platform/ballerina-lang/issues/43041