mirror of https://github.com/adamdruppe/arsd.git
more dispatchermagic
This commit is contained in:
parent
933423478f
commit
30586da8bf
155
cgi.d
155
cgi.d
|
@ -8518,6 +8518,25 @@ class MissingArgumentException : Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/++
|
||||||
|
You can throw this from an api handler to indicate a 404 response. This is done by the presentExceptionAsHtml function in the presenter.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 15, 2021 (dub v10.5)
|
||||||
|
+/
|
||||||
|
class ResourceNotFoundException : Exception {
|
||||||
|
string resourceType;
|
||||||
|
string resourceId;
|
||||||
|
|
||||||
|
this(string resourceType, string resourceId, string file = __FILE__, size_t line = __LINE__, Throwable next = null) {
|
||||||
|
this.resourceType = resourceType;
|
||||||
|
this.resourceId = resourceId;
|
||||||
|
|
||||||
|
super("Resource not found: " ~ resourceType ~ " " ~ resourceId, file, line, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
This can be attached to any constructor or function called from the cgi system.
|
This can be attached to any constructor or function called from the cgi system.
|
||||||
|
|
||||||
|
@ -8831,6 +8850,74 @@ private bool hasIfCalledFromWeb(attrs...)() {
|
||||||
+/
|
+/
|
||||||
template AutomaticForm(alias customizer) { }
|
template AutomaticForm(alias customizer) { }
|
||||||
|
|
||||||
|
/++
|
||||||
|
This is meant to be returned by a function that takes a form POST submission. You
|
||||||
|
want to set the url of the new resource it created, which is set as the http
|
||||||
|
Location header for a "201 Created" result, and you can also set a separate
|
||||||
|
destination for browser users, which it sets via a "Refresh" header.
|
||||||
|
|
||||||
|
The `resourceRepresentation` should generally be the thing you just created, and
|
||||||
|
it will be the body of the http response when formatted through the presenter.
|
||||||
|
The exact thing is up to you - it could just return an id, or the whole object, or
|
||||||
|
perhaps a partial object.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
---
|
||||||
|
class Test : WebObject {
|
||||||
|
@(Cgi.RequestMethod.POST)
|
||||||
|
CreatedResource!int makeThing(string value) {
|
||||||
|
return CreatedResource!int(value.to!int, "/resources/id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 18, 2021
|
||||||
|
+/
|
||||||
|
struct CreatedResource(T) {
|
||||||
|
static if(!is(T == void))
|
||||||
|
T resourceRepresentation;
|
||||||
|
string resourceUrl;
|
||||||
|
string refreshUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/+
|
||||||
|
/++
|
||||||
|
This can be attached as a UDA to a handler to add a http Refresh header on a
|
||||||
|
successful run. (It will not be attached if the function throws an exception.)
|
||||||
|
This will refresh the browser the given number of seconds after the page loads,
|
||||||
|
to the url returned by `urlFunc`, which can be either a static function or a
|
||||||
|
member method of the current handler object.
|
||||||
|
|
||||||
|
You might use this for a POST handler that is normally used from ajax, but you
|
||||||
|
want it to degrade gracefully to a temporarily flashed message before reloading
|
||||||
|
the main page.
|
||||||
|
|
||||||
|
History:
|
||||||
|
Added December 18, 2021
|
||||||
|
+/
|
||||||
|
struct Refresh(alias urlFunc) {
|
||||||
|
int waitInSeconds;
|
||||||
|
|
||||||
|
string url() {
|
||||||
|
static if(__traits(isStaticFunction, urlFunc))
|
||||||
|
return urlFunc();
|
||||||
|
else static if(is(urlFunc : string))
|
||||||
|
return urlFunc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+/
|
||||||
|
|
||||||
|
/+
|
||||||
|
/++
|
||||||
|
Sets a filter to be run before
|
||||||
|
|
||||||
|
A before function can do validations of params and log and stop the function from running.
|
||||||
|
+/
|
||||||
|
template Before(alias b) {}
|
||||||
|
template After(alias b) {}
|
||||||
|
+/
|
||||||
|
|
||||||
/+
|
/+
|
||||||
Argument conversions: for the most part, it is to!Thing(string).
|
Argument conversions: for the most part, it is to!Thing(string).
|
||||||
|
|
||||||
|
@ -9081,6 +9168,17 @@ html", true, true);
|
||||||
cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
|
cgi.setResponseLocation(ret.to, true, getHttpCodeText(ret.code));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [CreatedResource]s send code 201 and will set the given urls, then present the given representation.
|
||||||
|
void presentSuccessfulReturn(T : CreatedResource!R, Meta, R)(Cgi cgi, T ret, Meta meta, string format) {
|
||||||
|
cgi.setResponseStatus(getHttpCodeText(201));
|
||||||
|
if(ret.resourceUrl.length)
|
||||||
|
cgi.header("Location: " ~ ret.resourceUrl);
|
||||||
|
if(ret.refreshUrl.length)
|
||||||
|
cgi.header("Refresh: 0;" ~ ret.refreshUrl);
|
||||||
|
static if(!is(R == void))
|
||||||
|
presentSuccessfulReturn(cgi, ret.resourceRepresentation, meta, format);
|
||||||
|
}
|
||||||
|
|
||||||
/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
|
/// Multiple responses deconstruct the algebraic type and forward to the appropriate handler at runtime
|
||||||
void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
|
void presentSuccessfulReturn(T : MultipleResponses!Types, Meta, Types...)(Cgi cgi, T ret, Meta meta, string format) {
|
||||||
bool outputted = false;
|
bool outputted = false;
|
||||||
|
@ -9119,11 +9217,27 @@ html", true, true);
|
||||||
useful forms or richer error messages for the user.
|
useful forms or richer error messages for the user.
|
||||||
+/
|
+/
|
||||||
void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg) {
|
void presentExceptionAsHtml(alias func, T)(Cgi cgi, Throwable t, T dg) {
|
||||||
presentExceptionAsHtmlImpl(cgi, t, createAutomaticFormForFunction!(func)(dg));
|
Form af;
|
||||||
|
foreach(attr; __traits(getAttributes, func)) {
|
||||||
|
static if(__traits(isSame, attr, AutomaticForm)) {
|
||||||
|
af = createAutomaticFormForFunction!(func)(dg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
presentExceptionAsHtmlImpl(cgi, t, af);
|
||||||
}
|
}
|
||||||
|
|
||||||
void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
|
void presentExceptionAsHtmlImpl(Cgi cgi, Throwable t, Form automaticForm) {
|
||||||
if(auto mae = cast(MissingArgumentException) t) {
|
if(auto e = cast(ResourceNotFoundException) t) {
|
||||||
|
auto container = this.htmlContainer();
|
||||||
|
|
||||||
|
container.addChild("p", e.msg);
|
||||||
|
|
||||||
|
if(!cgi.outputtedResponseData)
|
||||||
|
cgi.setResponseStatus("404 Not Found");
|
||||||
|
cgi.write(container.parentDocument.toString(), true);
|
||||||
|
} else if(auto mae = cast(MissingArgumentException) t) {
|
||||||
|
if(automaticForm is null)
|
||||||
|
goto generic;
|
||||||
auto container = this.htmlContainer();
|
auto container = this.htmlContainer();
|
||||||
if(cgi.requestMethod == Cgi.RequestMethod.POST)
|
if(cgi.requestMethod == Cgi.RequestMethod.POST)
|
||||||
container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
|
container.appendChild(Element.make("p", "Argument `" ~ mae.argumentName ~ "` of type `" ~ mae.argumentType ~ "` is missing"));
|
||||||
|
@ -9131,6 +9245,7 @@ html", true, true);
|
||||||
|
|
||||||
cgi.write(container.parentDocument.toString(), true);
|
cgi.write(container.parentDocument.toString(), true);
|
||||||
} else {
|
} else {
|
||||||
|
generic:
|
||||||
auto container = this.htmlContainer();
|
auto container = this.htmlContainer();
|
||||||
|
|
||||||
// import std.stdio; writeln(t.toString());
|
// import std.stdio; writeln(t.toString());
|
||||||
|
@ -9192,7 +9307,7 @@ html", true, true);
|
||||||
/++
|
/++
|
||||||
Returns an element for a particular type
|
Returns an element for a particular type
|
||||||
+/
|
+/
|
||||||
Element elementFor(T)(string displayName, string name) {
|
Element elementFor(T)(string displayName, string name, Element function() udaSuggestion) {
|
||||||
import std.traits;
|
import std.traits;
|
||||||
|
|
||||||
auto div = Element.make("div");
|
auto div = Element.make("div");
|
||||||
|
@ -9218,7 +9333,7 @@ html", true, true);
|
||||||
fieldset.addChild("input", name);
|
fieldset.addChild("input", name);
|
||||||
foreach(idx, memberName; __traits(allMembers, T))
|
foreach(idx, memberName; __traits(allMembers, T))
|
||||||
static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
|
static if(__traits(compiles, __traits(getMember, T, memberName).offsetof)) {
|
||||||
fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName));
|
fieldset.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(beautify(memberName), name ~ "." ~ memberName, null /* FIXME: pull off the UDA */));
|
||||||
}
|
}
|
||||||
} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
|
} else static if(isSomeString!T || isIntegral!T || isFloatingPoint!T) {
|
||||||
Element lbl;
|
Element lbl;
|
||||||
|
@ -9229,13 +9344,22 @@ html", true, true);
|
||||||
} else {
|
} else {
|
||||||
lbl = div;
|
lbl = div;
|
||||||
}
|
}
|
||||||
auto i = lbl.addChild("input", name);
|
Element i;
|
||||||
|
if(udaSuggestion) {
|
||||||
|
i = udaSuggestion();
|
||||||
|
lbl.appendChild(i);
|
||||||
|
} else {
|
||||||
|
i = lbl.addChild("input", name);
|
||||||
|
}
|
||||||
i.attrs.name = name;
|
i.attrs.name = name;
|
||||||
static if(isSomeString!T)
|
static if(isSomeString!T)
|
||||||
i.attrs.type = "text";
|
i.attrs.type = "text";
|
||||||
else
|
else
|
||||||
i.attrs.type = "number";
|
i.attrs.type = "number";
|
||||||
i.attrs.value = to!string(T.init);
|
if(i.tagName == "textarea")
|
||||||
|
i.textContent = to!string(T.init);
|
||||||
|
else
|
||||||
|
i.attrs.value = to!string(T.init);
|
||||||
} else static if(is(T == bool)) {
|
} else static if(is(T == bool)) {
|
||||||
Element lbl;
|
Element lbl;
|
||||||
if(displayName !is null) {
|
if(displayName !is null) {
|
||||||
|
@ -9263,7 +9387,7 @@ html", true, true);
|
||||||
i.attrs.type = "file";
|
i.attrs.type = "file";
|
||||||
} else static if(is(T == K[], K)) {
|
} else static if(is(T == K[], K)) {
|
||||||
auto templ = div.addChild("template");
|
auto templ = div.addChild("template");
|
||||||
templ.appendChild(elementFor!(K)(null, name));
|
templ.appendChild(elementFor!(K)(null, name, null /* uda??*/));
|
||||||
if(displayName !is null)
|
if(displayName !is null)
|
||||||
div.addChild("span", displayName, "label-text");
|
div.addChild("span", displayName, "label-text");
|
||||||
auto btn = div.addChild("button");
|
auto btn = div.addChild("button");
|
||||||
|
@ -9312,10 +9436,15 @@ html", true, true);
|
||||||
|
|
||||||
static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
|
static if(!mustNotBeSetFromWebParams!(param[0], __traits(getAttributes, param))) {
|
||||||
string displayName = beautify(__traits(identifier, param));
|
string displayName = beautify(__traits(identifier, param));
|
||||||
foreach(attr; __traits(getAttributes, param))
|
Element function() element;
|
||||||
|
foreach(attr; __traits(getAttributes, param)) {
|
||||||
static if(is(typeof(attr) == DisplayName))
|
static if(is(typeof(attr) == DisplayName))
|
||||||
displayName = attr.name;
|
displayName = attr.name;
|
||||||
auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param)));
|
else static if(is(typeof(attr) : typeof(element))) {
|
||||||
|
element = attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto i = form.appendChild(elementFor!(param)(displayName, __traits(identifier, param), element));
|
||||||
if(i.querySelector("input[type=file]") !is null)
|
if(i.querySelector("input[type=file]") !is null)
|
||||||
form.setAttribute("enctype", "multipart/form-data");
|
form.setAttribute("enctype", "multipart/form-data");
|
||||||
}
|
}
|
||||||
|
@ -9343,10 +9472,13 @@ html", true, true);
|
||||||
foreach(idx, memberName; __traits(derivedMembers, T)) {{
|
foreach(idx, memberName; __traits(derivedMembers, T)) {{
|
||||||
static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
|
static if(__traits(compiles, __traits(getMember, obj, memberName).offsetof)) {
|
||||||
string displayName = beautify(memberName);
|
string displayName = beautify(memberName);
|
||||||
|
Element function() element;
|
||||||
foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName)))
|
foreach(attr; __traits(getAttributes, __traits(getMember, T, memberName)))
|
||||||
static if(is(typeof(attr) == DisplayName))
|
static if(is(typeof(attr) == DisplayName))
|
||||||
displayName = attr.name;
|
displayName = attr.name;
|
||||||
form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName));
|
else static if(is(typeof(attr) : typeof(element)))
|
||||||
|
element = attr;
|
||||||
|
form.appendChild(elementFor!(typeof(__traits(getMember, T, memberName)))(displayName, memberName, element));
|
||||||
|
|
||||||
form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
|
form.setValue(memberName, to!string(__traits(getMember, obj, memberName)));
|
||||||
}}}
|
}}}
|
||||||
|
@ -9552,6 +9684,7 @@ struct MultipleResponses(T...) {
|
||||||
+/
|
+/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: implement this somewhere maybe
|
||||||
struct RawResponse {
|
struct RawResponse {
|
||||||
int code;
|
int code;
|
||||||
string[] headers;
|
string[] headers;
|
||||||
|
@ -9567,7 +9700,7 @@ struct RawResponse {
|
||||||
+/
|
+/
|
||||||
struct Redirection {
|
struct Redirection {
|
||||||
string to; /// The URL to redirect to.
|
string to; /// The URL to redirect to.
|
||||||
int code = 303; /// The HTTP code to retrn.
|
int code = 303; /// The HTTP code to return.
|
||||||
}
|
}
|
||||||
|
|
||||||
/++
|
/++
|
||||||
|
|
Loading…
Reference in New Issue