Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug]: Annotations in the service type is not visible at the service object at runtime #42760

Open
TharmiganK opened this issue May 16, 2024 · 5 comments
Labels
Priority/Blocker Team/CompilerFE All issues related to Language implementation and Compiler, this exclude run times. Type/Bug userCategory/Compilation

Comments

@TharmiganK
Copy link
Contributor

Description

I have defined a service object type and added serveral annotations to the type as well as the resource functions. Those annotations are not visible in runtime when I access the service object provided by the service declaration approach.

Steps to Reproduce

Sample Ballerina code:

import ballerina/http;

public type User record {|
    int id;
    string name;
    string email;
|};

public type UserNotFound record {|
    *http:NotFound;
    record {|
        int id;
        string message = "User not found";
    |} body;
|};

public type NewUser record {|
    string name;
    string email;
|};

public type NewPost record {|
    string content;
|};

public type PostWithMeta record {|
    int id;
    string content;
    string createdAt;
|};

public type PostForbidden record {|
    *http:Forbidden;
    record {|
        string message = "Post rejected due to sensitive content";
    |} body;
|};

type HttpServiceConfig record {|
    *http:HttpServiceConfig;
    string basePath;
|};

annotation HttpServiceConfig ServiceConfig on type, service;

type HttpResourceConfig record {|
    *http:HttpResourceConfig;
    record {|
        map<string> parameters?;
        string requestBody?;
        string responseBody?;
    |} examples?;
|};

annotation HttpResourceConfig ResourceConfig on object function;

@ServiceConfig {
    basePath: "social-media"
}
public type SocialMedia service object {
    *http:Service;

    @ResourceConfig {
        examples: {
            responseBody: "[{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@gmail.com\"}]"
        }
    }
    resource function get users(User rec) returns User[]|error;

    @ResourceConfig {
        examples: {
            responseBody: "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@gmail.com\"}"
        }
    }
    resource function get users/[int id]() returns User|UserNotFound|error;

    @ResourceConfig {
        examples: {
            requestBody: "{\"name\":\"Alice\",\"email\":\"alice@gmail.com\"}"
        }
    }
    resource function post users(NewUser newUser) returns http:Created|error;

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

    @ResourceConfig {
        examples: {
            parameters: {
                id: "1"
            },
            responseBody: "[{\"id\":1,\"content\":\"Hello World\",\"createdAt\":\"2021-09-01T10:00:00Z\"}]"
        }
    }
    resource function get users/[int id]/posts() returns PostWithMeta[]|UserNotFound|error;

    @ResourceConfig {
        examples: {
            parameters: {
                id: "1"
            },
            requestBody: "{\"content\":\"Hello World\"}"
        }
    }
    resource function post users/[int id]/posts(NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error;
};

@ServiceConfig {
    basePath: "test-social-media",
    mediaTypeSubtypePrefix: "vnd.test"
}
service SocialMedia /social\-media on new http:Listener(8080) {

    // @http:ResourceConfig {
    //     name: "users"
    // }
    resource function get users(User rec) returns User[]|error {
        return [
            {id: 1, name: "Alice", email: "alice@gmail.com"},
            {id: 2, name: "Bob", email: "bob@gmail.com"}
        ];
    }

    @http:ResourceConfig {
        name: "user"
    }
    resource function get users/[int id]() returns User|UserNotFound|error {
        return {id: 1, name: "Alice", email: "alice@gmail.com"};
    }

    @http:ResourceConfig {
        name: "users"
    }
    resource function post users(NewUser newUser) returns http:Created|error {
        return http:CREATED;
    }

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

    @http:ResourceConfig {
        name: "user-posts"
    }
    resource function get users/[int id]/posts() returns PostWithMeta[]|UserNotFound|error {
        return [
            {id: 1, content: "Hello World", createdAt: "2021-09-01T10:00:00Z"},
            {id: 2, content: "Hello Ballerina", createdAt: "2021-09-01T10:00:00Z"}
        ];
    }

    @http:ResourceConfig {
        name: "user-posts"
    }
    resource function post users/[int id]/posts(NewPost newPost) returns http:Created|UserNotFound|PostForbidden|error {
        return http:CREATED;
    }
}

Affected Version(s)

Ballerina SwanLake Update 9 (2201.9.0)

OS, DB, other environment details and versions

No response

Related area

-> Compilation

Related issue(s) (optional)

No response

Suggested label(s) (optional)

No response

Suggested assignee(s) (optional)

No response

@SasinduDilshara
Copy link
Contributor

The related spec section is https://ballerina.io/spec/lang/master/#section_8.3.2

@SasinduDilshara
Copy link
Contributor

According to the spec this annotation should be visible

The static type S for the constructed service object is specified by the type-descriptor if present

@MaryamZi
Copy link
Member

IMO, the current behaviour is correct.

Service declaration

The static type S for the constructed service object is specified by the type-descriptor if present, and otherwise is the union of a type inferred from each expression in the expression list as follows. Each expression in the expression-list must have a type Listener<T,A>|E where T is a subtype of service object {}, A is a subtype of string[]|string|() and E is a subtype of error; the inferred type is T. The object-constructor-block has the same semantics as in an object-constructor-expr with a service qualifier and a type-reference that refers to S. If the service-decl includes isolated, then it is equivalent to an object-constructor-expr with an isolated qualifier as well as a service qualifier.

Object constructor

The annotations applying to the object-constructor-expr and its members are evaluated when the object-constructor-expr is evaluated. This means that every object resulting from the evaluation of an object-constructor-expr has its own type descriptor.

If there is a type-reference, then the referenced type is included in the object's type in the same was as an object-type-inclusion, and the type-ids of the constructed object are type-ids of the referenced type. If there is no type-reference, then the applicable contextually expected type T, if any, must be definite, and the type-ids of the constructed object are the type-ids, if any, induced by T.

Static type for the constructor and inherent type of the constructed object aren't the same. The static type is just the contextually-expected type for the constructor. The annotations come from the constructor, not the static type.

The actual object constructor can have the same annotations as the expected type, but with different values.

Since we don't have implementation inheritance, a method will always be implemented in an object constructor or a class, with which we can then include annotations. If you need SocialMedia's annotations, you should be able to get to them via the implicit type inclusion of SocialMedia in the constructed object.

May be different for fields though - #38535.

@MaryamZi MaryamZi added Team/CompilerFE All issues related to Language implementation and Compiler, this exclude run times. and removed needTriage The issue has to be inspected and labeled manually labels May 17, 2024
@LakshanWeerasinghe
Copy link
Contributor

@MaryamZi Are you suggesting to get the annotation values of the SocialMedia type and combine those with the service constructor annotations? If yes, since we are not supporting inheritance doing this also not correct right?

@MaryamZi
Copy link
Member

@MaryamZi Are you suggesting to get the annotation values of the SocialMedia type and combine those with the service constructor annotations? If yes, since we are not supporting inheritance doing this also not correct right?

Not suggesting combining per se, just suggesting how to access the annotations, it's up to the user to decide how/when to access what.

Generally, I'd expect relying on the annotations from the constructor.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Priority/Blocker Team/CompilerFE All issues related to Language implementation and Compiler, this exclude run times. Type/Bug userCategory/Compilation
Projects
None yet
Development

No branches or pull requests

5 participants