super Phase
# Classes with methods marked with the `restful` annotation
- var restful_classes = new HashSet[MClass]
+ var restful_classes = new HashSet[MClassDef]
redef fun process_annotated_node(node, nat)
do
var mpropdef = node.mpropdef
if mpropdef == null then return
-
var mproperty = mpropdef.mproperty
var mclassdef = mpropdef.mclassdef
var mmodule = mclassdef.mmodule
+ var http_resources = new Array[String]
+ var http_methods = new Array[String]
+ for arg in nat.n_args do
+ var str = arg.as_string
+ var id = arg.collect_text
+ if str != null then
+ # String -> rename resource
+ http_resources.add str
+ else if arg isa ATypeExpr and not id.chars.has("[") then
+ # Class id -> HTTP method
+ http_methods.add id
+ else if id == "async" then
+ mproperty.restful_async = true
+ else
+ toolcontext.error(nat.location,
+ "Syntax Error: `restful` expects String literals or ids as arguments.")
+ return
+ end
+ end
+
# Test subclass of `RestfulAction`
var sup_class_name = "RestfulAction"
var sup_class = toolcontext.modelbuilder.try_get_mclass_by_name(
end
# Register the property
- var mclass = mclassdef.mclass
- mclass.restful_methods.add mproperty
- restful_classes.add mclass
+ mclassdef.restful_methods.add mproperty
+ restful_classes.add mclassdef
+
+ if http_resources.not_empty then mproperty.restful_resources = http_resources
+ mproperty.restful_verbs = http_methods
end
end
-redef class MClass
+redef class MClassDef
# Methods with the `restful` annotation in this class
private var restful_methods = new Array[MMethod]
end
+redef class MMethod
+ # HTTP access methods, e.g. `GET, POST, PUT or DELETE`
+ private var restful_verbs = new Array[String] is lazy
+
+ # Associated resources within an action, e.g. `foo` in `http://localhost/foo?arg=bar`
+ private var restful_resources: Array[String] = [name] is lazy
+
+ # Is this a `restful` method to be executed asynchronously
+ private var restful_async = false
+end
+
redef class ToolContext
# Generate serialization and deserialization methods on `auto_serializable` annotated classes.
var restful_phase: Phase = new RestfulPhase(self, [modelize_class_phase])
else
# Deserialize everything else
template.add """
- var out_{{{arg_name}}} = deserialize_arg(in_{{{arg_name}}})
+ var out_{{{arg_name}}} = deserialize_arg(in_{{{arg_name}}}, "{{{self.name}}}")
"""
end
end
end
var nit_module = new NitModule(module_name)
+nit_module.annotations.add """generated"""
+nit_module.annotations.add """no_warning("parentheses")"""
nit_module.header = """
# This file is generated by nitrestful
# Do not modify, instead refine the generated services.
var phase = toolcontext.restful_phase
assert phase isa RestfulPhase
-for mclass in phase.restful_classes do
+for mclassdef in phase.restful_classes do
+ var mclass = mclassdef.mclass
var t = new Template
nit_module.content.add t
+ var classes = new Template
+ nit_module.content.add classes
+
t.add """
redef class {{{mclass}}}
- redef fun answer(request, truncated_uri)
+ redef fun prepare_respond_and_close(request, truncated_uri, http_server)
do
- var verbs = truncated_uri.split("/")
- if verbs.not_empty and verbs.first.is_empty then verbs.shift
+ var resources = truncated_uri.split("/")
+ if resources.not_empty and resources.first.is_empty then resources.shift
- if verbs.length != 1 then return super
- var verb = verbs.first
+ if resources.length != 1 then
+ super
+ return
+ end
+ var resource = resources.first
"""
- var methods = mclass.restful_methods
+ var methods = mclassdef.restful_methods
for i in methods.length.times, method in methods do
var msig = method.intro.msignature
if msig == null then continue
- t.add " "
- if i != 0 then t.add "else "
+ # Condition to select this method from a request
+ var conds = new Array[String]
+
+ # Name of the resource from the method or as renamed
+ var resource_conds = new Array[String]
+ for resource in method.restful_resources do resource_conds.add "resource == \"{resource}\""
+ conds.add "(" + resource_conds.join(" or ") + ")"
+
+ # HTTP methods/verbs
+ if method.restful_verbs.not_empty then
+ var method_conds = new Array[String]
+ for meth in method.restful_verbs do method_conds.add "request.method == \"{meth}\""
+ conds.add "(" + method_conds.join(" or ") + ")"
+ end
- t.add """if verb == "{{{method.name}}}" then
+ t.add """
+ if {{{conds.join(" and ")}}} then
"""
+ # Extract the arguments from the request for the method call
var args = new Array[String]
var isas = new Array[String]
for param in msig.mparameters do
"""
var mtype = param.mtype
- mtype.gen_arg_convert(t, param.name)
+ var bound_mtype = mclassdef.bound_mtype
+ var resolved_mtype = mtype.resolve_for(bound_mtype, bound_mtype, mclassdef.mmodule, true)
+ var resolved_type_name = resolved_mtype.name
+
+ resolved_mtype.gen_arg_convert(t, param.name)
var arg = "out_{param.name}"
args.add arg
end
if isas.not_empty then t.add """
- if not {{{isas.join(" or not ")}}} then
- return super
- end
+ if {{{isas.join(" and ")}}} then
"""
var sig = ""
if args.not_empty then sig = "({args.join(", ")})"
+ if not method.restful_async then
+ # Synchronous method
+ t.add """
+ var response = {{{method.name}}}{{{sig}}}
+ http_server.respond response
+ http_server.close
+ return
+"""
+ else
+ # Asynchronous method
+ var task_name = "Task_{mclass}_{method.name}"
+ args.unshift "http_server"
+ args.unshift "request"
+ args.unshift "self"
+
+ t.add """
+ var task = new {{{task_name}}}({{{args.join(", ")}}})
+ self.thread_pool.execute task
+ return
+"""
+
+ var thread_attribs = new Array[String]
+ for param in msig.mparameters do
+ thread_attribs.add """
+ private var out_{{{param.name}}}: {{{param.mtype}}}"""
+ end
+
+ classes.add """
+
+# Generated task to execute {{{mclass}}}::{{{method.name}}}
+class {{{task_name}}}
+ super RestfulTask
+
+ redef type A: {{{mclass}}}
+
+{{{thread_attribs.join("\n")}}}
+
+ redef fun indirect_restful_method
+ do
+ return action.{{{method.name}}}{{{sig}}}
+ end
+end
+"""
+ end
+
+ if isas.not_empty then t.add """
+ end
+"""
t.add """
- return {{{method.name}}}{{{sig}}}
+ end
"""
end
t.add """
- end
- return super
+ super
end
end"""
end