Search code examples
dvibed

How can I return JSON with HTTP code in Vibe.d?


I would like to return not only JSON, but also the HTTP response code.

I am registering REST interface by URLRouter:

router.registerRestInterface(new ClientServerAPI);

Example of my REST implementation:

module clienserverapi.clientserver;

import api.clientserver;
import models.replies.client_versions;

/**
    Implementation of Client-Server API.
*/
class ClientServerAPI : IClientServerAPI {
@safe:
    ClientVersions getSupportedClientVersions() {
        bool[string] unstableFeatures;
        return ClientVersions(supportedVersions.dup, unstableFeatures);
    }
}

Solution

  • In the REST interface generator the response codes are handled automatically and as you can't pass in a HTTPServerResponse/HTTPServerRequest argument into your REST methods, you can't control what status gets returned.

    However there are some built-in statuses which get handled:

    • 200/204 are returned based on content
    • 400 Bad request for mismatched arguments
    • 404 Not found for unmatched routes
    • 500 internal server error is returned on most exceptions
    • (outside of debug mode) unauthorized / bad request / forbidden are sent

    See also: REST interface documentation

    and you can control any status code using HTTPStatusException, however it is treated as error and will result in a predefined error json which has statusMessage as exception message set and returns the HTTP status code you pass to it. (this is probably what you want for error handling)

    You can also change what the errors look like by setting the errorHandler to a RestErrorHandler delegate in your RestInterfaceSettings.

    Alternatively, depending on what you want to do, you can use a WebInterface which is a lot like a rest interface, but doesn't have some convenience functions REST interfaces do, but instead can fully access the request/response arguments and can basically do anything like a normal http route and has some other convenience functions you can use.

    In theory you could abuse the errorHandler + HTTPStatusException with valid HTTP status codes if you want to return custom success codes with your data, but I would discourage that and instead go with web interfaces if that's what you are after.

    However if all you want to do is having custom error codes with a custom, but consistent, error page then I would definitely go with REST interface with an errorHandler.

    Your could could now look like this:

    import vibe.vibe;
    import std.uni;
    
    @safe:
    
    void main() {
        auto server = new HTTPServerSettings;
        server.port = 3000;
        server.bindAddresses = ["::1", "127.0.0.1"];
        auto router = new URLRouter;
    
        RestInterfaceSettings settings = new RestInterfaceSettings();
        // this is how the error page will look on any thrown exception (like HTTPStatusException)
        settings.errorHandler = (HTTPServerRequest req, HTTPServerResponse res,
                RestErrorInformation error) @safe {
            res.writeJsonBody([
                // design this however you like
                "ok": Json(false),
                "error": serializeToJson([
                    "status": Json(cast(int)error.statusCode),
                    "message": Json(error.exception.msg),
                    "parent": Json("/api/something")
                ])
            ]);
        };
        router.registerRestInterface(new Impl, settings);
    
        listenHTTP(server, router);
        runApplication();
    }
    
    interface RestAPI {
        string getGreeting(string name);
    }
    
    class Impl : RestAPI {
        string getGreeting(string name)
        {
            // throw an HTTP Bad Request error when name is empty
            if (name.length == 0)
                throw new HTTPStatusException(HTTPStatus.badRequest, "Name parameter cannot be empty!");
            // throw an HTTP Conflict error code when name is Bob
            if (sicmp(name, "bob") == 0)
                throw new HTTPStatusException(HTTPStatus.conflict, "Server cannot greet Bob!");
            return "Hello, " ~ name ~ "!";
        }
    }
    

    and your server will then respond something like:

    {
        "ok": false,
        "error": {
            "message": "Server cannot greet Bob!",
            "status": 409,
            "parent": "/api/something"
        }
    }