Skip to content

Commit

Permalink
Add viaStatus REST interface attribute.
Browse files Browse the repository at this point in the history
Allows customizing the HTTP response status in a cleaner way than (ab)using an `after` annotation.
  • Loading branch information
s-ludwig committed Oct 3, 2024
1 parent 087499e commit ff1cb85
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 19 deletions.
15 changes: 15 additions & 0 deletions tests/rest/source/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,8 @@ interface Example6API
// Without a parameter, it will represent the entire body
string postConcatBody(@viaBody() FooType obj);

int testStatus(@viaStatus out int status, @viaStatus out string status_phrase);

struct FooType {
int a;
string s;
Expand Down Expand Up @@ -440,6 +442,13 @@ override:
{
return postConcat(obj);
}

int testStatus(out int status, out string status_phrase)
{
status = HTTPStatus.accepted;
status_phrase = "Request accepted!";
return 42;
}
}

unittest
Expand Down Expand Up @@ -644,6 +653,12 @@ void runTests(string url)
assert(tester == "The cake is a lie", tester);
assert(www == `Basic realm="Aperture"`.nullable, www.to!string);
}

int status;
string status_phrase;
assert(api.testStatus(status, status_phrase) == 42);
assert(status == HTTPStatus.accepted);
assert(status_phrase == "Request accepted!");
}

// Example 6 -- Query
Expand Down
20 changes: 11 additions & 9 deletions web/vibe/web/common.d
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import vibe.http.common;
import vibe.http.server : HTTPServerRequest;
import vibe.data.json;
import vibe.internal.meta.uda : onlyAsUda, UDATuple;
import vibe.web.internal.rest.common : ParameterKind;

import std.meta : AliasSeq;
static import std.utf;
Expand Down Expand Up @@ -686,8 +687,6 @@ package struct NoRouteAttribute {}
* and the parameter (identifier) name of the function.
*/
public struct WebParamAttribute {
import vibe.web.internal.rest.common : ParameterKind;

/// The type of the WebParamAttribute
ParameterKind origin;
/// Parameter name (function parameter name).
Expand Down Expand Up @@ -722,7 +721,6 @@ public struct WebParamAttribute {
*/
WebParamAttribute viaBody(string field = null)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.body_, null, field);
Expand All @@ -735,7 +733,6 @@ in {
}
do
{
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.body_, identifier, field);
Expand All @@ -744,7 +741,6 @@ do
/// ditto
WebParamAttribute bodyParam(string identifier)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.body_, identifier, "");
Expand Down Expand Up @@ -774,7 +770,6 @@ WebParamAttribute bodyParam(string identifier)
*/
WebParamAttribute viaHeader(string field)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.header, null, field);
Expand All @@ -783,7 +778,6 @@ WebParamAttribute viaHeader(string field)
/// Ditto
WebParamAttribute headerParam(string identifier, string field)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.header, identifier, field);
Expand Down Expand Up @@ -813,7 +807,6 @@ WebParamAttribute headerParam(string identifier, string field)
*/
WebParamAttribute viaQuery(string field)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.query, null, field);
Expand All @@ -822,12 +815,21 @@ WebParamAttribute viaQuery(string field)
/// Ditto
WebParamAttribute queryParam(string identifier, string field)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.query, identifier, field);
}


/** Declares a parameter to be transmitted via the HTTP status code or phrase.
This attribute can be applied to one or two `out` parameters of type
`HTTPStatus`/`int` or `string`. The values of those parameters correspond
to the HTTP status code or phrase, depending on the type.
*/
enum viaStatus = WebParamAttribute(ParameterKind.status);


/**
Determines the naming convention of an identifier.
*/
Expand Down
5 changes: 4 additions & 1 deletion web/vibe/web/internal/rest/common.d
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ import std.traits : hasUDA;
case ParameterKind.internal: route.internalParameters ~= pi; break;
case ParameterKind.attributed: route.attributedParameters ~= pi; break;
case ParameterKind.auth: route.authParameters ~= pi; break;
case ParameterKind.status: route.statusParameters ~= pi; break;
}
}

Expand Down Expand Up @@ -437,6 +438,7 @@ struct Route {
Parameter[] attributedParameters;
Parameter[] internalParameters;
Parameter[] authParameters;
Parameter[] statusParameters;
}

struct PathPart {
Expand Down Expand Up @@ -474,7 +476,8 @@ enum ParameterKind {
header, // req.header[]
attributed, // @before
internal, // req.params[]
auth // @authrorized!T
auth, // @authrorized!T
status // res.statusCode/res.statusPhrase
}

struct SubInterface {
Expand Down
35 changes: 26 additions & 9 deletions web/vibe/web/rest.d
Original file line number Diff line number Diff line change
Expand Up @@ -1623,13 +1623,20 @@ private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(
handleCors();
foreach (i, P; PTypes) {
static if (sroute.parameters[i].isOut) {
static assert (sroute.parameters[i].kind == ParameterKind.header);
static if (isInstanceOf!(Nullable, typeof(params[i]))) {
if (!params[i].isNull)
static if (sroute.parameters[i].kind == ParameterKind.header) {
static if (isInstanceOf!(Nullable, typeof(params[i]))) {
if (!params[i].isNull)
res.headers[route.parameters[i].fieldName] = to!string(params[i]);
} else {
res.headers[route.parameters[i].fieldName] = to!string(params[i]);
} else {
res.headers[route.parameters[i].fieldName] = to!string(params[i]);
}
}
} else static if (sroute.parameters[i].kind == ParameterKind.status) {
static if (is(typeof(params[i]) == HTTPStatus) || is(typeof(params[i]) == int))
res.statusCode = params[i];
else static if (is(typeof(params[i]) == string))
res.statusPhrase = params[i];
else static assert(false, "`@viaStatus` parameters must be of type `HTTPStats`/`int` or `string`");
} else static assert (false, "`out` parameters must be annotated with either `@viaHeader` or `@viaStatus`");
}
}
}
Expand Down Expand Up @@ -1855,6 +1862,8 @@ private auto executeClientMethod(I, size_t ridx, ARGS...)
InetHeaderMap headers;
InetHeaderMap reqhdrs;
InetHeaderMap opthdrs;
HTTPStatus status_code;
string status_phrase;

string url_prefix;

Expand Down Expand Up @@ -1930,15 +1939,21 @@ private auto executeClientMethod(I, size_t ridx, ARGS...)
foreach (i, PT; PTT) {
enum sparam = sroute.parameters[i];
auto fieldname = route.parameters[i].fieldName;
static if (sparam.kind == ParameterKind.header) {
static if (sparam.isOut) {
static if (sparam.isOut) {
static if (sparam.kind == ParameterKind.header) {
static if (isInstanceOf!(Nullable, PT)) {
ARGS[i] = to!(TemplateArgsOf!PT)(
opthdrs.get(fieldname, null));
} else {
if (auto ptr = fieldname in reqhdrs)
ARGS[i] = to!PT(*ptr);
}
} else static if (sparam.kind == ParameterKind.status) {
static if (is(PT == HTTPStatus) || is(PT == int)) {
ARGS[i] = status_code;
} else static if (is(PT == string)) {
ARGS[i] = status_phrase;
}
}
}
}
Expand All @@ -1964,6 +1979,8 @@ private auto executeClientMethod(I, size_t ridx, ARGS...)
auto ret = request(URL(intf.baseURL), request_filter, request_body_filter,
sroute.method, url, headers, query.data, body_, reqhdrs, opthdrs,
intf.settings.httpClientSettings);
status_code = cast(HTTPStatus)ret.statusCode;
status_phrase = ret.statusPhrase;

static if (is(RT == InputStream)) {
return ret.bodyReader.asInterface!InputStream;
Expand Down Expand Up @@ -2438,7 +2455,7 @@ do {
static if (Attr.length != 1) {
if (hack) return "%s: Parameter '%s' cannot be %s"
.format(FuncId, PN[i], SC & PSC.out_ ? "out" : "ref");
} else static if (Attr[0].origin != ParameterKind.header) {
} else static if (Attr[0].origin != ParameterKind.header && Attr[0].origin != ParameterKind.status) {
if (hack) return "%s: %s parameter '%s' cannot be %s"
.format(FuncId, Attr[0].origin, PN[i],
SC & PSC.out_ ? "out" : "ref");
Expand Down

0 comments on commit ff1cb85

Please sign in to comment.