X-Git-Url: http://nitlanguage.org diff --git a/src/nitrestful.nit b/src/nitrestful.nit index 22ffd1d..6a93f30 100644 --- a/src/nitrestful.nit +++ b/src/nitrestful.nit @@ -23,7 +23,7 @@ private class RestfulPhase 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 @@ -39,11 +39,30 @@ private class RestfulPhase 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( @@ -58,18 +77,31 @@ private class RestfulPhase 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]) @@ -99,7 +131,7 @@ redef class MType 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 @@ -161,6 +193,8 @@ else 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. @@ -173,33 +207,54 @@ end 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 @@ -209,7 +264,11 @@ redef class {{{mclass}}} """ 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 @@ -222,22 +281,67 @@ redef class {{{mclass}}} 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