Skip to content

Commit

Permalink
Fix vibe-d#1549 - Support bodyParam attribute without form parameter …
Browse files Browse the repository at this point in the history
…name to map the whole body to a single parameter
  • Loading branch information
wilzbach committed Feb 6, 2017
1 parent b88b313 commit a765038
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 20 deletions.
20 changes: 20 additions & 0 deletions examples/rest/source/app.d
Expand Up @@ -352,6 +352,9 @@ unittest
* This is to be consistent with the way D 'out' and 'ref' works.
* However, it makes no sense to have 'ref' or 'out' parameters on
* body or query parameter, so those are treated as error at compile time.
*
* To serialize the entire Json body into one parameter, simply name the
* parameter "foo"
*/
@rootPathFromName
interface Example6API
Expand All @@ -374,6 +377,13 @@ interface Example6API
@bodyParam("myFoo", "parameter")
string postConcat(FooType myFoo);

// If no field name is passed to @bodyParam the entire json object is
// serialized into the parameter.
// Moreover if only one bodyParameter is present, this is the default
// behavior.
@bodyParam("obj")
string postConcatBody(FooType obj);

struct FooType {
int a;
string s;
Expand Down Expand Up @@ -414,6 +424,11 @@ override:
import std.conv : to;
return to!string(myFoo.a)~myFoo.s~to!string(myFoo.d);
}

string postConcatBody(FooType obj)
{
return postConcat(obj);
}
}

unittest
Expand Down Expand Up @@ -558,6 +573,11 @@ shared static this()
auto api = new RestInterfaceClient!Example6API("http://127.0.0.1:8080");
auto answer = api.postAnswer("IDK");
assert(answer == "False");

Example6API.FooType fType = {a: 1, s: "str", d: 3.14};
auto expected = "1str3.14";
assert(api.postConcat(fType) == expected);
assert(api.postConcatBody(fType) == expected);
}

// Example 7 -- Custom JSON response
Expand Down
83 changes: 69 additions & 14 deletions tests/rest/source/app.d
Expand Up @@ -373,6 +373,13 @@ interface Example6API
@bodyParam("myFoo", "parameter")
string postConcat(FooType myFoo);

// expects the entire body
@bodyParam("obj")
string postConcatBody(FooType obj);

// expects the entire body (by default)
string postConcatBody2(FooType obj);

struct FooType {
int a;
string s;
Expand Down Expand Up @@ -413,6 +420,16 @@ override:
import std.conv : to;
return to!string(myFoo.a)~myFoo.s~to!string(myFoo.d);
}

string postConcatBody(FooType obj)
{
return postConcat(obj);
}

string postConcatBody2(FooType obj)
{
return postConcat(obj);
}
}

unittest
Expand Down Expand Up @@ -544,21 +561,59 @@ void runTests()
enum expected = "42fortySomething51.42"; // to!string(51.42) doesn't work at CT

auto api = new RestInterfaceClient!Example6API("http://127.0.0.1:8080");
// First we make sure parameters are transmitted via query.
auto res = requestHTTP("http://127.0.0.1:8080/example6_api/concat",
(scope r) {
import vibe.data.json;
r.method = HTTPMethod.POST;
Json obj = Json.emptyObject;
obj["parameter"] = serializeToJson(Example6API.FooType(42, "fortySomething", 51.42));
r.writeJsonBody(obj);
});
{
// First we make sure parameters are transmitted via query.
auto res = requestHTTP("http://127.0.0.1:8080/example6_api/concat",
(scope r) {
import vibe.data.json;
r.method = HTTPMethod.POST;
Json obj = Json.emptyObject;
obj["parameter"] = serializeToJson(Example6API.FooType(42, "fortySomething", 51.42));
r.writeJsonBody(obj);
});

assert(res.statusCode == 200);
assert(res.bodyReader.readAllUTF8() == `"`~expected~`"`);
// Then we check that both can communicate together.
auto answer = api.postConcat(Example6API.FooType(42, "fortySomething", 51.42));
assert(answer == expected);
}

assert(res.statusCode == 200);
assert(res.bodyReader.readAllUTF8() == `"`~expected~`"`);
// Then we check that both can communicate together.
auto answer = api.postConcat(Example6API.FooType(42, "fortySomething", 51.42));
assert(answer == expected);
// suppling the whole body
{
// First we make sure parameters are transmitted via query.
auto res = requestHTTP("http://127.0.0.1:8080/example6_api/concat_body",
(scope r) {
import vibe.data.json;
r.method = HTTPMethod.POST;
Json obj = serializeToJson(Example6API.FooType(42, "fortySomething", 51.42));
r.writeJsonBody(obj);
});

assert(res.statusCode == 200);
assert(res.bodyReader.readAllUTF8() == `"`~expected~`"`);
// Then we check that both can communicate together.
auto answer = api.postConcatBody(Example6API.FooType(42, "fortySomething", 51.42));
assert(answer == expected);
}

// suppling the whole body (default parameter)
{
// First we make sure parameters are transmitted via query.
auto res = requestHTTP("http://127.0.0.1:8080/example6_api/concat_body2",
(scope r) {
import vibe.data.json;
r.method = HTTPMethod.POST;
Json obj = serializeToJson(Example6API.FooType(42, "fortySomething", 51.42));
r.writeJsonBody(obj);
});

assert(res.statusCode == 200);
assert(res.bodyReader.readAllUTF8() == `"`~expected~`"`);
// Then we check that both can communicate together.
auto answer = api.postConcatBody2(Example6API.FooType(42, "fortySomething", 51.42));
assert(answer == expected);
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions web/vibe/web/common.d
Expand Up @@ -463,11 +463,14 @@ package struct WebParamAttribute {
string field;
}

package(vibe.web) enum bodyParamWholeName = "bodyParamWhole";

/**
* Declare that a parameter will be transmitted to the API through the body.
*
* It will be serialized as part of a JSON object.
* The serialization format is currently not customizable.
* If no fieldname is given, the entire body is serialized into the object.
*
* Params:
* - identifier: The name of the parameter to customize. A compiler error will be issued on mismatch.
Expand All @@ -488,6 +491,15 @@ WebParamAttribute bodyParam(string identifier, string field)
return WebParamAttribute(ParameterKind.body_, identifier, field);
}

/// ditto
WebParamAttribute bodyParam(string identifier)
@safe {
import vibe.web.internal.rest.common : ParameterKind;
if (!__ctfe)
assert(false, onlyAsUda!__FUNCTION__);
return WebParamAttribute(ParameterKind.body_, identifier, bodyParamWholeName);
}

/**
* Declare that a parameter will be transmitted to the API through the headers.
*
Expand Down
36 changes: 30 additions & 6 deletions web/vibe/web/rest.d
Expand Up @@ -1038,6 +1038,15 @@ private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(

PTypes params;

enum bool hasSingleBodyParam = {
int i;
foreach (j, PT; PTypes)
if (sroute.parameters[j].kind == ParameterKind.body_)
i++;

return i == 1;
}();

foreach (i, PT; PTypes) {
enum sparam = sroute.parameters[i];
enum pname = sparam.name;
Expand All @@ -1051,12 +1060,14 @@ private HTTPServerRequestDelegate jsonMethodHandler(alias Func, size_t ridx, T)(
if (auto pv = fieldname in req.query)
v = fromRestString!PT(*pv);
} else static if (sparam.kind == ParameterKind.body_) {
if (auto pv = fieldname in req.json) {
try
try {
// for @bodyParam("s") and by default the entire body should be serialized
if (sparam.fieldName == bodyParamWholeName || hasSingleBodyParam && sparam.fieldName.length == 0)
v = deserializeJson!PT(req.json);
else if (auto pv = fieldname in req.json)
v = deserializeJson!PT(*pv);
catch (JSONException e)
enforceBadRequest(false, e.msg);
}
} catch (JSONException e)
enforceBadRequest(false, e.msg);
} else static if (sparam.kind == ParameterKind.header) {
if (auto pv = fieldname in req.headers)
v = fromRestString!PT(*pv);
Expand Down Expand Up @@ -1317,6 +1328,15 @@ private auto executeClientMethod(I, size_t ridx, ARGS...)
auto jsonBody = Json.emptyObject;
string body_;

enum bool hasSingleBodyParam = {
int i;
foreach (j, PT; PTT)
if (sroute.parameters[j].kind == ParameterKind.body_)
i++;

return i == 1;
}();

void addQueryParam(size_t i)(string name)
{
if (query.data.length) query.put('&');
Expand All @@ -1334,7 +1354,11 @@ private auto executeClientMethod(I, size_t ridx, ARGS...)
static if (sparam.kind == ParameterKind.query) {
addQueryParam!i(fieldname);
} else static if (sparam.kind == ParameterKind.body_) {
jsonBody[fieldname] = serializeToJson(ARGS[i]);
// for @bodyParam("s") and by default the entire body should be serialized
if (sparam.fieldName == bodyParamWholeName || hasSingleBodyParam && sparam.fieldName.length == 0)
jsonBody = serializeToJson(ARGS[i]);
else
jsonBody[fieldname] = serializeToJson(ARGS[i]);
} else static if (sparam.kind == ParameterKind.header) {
// Don't send 'out' parameter, as they should be default init anyway and it might confuse some server
static if (sparam.isIn) {
Expand Down

0 comments on commit a765038

Please sign in to comment.