Skip to content

Commit

Permalink
add @SuccessCode UDA to return other 2XX codes
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinNowak committed Dec 30, 2017
1 parent 5766e51 commit 1939c47
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 1 deletion.
35 changes: 35 additions & 0 deletions tests/rest/source/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,25 @@ class Example7 : Example7API {
}
}

@rootPathFromName
@safe interface Example8API {
// POST /entry1/
// returns 201 Created
@successCode(HTTPStatus.created)
void createEntry1();
// POST /entry2/
void createEntry2();
// POST /entry3/
@successCode(HTTPStatus.created)
int createEntry3();
}

class Example8 : Example8API {
void createEntry1() {}
@successCode(HTTPStatus.created)
void createEntry2() {}
int createEntry3() { return 0; }
}

void runTests()
{
Expand Down Expand Up @@ -589,6 +608,21 @@ void runTests()
assert(answer == expected);
}
}

// Example 8
{
import vibe.http.client : requestHTTP;

foreach (path; ["entry1", "entry2", "entry3"]) {
auto res = requestHTTP("http://127.0.0.1:8080/example8_api/" ~ path,
(scope r) {
r.method = HTTPMethod.POST;
});

assert(res.statusCode == HTTPStatus.created);
res.dropBody;
}
}
}

shared static this()
Expand All @@ -604,6 +638,7 @@ shared static this()
registerRestInterface(routes, new Example5());
registerRestInterface(routes, new Example6());
registerRestInterface(routes, new Example7());
registerRestInterface(routes, new Example8());

auto settings = new HTTPServerSettings();
settings.port = 8080;
Expand Down
9 changes: 9 additions & 0 deletions tests/vibe.web.web/source/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,20 @@ shared static this()
}
test("/foo", HTTPStatus.notFound);
test("/bar", HTTPStatus.ok);
test("/status1", HTTPStatus.created);
test("/status2", HTTPStatus.noContent);
test("/status3", HTTPStatus.internalServerError);
logInfo("All web tests succeeded.");
});
}

class Service {
@noRoute void getFoo(HTTPServerResponse res) { res.writeBody("oops"); }
void getBar(HTTPServerResponse res) { res.writeBody("ok"); }
@successCode(HTTPStatus.created)
void getStatus1(HTTPServerResponse res) { res.writeVoidBody(); }
@successCode(HTTPStatus.created)
void getStatus2(HTTPServerResponse res) { status = HTTPStatus.noContent; res.writeVoidBody(); }
@successCode(HTTPStatus.created)
void getStatus3(HTTPServerResponse res) { throw new Exception(""); }
}
40 changes: 40 additions & 0 deletions web/vibe/web/common.d
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,39 @@ ContentTypeAttribute contentType(string data)
}


/**
Attribute to set the 2XX HTTP status code returned when the method succeeds.
*/
SuccessCodeAttribute successCode(HTTPStatus code)
@safe {
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
assert(isSuccessCode(code));
return SuccessCodeAttribute(code);
}

///
unittest {
interface IAPI
{
// Will return "201 Created" instead of default "200 OK"
@successCode(HTTPStatus.created) void createEntry();
}
}

/// Get any applied successCode value or return `default_`
HTTPStatus extractSuccessCode(alias Func)(HTTPStatus default_ = HTTPStatus.ok)
{
import std.meta : Filter;
import vibe.internal.meta.traits : RecursiveFunctionAttributes;
enum pred(alias E) = is(typeof(E) == SuccessCodeAttribute);
alias UDAs = Filter!(pred, RecursiveFunctionAttributes!(Func));
static if (UDAs.length)
return UDAs[0];
else
return default_;
}

/**
Attribute to force a specific HTTP method for an interface method.
Expand Down Expand Up @@ -472,6 +505,13 @@ package struct MethodAttribute
alias data this;
}

/// private
package struct SuccessCodeAttribute
{
HTTPStatus data;
alias data this;
}

/// private
package struct PathAttribute
{
Expand Down
4 changes: 3 additions & 1 deletion web/vibe/web/rest.d
Original file line number Diff line number Diff line change
Expand Up @@ -1398,15 +1398,17 @@ private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(
static if (is(RT == void)) {
() @trusted { __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period
returnHeaders();
res.statusCode = extractSuccessCode!CFunc(cast(HTTPStatus) res.statusCode);
res.writeBody(cast(ubyte[])null);
} else {
auto ret = () @trusted { return __traits(getMember, inst, Method)(params); } (); // TODO: remove after deprecation period

static if (!__traits(compiles, () @safe { evaluateOutputModifiers!Func(ret, req, res); } ()))
static if (!__traits(compiles, () @safe { evaluateOutputModifiers!CFunc(ret, req, res); } ()))
pragma(msg, "Non-@safe @after evaluators are deprecated - annotate @after evaluator function for "~T.stringof~"."~Method~" as @safe.");

ret = () @trusted { return evaluateOutputModifiers!CFunc(ret, req, res); } ();
returnHeaders();
res.statusCode = extractSuccessCode!CFunc(cast(HTTPStatus) res.statusCode);
debug res.writePrettyJsonBody(ret);
else res.writeJsonBody(ret);
}
Expand Down
3 changes: 3 additions & 0 deletions web/vibe/web/web.d
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,9 @@ private void handleRequest(string M, alias overload, C, ERROR...)(HTTPServerRequ
static if (hasAuth)
handleAuthorization!(C, overload, params)(auth_info);

// set statusCode before calling method so it can be altered
res.statusCode = extractSuccessCode!overload(cast(HTTPStatus) res.statusCode);

// execute the method and write the result
try {
import vibe.internal.meta.funcattr;
Expand Down

0 comments on commit 1939c47

Please sign in to comment.