Skip to content

Commit

Permalink
Merge pull request #138 from eskimor/form_struct_parameters
Browse files Browse the repository at this point in the history
Form struct parameters
  • Loading branch information
s-ludwig committed Nov 27, 2012
2 parents a74d72c + c1ba6b5 commit 1ab13dc
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 18 deletions.
15 changes: 15 additions & 0 deletions examples/form_interface/source/app.d
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import std.stdio;
import std.algorithm;
import std.string;
import vibe.http.form;

struct Address {
string street;
int door;
int zip_code;
}
/**
This example pretty well shows how registerFormInterface is meant to be used and what possibilities it offers.
The API that is exposed by DataProvider is conveniently usable by the methods in App and from JavaScript, with just
Expand Down Expand Up @@ -43,6 +49,11 @@ class DataProvider {
void addUser(string name, string surname, string address) {
users~=[name, surname, address];
}
/// Add user with structured address
/// Don't use ref Address at the moment (dmd 2.060) you'll get an ICE.
void addUser(string name, string surname, Address address) {
users~=[name, surname, address.street~" "~to!string(address.door)~"\n"~to!string(address.zip_code)];
}
private:
string[][] users=[["Tina", "Muster", "Wassergasse 12"],
["Martina", "Maier", "Broadway 6"],
Expand Down Expand Up @@ -71,6 +82,10 @@ class App {
dataProvider.addUser(name, surname, address);
res.redirect(prefix);
}
void addUser(HttpServerRequest req, HttpServerResponse res, string name, string surname, Address address) {
dataProvider.addUser(name, surname, address);
res.redirect(prefix);
}
@property string prefix() {
return prefix_;
}
Expand Down
20 changes: 20 additions & 0 deletions examples/form_interface/views/tableview.dt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,26 @@ block content
td
td
input(type='submit', value="Add")
.additemcontainer
h2 Add new user (structured address)
form(action='/addUser', method='POST')
table
- import std.string;
- foreach ( item ; ["name", "surname"] )
tr
td= item[0..1].toUpper()~item[1..$]
td
input(type="text", width="160", name="#{item}")
tr
- foreach ( item ; ["street", "door", "zip_code"] )
tr
td= item[0..1].toUpper()~item[1..$]
td
input(type="text", width="160", name='#{"address_"~item}')
tr
td
td
input(type='submit', value="Add")
script
function getTable(field, value) {
var req = new XMLHttpRequest();
Expand Down
106 changes: 88 additions & 18 deletions source/vibe/http/form.d
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,37 @@ private bool parseMultipartFormPart(InputStream stream, ref string[string] form,
parameters. All methods of I that start with "get", "query", "add", "create",
"post" are made available via the URL url_prefix~method_name. A method named
"index" will be made available via url_prefix. method_name is generated from
the original method name ba the same rules as for
the original method name by the same rules as for
vibe.http.rest.registerRestInterface. All these methods might take a
HttpServerRequest parameter and a HttpServerResponse parameter, but don't have
to.
All additional parameters will be filled with available form-data fields.
Every parameter name has to match a form field name. The registered handler
will throw an exception if no overload is found that is compatible with all
available form data fields.
Every parameter name has to match a form field name (or is a fillable
struct). The registered handler will throw an exception if no overload is
found that is compatible with all available form data fields.
If a parameter name is not found in the form data and the parameter is a
struct, all accessible fields of the struct (might also be properties) will
be searched in the form, with the parameter (struct) name prefixed. An underscore is
used as delimiter. So if you have a struct parameter with name 'foo' of type:
---
struct FooBar {
int bar;
int another_foo;
}
---
the form data must contain the keys 'foo_bar' and 'foo_another_foo'. Their
corresponding values will be applied to the structure's fields. If not all
fields of the struct are found, this is considered an error and the next
overload (if any) will be tried.
The registered handler gives really good error messages if no appropriate
overload is found, but this comes at the price of some allocations for the
error messages, which are not used at all if eventually a valid overload is
found. So because of this and because the search for an appropriate
overload is done at run time (according to the provided form data) you
might want to avoid overloads for performance critical sites.
For a thorough example of how to use this method, see the form_interface
example in the examples directory.
Expand Down Expand Up @@ -324,7 +346,7 @@ HttpServerRequestDelegate formMethodHandler(T, string method)(T inst)
if(applyParametersFromAssociativeArray!func(req, res, myoverload, error)) {
return;
}
errors~="Overload "~method~typeid(ParameterTypeTuple!func).toString()~" failed: "~error~"\n";
errors~="Overload "~method~typeid(ParameterTypeTuple!func).toString()~" failed: "~error~"\n\n";
}
enforce(false, "No method found that matches the found form data:\n"~errors);
}
Expand All @@ -344,6 +366,15 @@ HttpServerRequestDelegate formMethodHandler(T, string method)(T inst)
parameters could not be applied. Exceptions are not used in this situation,
because when traversing overloads this might be a quite common scenario.
Applying data happens as follows:
1. All parameters are traversed
2. If parameter is of type HttpServerRequest or HttpServerResponse req/res will be applied.
3. If the parameters name is found in the form, the form data has to be convertible with conv.to to the parameters type, otherwise this method returns false.
4. If the parameters name is not found in the form, but is a struct, its fields are traversed and searched in the form. The form needs to contain keys in the form: parameterName_structField.
So if you have a struct paramter foo with a field bar and a field fooBar, the form would need to contain keys: foo_bar and foo_fooBar. The struct fields maybe datafields or properties.
5. If a struct field is not found in the form or the struct has no fields that are assignable, the method returns false.
Calls: applyParametersFromAssociativeArray!(Func,Func)(req, res, func, error),
if you want to handle overloads of func, use the second version of this method
and pass the overload alias as first template parameter. (For retrieving parameter names)
Expand Down Expand Up @@ -377,29 +408,68 @@ private bool applyParametersFromAssociativeArray(alias Overload, Func)(HttpServe
ParameterTypes args;
string[string] form = req.method == HttpMethod.GET ? req.query : req.form;
int count=0;
foreach(i, item; args) {
string[] missing_parameters;
foreach(i, item; ParameterIdentifierTuple!Overload) {
static if(is(ParameterTypes[i] : HttpServerRequest)) {
args[i] = req;
}
}
else static if(is(ParameterTypes[i] : HttpServerResponse)) {
args[i] = res;
}
else {
count++;
auto found_item=item in form;
if(found_item) {
try {
args[i] = to!(typeof(args[i]))(*found_item);
count++;
}
catch(ConvException e) {
error~="Conversion of '"~item~"' failed, reason: "~e.msg~"\n";
}
}
else {
int old_count=count;
static if(is(typeof(args[i]) == struct)) {
foreach(elem; __traits(allMembers, typeof(args[i]))) {
//static if(__traits(compiles, {__traits(getMember, args[i], elem)=__traits(getMember, args[i], elem);})) // Does not compile: _args_field_4 Internal error: e2ir.c 720
//pragma(msg, "Compiles '__traits(compiles, {args[i]."~elem~"=args[i]."~elem~";})': "~to!string(mixin("__traits(compiles, {args[i]."~elem~"=args[i]."~elem~";})")));
static if(mixin("__traits(compiles, {args[i]."~elem~"=args[i]."~elem~";})")) {
string fname=item~"_"~elem;
auto found=fname in form;
if(found) {
try {
mixin("args[i]."~elem~"=to!(typeof(args[i]."~elem~"))(*found);");
count++;
//__traits(getMember, args[i], elem)=to!(typeof(__traits(getMember, args[i], elem)))(*found); // Does not compile: _args_field_4 Internal error: e2ir.c 720
}
catch(ConvException e) {
error~="Conversion of '"~fname~"' failed, reason: "~e.msg~"\n";
}
}
else
missing_parameters~=fname;
}
}
if(old_count==count) {
error~="struct parameter found, with no assignable or readable fields (make sure fields are accessible (public), assignable and readable): "~item~"\n";
}
}
else {
missing_parameters~=item;
}
}
}
}
if(missing_parameters.length) {
error~="The following parameters have not been found in the form data: "~to!string(missing_parameters)~"\n";
error~="Provied form data was: "~to!string(form.keys)~"\n";
}
if(count!=form.length) {
error="The form had "~to!string(form.length)~" element(s), but "~to!string(count)~" parameter(s) need to be supplied.";
return false;
error~="The form had "~to!string(form.length)~" element(s), of which "~to!string(count)~" element(s) were applicable.\n";
}
foreach(i, item; ParameterIdentifierTuple!Overload) {
static if(!is( typeof(args[i]) : HttpServerRequest) && !is( typeof(args[i]) : HttpServerResponse)) {
if(item !in form) {
error="Form misses parameter: "~item;
return false;
}
args[i] = to!(typeof(args[i]))(form[item]);
}
if(error) {
error="\n------\n"~error~"------";
return false;
}
func(args);
return true;
Expand Down

0 comments on commit 1ab13dc

Please sign in to comment.