sahanhe
sahanhe

Reputation: 190

Payload data binding doesn't work when the return is a union type

I'm trying to run the access the following service in my code.

Following code segment illustrates the service:

import ballerina/http;

type Album readonly & record {|
    string title;
    string artist;
|};

type IdNotFound record {|
    *http:NotFound;
    record {
        string message;
    } body;
|};

table<Album> key(title) albums = table [
    {title: "Blue Train", artist: "John Coltrane"},
    {title: "Jeru", artist: "Gerry Mulligan"}
];

service / on new http:Listener(8022) {

    resource function get album/[string id]() returns Album|IdNotFound {
        if (!albums.hasKey(id)) {
            return {body: {message: "No matching resource found."}};
        }
        return albums.get(id);
    }
}

Following is my code in main.bal file

import ballerina/http;
import ballerina/io;

type Album readonly & record {
    string title;
    string artist;
};

type IdNotFound record {|
    *http:NotFound;
    record {
        string message;
    } body;
|};

public function main() returns error? {
    // Creates a new client with the Basic REST service URL.
    http:Client albumClient = check new ("localhost:8022");

    string id = "Blue Train";
    Album|IdNotFound album = check albumClient->/album/[id];
    io:println("GET request:" + album.toJsonString());
}

When tried to run this code, I'm getting the following error in line Album|IdNotFound album = check albumClient->/album/[id];

line

incompatible type for parameter 'targetType' with inferred typedesc value: expected 'typedesc<(ballerina/http:2.8.0:Response|anydata)>', found 'typedesc<(wso2healthcare/abc:0.2.3:Album|wso2healthcare/abc:0.2.3:IdNotFound)>'(BCE3931)

Can I know what is wrong with my code?

Upvotes: 0

Views: 37

Answers (1)

sahanhe
sahanhe

Reputation: 190

You can only bind the response payload (which is expected to be a subtype of anydata). The error is because you are also using IdNotFound (which is not anydata) to try and bind to it.

You can take a similar approach to the following.

Album|http:ClientError album = albumClient->/album/[id];

if album is Album {
    string title = album.title;
    io:println("Title: ", title);
    return;
}

if album !is http:ClientRequestError {
    return album;
}

// album is http:ClientRequestError here
// 4xx errors
record {
    string message;
} body = check album.detail().body.ensureType();
io:println("Message: ", body.message);

If you have a requirement to wrap all errors using a spec-defined error message structure, you can use ResponseErrorInterceptor to change the error structure like this:

type ErrorDetails record {|
    string timestamp;
    string message;
|};

// This can handle all the interceptable errors
service class ErrorInterceptor {
    *http:ResponseErrorInterceptor;

    // We can access error and the already built response wrt to the error type
    remote function interceptResponseError(error err, http:Response errResponse) returns error {
        // Changing all the errors
        // Construct the spec defined error body
        ErrorDetails errDetails = {
            timestamp: time:utcToString(time:utcNow()),
            message: err.message()
        };
        // Return a default status code error with the error body
        // Set the cause to the original error to maintain the status code
        return error httpscerr:DefaultStatusCodeError("default error", err, body = errDetails);
    }
}

service http:InterceptableService / on new http:Listener(8022) {
    public function createInterceptors() returns ErrorInterceptor {
        return new ErrorInterceptor();
    }

    ...
}

Upvotes: 0

Related Questions