Skip to content
Permalink
Browse files

Add callables

  • Loading branch information...
Kripth committed Jan 11, 2019
1 parent ebfc1d8 commit c2f93adb9d5530c9e2f88e7dee1b33fd6f80570a

This file was deleted.

Oops, something went wrong.
@@ -0,0 +1,4 @@
name "scorpion-javascript-to-d"
targetType "library"
dependency "scorpion" path="../.."
dependency "scorpion-boot" version="~master"
@@ -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() {}

}

This file was deleted.

Oops, something went wrong.
@@ -5,7 +5,10 @@ import std.exception : enforce;
import std.experimental.logger : sharedLog, LogLevel, info;
import std.regex : Regex;
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.router : Router, routeInfo;
@@ -15,7 +18,7 @@ import lighttp.util : StatusCodes, ServerRequest, ServerResponse;
import scorpion.component : Component, Init, Value;
import scorpion.config : Config, Configuration, LanguageConfiguration, ProfilesConfiguration;
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.lang : LanguageManager;
import scorpion.profile : Profile;
@@ -59,12 +62,6 @@ final class ScorpionServer {
*/
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
* routes.
@@ -287,16 +284,16 @@ final class ScorpionServer {
override void init(Router router, Context context, Database database) {
T controller = new T();
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") {
immutable full = "controller." ~ member;
alias F = __traits(getMember, T, member);
enum tests = {
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(__traits(compiles, uda())) ret ~= "__traits(getAttributes, F)[" ~ i.to!string ~ "].init";
else ret ~= "__traits(getAttributes, F)[" ~ i.to!string ~ "]";
static if(__traits(compiles, uda())) ret ~= "__traits(getAttributes, F)[" ~ j.to!string ~ "].init";
else ret ~= "__traits(getAttributes, F)[" ~ j.to!string ~ "]";
}
}
return ret;
@@ -311,7 +308,7 @@ final class ScorpionServer {
static if(isFunction!F) {
auto fun = mixin(generateFunction!F(T.stringof, member, regexPath, tests));
} 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){
context.refresh(request, response);
static foreach(test ; tests) {
@@ -324,6 +321,34 @@ final class ScorpionServer {
}
router.add(routeInfo(uda.method, uda.hasBody, regexPath), fun);
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)) {
@@ -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) {
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 ~ "`;";
@@ -357,8 +392,7 @@ private string generateFunction(alias M)(string controller, string member, strin
body2 ~= "View view=View(request,response,languageManager);";
call[i] = "view";
} else static if(is(param == Session)) {
body2 ~= "Session session=Session.get(request);";
call[i] = "session";
call[i] = "context.session";
} else static if(is(param == Validation)) {
call[i] = "validation";
validation = true;
@@ -5,7 +5,8 @@ import lighttp.util : Status, ServerRequest, ServerResponse;
import scorpion.context : Context;

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

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

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

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

this(string method, bool hasBody, string[] path...) {
@@ -73,6 +87,49 @@ Route Delete(string[] 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.
* Note that every regular expression in this enum is enclosed
@@ -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
@@ -4,7 +4,7 @@ public import lighttp : Request = ServerRequest, Response = ServerResponse, Stat

public import scorpion.component : Component, Init, Value;
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.profile : Profile;
public import scorpion.repository : Repository, Select, Insert, Update, Remove, Where, OrderBy, Limit, Fields;
@@ -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;
static if(is(T == class)) ret = new T();
auto contentType = {
@@ -27,21 +27,20 @@ class ScorpionWelcomeController {
view.render!("scorpion-welcome.index.dt", example);
}

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

}

@Controller("assets")
class ScorpionAssetsController {

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

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

this() {
logo = new CachedResource("image/svg+xml", import("scorpion.logo.svg"));
scorpionJs = new CachedResource("text/javascript", import("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.
You can’t perform that action at this time.