Skip to content

Commit

Permalink
Add callables
Browse files Browse the repository at this point in the history
  • Loading branch information
Kripth committed Jan 11, 2019
1 parent ebfc1d8 commit c2f93ad
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 40 deletions.
7 changes: 0 additions & 7 deletions examples/crud/.gitignore

This file was deleted.

4 changes: 4 additions & 0 deletions examples/javascript-to-d/dub.sdl
@@ -0,0 +1,4 @@
name "scorpion-javascript-to-d"
targetType "library"
dependency "scorpion" path="../.."
dependency "scorpion-boot" version="~master"
16 changes: 16 additions & 0 deletions examples/javascript-to-d/src/app.d
@@ -0,0 +1,16 @@
module app;

import scorpion;

@Controller
class JavascriptToDController {

@Get
getIndex(Response response) {
response.body_ = "<script src='/assets/scorpion.js'></script>";
}

@Callable
void firstFunction() {}

}
7 changes: 0 additions & 7 deletions examples/pastebin/.gitignore

This file was deleted.

64 changes: 49 additions & 15 deletions src/scorpion/bootstrap.d
Expand Up @@ -5,7 +5,10 @@ import std.exception : enforce;
import std.experimental.logger : sharedLog, LogLevel, info; import std.experimental.logger : sharedLog, LogLevel, info;
import std.regex : Regex; import std.regex : Regex;
import std.string : split, join; import std.string : split, join;
import std.traits : hasUDA, getUDAs, isFunction, Parameters, ParameterIdentifierTuple; import std.traits : hasUDA, getUDAs, isFunction, Parameters, ParameterIdentifierTuple, ReturnType;
import std.typecons : Tuple;

import asdf : serializeToJson;


import lighttp.resource : Resource; import lighttp.resource : Resource;
import lighttp.router : Router, routeInfo; import lighttp.router : Router, routeInfo;
Expand All @@ -15,7 +18,7 @@ import lighttp.util : StatusCodes, ServerRequest, ServerResponse;
import scorpion.component : Component, Init, Value; import scorpion.component : Component, Init, Value;
import scorpion.config : Config, Configuration, LanguageConfiguration, ProfilesConfiguration; import scorpion.config : Config, Configuration, LanguageConfiguration, ProfilesConfiguration;
import scorpion.context : Context; import scorpion.context : Context;
import scorpion.controller : Controller, Route, Path, Param, Body; import scorpion.controller : Controller, Route, Callable, Path, Param, Body;
import scorpion.entity : Entity, ExtendEntity; import scorpion.entity : Entity, ExtendEntity;
import scorpion.lang : LanguageManager; import scorpion.lang : LanguageManager;
import scorpion.profile : Profile; import scorpion.profile : Profile;
Expand Down Expand Up @@ -59,12 +62,6 @@ final class ScorpionServer {
*/ */
private ComponentInfo[] components; private ComponentInfo[] components;


/**
* Contains informations about services and instructions on how
* to build a new one.
*/
private ServiceInfo[] services;

/** /**
* Contains informations about controllers and a function to initialize * Contains informations about controllers and a function to initialize
* routes. * routes.
Expand Down Expand Up @@ -287,16 +284,16 @@ final class ScorpionServer {
override void init(Router router, Context context, Database database) { override void init(Router router, Context context, Database database) {
T controller = new T(); T controller = new T();
static if(!__traits(compiles, getUDAs!(T, Controller)[0]())) enum controllerPath = getUDAs!(T, Controller)[0].path; static if(!__traits(compiles, getUDAs!(T, Controller)[0]())) enum controllerPath = getUDAs!(T, Controller)[0].path;
foreach(immutable member ; __traits(allMembers, T)) { foreach(i, immutable member; __traits(allMembers, T)) {
static if(__traits(getProtection, __traits(getMember, T, member)) == "public") { static if(__traits(getProtection, __traits(getMember, T, member)) == "public") {
immutable full = "controller." ~ member; immutable full = "controller." ~ member;
alias F = __traits(getMember, T, member); alias F = __traits(getMember, T, member);
enum tests = { enum tests = {
string[] ret; string[] ret;
foreach(i, immutable uda; __traits(getAttributes, F)) { foreach(j, immutable uda; __traits(getAttributes, F)) {
static if(is(typeof(__traits(getMember, uda, "test")) == function)) { static if(is(typeof(__traits(getMember, uda, "test")) == function)) {
static if(__traits(compiles, uda())) ret ~= "__traits(getAttributes, F)[" ~ i.to!string ~ "].init"; static if(__traits(compiles, uda())) ret ~= "__traits(getAttributes, F)[" ~ j.to!string ~ "].init";
else ret ~= "__traits(getAttributes, F)[" ~ i.to!string ~ "]"; else ret ~= "__traits(getAttributes, F)[" ~ j.to!string ~ "]";
} }
} }
return ret; return ret;
Expand All @@ -311,7 +308,7 @@ final class ScorpionServer {
static if(isFunction!F) { static if(isFunction!F) {
auto fun = mixin(generateFunction!F(T.stringof, member, regexPath, tests)); auto fun = mixin(generateFunction!F(T.stringof, member, regexPath, tests));
} else { } else {
static assert(is(typeof(F) : Resource), "Member annotated with @Route must be callable or an instance of Resource"); static assert(is(typeof(F) : Resource), "Members annotated with @Route must be callable or an instance of Resource");
auto fun = delegate(ServerRequest request, ServerResponse response){ auto fun = delegate(ServerRequest request, ServerResponse response){
context.refresh(request, response); context.refresh(request, response);
static foreach(test ; tests) { static foreach(test ; tests) {
Expand All @@ -324,6 +321,34 @@ final class ScorpionServer {
} }
router.add(routeInfo(uda.method, uda.hasBody, regexPath), fun); router.add(routeInfo(uda.method, uda.hasBody, regexPath), fun);
info("Routing ", uda.method, " /", path.join("/"), " to ", T.stringof, ".", member, (isFunction!F ? "()" : "")); info("Routing ", uda.method, " /", path.join("/"), " to ", T.stringof, ".", member, (isFunction!F ? "()" : ""));
} else static if(is(typeof(uda) == Callable) || is(typeof(uda()) == Callable)) {
static if(is(typeof(uda) == Callable)) enum path = ["internal", "function", uda.functionName];
else enum path = ["internal", "function", member];
mixin(generateStruct!(F, i));
auto fun = delegate(ServerRequest request, ServerResponse response){
context.refresh(request, response);
static foreach(test ; tests) {
if(!mixin(test).test(context)) return;
}
static if(Parameters!F.length) {
Validation validation = new Validation();
X members = validateBody!X(request, response, validation);
if(validation.valid) {
Parameters!F args;
foreach(j, immutable member; __traits(allMembers, X)) {
mixin("args[" ~ j.to!string ~ "]") = mixin("members." ~ member);
}
response.contentType = "application/json";
static if(is(ReturnType!F == void)) mixin(full)(args);
else response.body_ = serializeToJson(mixin(full)(args));
}
} else {
response.contentType = "application/json";
static if(is(ReturnType!F == void)) mixin(full)();
else response.body_ = serializeToJson(mixin(full)());
}
};
router.add(routeInfo("CALL", true, path.join(`\/`)), fun);
} }
} }
static if(hasUDA!(F, Init)) { static if(hasUDA!(F, Init)) {
Expand All @@ -340,6 +365,16 @@ final class ScorpionServer {


} }


private string generateStruct(alias F, size_t index)() {
string ret;
static foreach(i ; 0..Parameters!F.length) {
ret ~= "Parameters!F[" ~ i.to!string ~ "] ";
ret ~= ParameterIdentifierTuple!F[i] ~ ';';
}
return "struct X" ~ index.to!string ~ "{" ~ ret ~ "}alias X=X" ~ index.to!string ~ ";";

}

private string generateFunction(alias M)(string controller, string member, string path, string[] tests) { private string generateFunction(alias M)(string controller, string member, string path, string[] tests) {
string[] ret = ["ServerRequest request", "ServerResponse response"]; string[] ret = ["ServerRequest request", "ServerResponse response"];
string body1 = "context.refresh(request,response);response.headers[`X-Scorpion-Controller`]=`" ~ controller ~ "." ~ member ~ "`;response.headers[`X-Scorpion-Path`]=`" ~ path ~ "`;"; string body1 = "context.refresh(request,response);response.headers[`X-Scorpion-Controller`]=`" ~ controller ~ "." ~ member ~ "`;response.headers[`X-Scorpion-Path`]=`" ~ path ~ "`;";
Expand All @@ -357,8 +392,7 @@ private string generateFunction(alias M)(string controller, string member, strin
body2 ~= "View view=View(request,response,languageManager);"; body2 ~= "View view=View(request,response,languageManager);";
call[i] = "view"; call[i] = "view";
} else static if(is(param == Session)) { } else static if(is(param == Session)) {
body2 ~= "Session session=Session.get(request);"; call[i] = "context.session";
call[i] = "session";
} else static if(is(param == Validation)) { } else static if(is(param == Validation)) {
call[i] = "validation"; call[i] = "validation";
validation = true; validation = true;
Expand Down
74 changes: 70 additions & 4 deletions src/scorpion/controller.d
Expand Up @@ -5,7 +5,8 @@ import lighttp.util : Status, ServerRequest, ServerResponse;
import scorpion.context : Context; import scorpion.context : Context;


/** /**
* Attribute for controllers. * Attribute for controllers to be used with classes that contain
* routes.
* Example: * Example:
* --- * ---
* @Controller * @Controller
Expand All @@ -30,14 +31,27 @@ struct Controller {
* Example: * Example:
* --- * ---
* @Route("GET", false, "hello", "world") // GET /hello/world * @Route("GET", false, "hello", "world") // GET /hello/world
* @Post // POST / * @Post // POST /
* @Delete("resource", "([a-z]+)") // DELETE /resource/:name * @Delete("resource", "([a-z]+)") // DELETE /resource/:name
* --- * ---
*/ */
struct Route { struct Route {


/**
* Method accepted, conventionally uppercase.
*/
string method; string method;

/**
* Indicates whether the request can have a body. If set false
* the body, if present, is always ignored.
*/
bool hasBody; bool hasBody;

/**
* Route's path. It is later glued used path separators by
* the router manager.
*/
string[] path; string[] path;


this(string method, bool hasBody, string[] path...) { this(string method, bool hasBody, string[] path...) {
Expand Down Expand Up @@ -73,6 +87,49 @@ Route Delete(string[] path...) {
return Route("DELETE", true, path); return Route("DELETE", true, path);
} }


/**
* Callable functions are routes that can be called from javascipt
* using scorpion's javscript file (served to `/assets/scorpion.js`),
* calling the `scorpion.call` javscript function.
* The javascipt function takes a arguments the name of the function,
* an object with the function's parameter and a callback. Both the
* object and the callbacks are optional.
* Example:
* ---
* // D code
* @Callable
* uint randomInteger() {
* return uniform!uint();
* }
*
* // JS code
* scorpion.call("randomInteger", {}, number => console.log(number));
* ---
* Example:
* ---
* // D code
* @Callable
* void startProcess(string name) {
* processFactory.start(name);
* }
*
* // JS code
* scorpion.call("startProcess", {name: "my_process"});
* ---
* The `Callable` attribute, like other routes, can also be used with
* other custom attributes such as `Auth` and `AuthRedirect`.
*/
struct Callable {

/**
* Optional name of the function. If not present it defaults
* to the function's name the `Callable` attribute is associated
* with.
*/
string functionName;

}

/** /**
* Useful regular expressions for routing. * Useful regular expressions for routing.
* Note that every regular expression in this enum is enclosed * Note that every regular expression in this enum is enclosed
Expand Down Expand Up @@ -134,7 +191,16 @@ enum Path;
* } * }
* --- * ---
*/ */
struct Param { string param; } struct Param {

/**
* Indicates the name of the parameter. If not present defaults
* to the identifier of the function's parameter that the `Param`
* attribute is associated to.
*/
string param;

}


/** /**
* Attribute that indicates that the parameter is converted from * Attribute that indicates that the parameter is converted from
Expand Down
2 changes: 1 addition & 1 deletion src/scorpion/package.d
Expand Up @@ -4,7 +4,7 @@ public import lighttp : Request = ServerRequest, Response = ServerResponse, Stat


public import scorpion.component : Component, Init, Value; public import scorpion.component : Component, Init, Value;
public import scorpion.config : Configuration, LanguageConfiguration, ProfilesConfiguration; public import scorpion.config : Configuration, LanguageConfiguration, ProfilesConfiguration;
public import scorpion.controller : Controller, Get, Post, Put, Delete, Paths, Path, Param, Body, Async; public import scorpion.controller : Controller, Get, Post, Put, Delete, Callable, Paths, Path, Param, Body, Async;
public import scorpion.entity : Entity; public import scorpion.entity : Entity;
public import scorpion.profile : Profile; public import scorpion.profile : Profile;
public import scorpion.repository : Repository, Select, Insert, Update, Remove, Where, OrderBy, Limit, Fields; public import scorpion.repository : Repository, Select, Insert, Update, Remove, Where, OrderBy, Limit, Fields;
Expand Down
2 changes: 1 addition & 1 deletion src/scorpion/validation.d
Expand Up @@ -221,7 +221,7 @@ enum ContentType {


} }


T validateBody(T)(ServerRequest request, ServerResponse response, ref Validation validation) { T validateBody(T)(ServerRequest request, ServerResponse response, Validation validation) {
T ret; T ret;
static if(is(T == class)) ret = new T(); static if(is(T == class)) ret = new T();
auto contentType = { auto contentType = {
Expand Down
9 changes: 4 additions & 5 deletions src/scorpion/welcome.d
Expand Up @@ -27,21 +27,20 @@ class ScorpionWelcomeController {
view.render!("scorpion-welcome.index.dt", example); view.render!("scorpion-welcome.index.dt", example);
} }


@Get("number", "([0-9]*)")
getNumber(Response response, @Path string number) {
response.body_ = number;
}

} }


@Controller("assets") @Controller("assets")
class ScorpionAssetsController { class ScorpionAssetsController {


@Get("scorpion.svg") @Get("scorpion.svg")
Resource logo; Resource logo;

@Get("scorpion.js")
Resource scorpionJs;


this() { this() {
logo = new CachedResource("image/svg+xml", import("scorpion.logo.svg")); logo = new CachedResource("image/svg+xml", import("scorpion.logo.svg"));
scorpionJs = new CachedResource("text/javascript", import("scorpion.js"));
} }


} }
38 changes: 38 additions & 0 deletions views/scorpion.js
@@ -0,0 +1,38 @@
const scorpion = {

ajax: function(info){
var request = new XMLHttpRequest();
request.open(info.method, info.path);
request.setRequestHeader('Content-Type', 'application/json');
request.onreadystatechange = function(){
if(request.readyState == 4) {
if(request.status >= 200 && request.status < 300) {
if(info.success) info.success(JSON.parse(request.responseText), request);
} else {
if(info.fail) info.fail(request);
}
}
};
request.send(JSON.stringify(info.data));
},

get: function(info){
info.method = 'GET';
scorpion.ajax(info);
},

post: function(info){
info.method = 'POST';
scorpion.ajax(info);
},

call: function(functionName, args, callback){
scorpion.ajax({
method: 'CALL',
path: '/internal/function/' + functionName,
data: args,
success: callback
});
}

};

0 comments on commit c2f93ad

Please sign in to comment.