nitc :: nitrestful
nitc :: RestfulPhase
nitc :: nitrestful $ MClassDef
A definition (an introduction or a refinement) of a class in a modulenitc :: nitrestful $ MClassDef
A definition (an introduction or a refinement) of a class in a modulenitc :: actors_injection_phase
Injects model for the classes annotated with "is actor" sonitc :: astbuilder
Instantiation and transformation of semantic nodes in the AST of expressions and statementsnitc :: i18n_phase
Basic support of internationalization through the generation of id-to-string tablesSerializable::inspect
to show more useful information
nitc :: modelbuilder
more_collections :: more_collections
Highly specific, but useful, collections-related classes.threaded
annotation
serialization :: serialization_core
Abstract services to serialize Nit objects to different formatsnitc :: serialization_model_phase
Phase generating methods (model-only) to serialize Nit objectsnitc :: toolcontext
Common command-line tool infrastructure than handle options and error messagescore :: union_find
union–find algorithm using an efficient disjoint-set data structure
# Tool generating boilerplate code linking RESTful actions to Nit methods
module nitrestful
import gen_nit
import frontend
private class RestfulPhase
super Phase
# Classes with methods marked with the `restful` annotation
var restful_classes = new HashSet[MClassDef]
redef fun process_annotated_node(node, nat)
do
# Skip if we are not interested
var text = nat.n_atid.n_id.text
if text != "restful" then return
if not node isa AMethPropdef then
toolcontext.error(nat.location,
"Syntax Error: `restful` can only be applied on method definitions")
return
end
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(
nat, mmodule, sup_class_name)
var in_hierarchy = mclassdef.in_hierarchy
if in_hierarchy == null or sup_class == null then return
var sup_classes = in_hierarchy.greaters
if not sup_classes.has(sup_class.intro) then
toolcontext.error(nat.location,
"Syntax Error: `restful` is only valid within subclasses of `{sup_class_name}`")
return
end
# Register the property
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 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])
# Where do we put a single result?
var opt_output: OptionString = new OptionString("Output file (can also be 'stdout')", "-o", "--output")
# Where do we put the result?
var opt_dir: OptionString = new OptionString("Output directory", "--dir")
redef init
do
option_context.add_option(opt_output, opt_dir)
super
end
end
redef class MType
# Write code in `template` to parse the argument `arg_name` to this parameter type
private fun gen_arg_convert(template: Template, arg_name: String)
do
if self.name == "String" or self.name == "nullable String" then
# String are used as is
template.add """
var out_{{{arg_name}}} = in_{{{arg_name}}}
"""
else
# Deserialize everything else
template.add """
var out_{{{arg_name}}} = deserialize_arg(in_{{{arg_name}}}, "{{{self.name}}}")
"""
end
end
# Does this parameter type needs to be checked before calling the method?
#
# Some nullable types do not need to be check as `null` values are acceptable.
private fun needs_type_check: Bool do return true
end
redef class MNullableType
redef fun needs_type_check do return name != "nullable String" and name != "nullable Object"
end
var toolcontext = new ToolContext
toolcontext.tooldescription = """
Usage: nitrestful [OPTION] module.nit [other_module.nit [...]]
Generates the boilerplate code to link RESTful request to static Nit methods."""
toolcontext.process_options args
var arguments = toolcontext.option_context.rest
# Check options
if toolcontext.opt_output.value != null and toolcontext.opt_dir.value != null then
print "Error: cannot use both --dir and --output"
exit 1
end
if arguments.length > 1 and toolcontext.opt_output.value != null then
print "Error: --output needs a single source file. Do you prefer --dir?"
exit 1
end
var model = new Model
var modelbuilder = new ModelBuilder(model, toolcontext)
var mmodules = modelbuilder.parse(arguments)
modelbuilder.run_phases
var first_mmodule = mmodules.first
# Name of the support module
var module_name
# Path to the support module
var module_path = toolcontext.opt_output.value
if module_path == null then
module_name = "{first_mmodule.name}_rest"
module_path = "{module_name}.nit"
var dir = toolcontext.opt_dir.value
if dir != null then module_path = dir.join_path(module_path)
else if module_path == "stdout" then
module_name = "{first_mmodule.name}_rest"
module_path = null
else if module_path.has_suffix(".nit") then
module_name = module_path.basename(".nit")
else
module_name = module_path.basename
module_path += ".nit"
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.
"""
for mmod in mmodules do
nit_module.imports.add mmod.name
end
var phase = toolcontext.restful_phase
assert phase isa RestfulPhase
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 prepare_respond_and_close(request, truncated_uri, http_server)
do
var resources = truncated_uri.split("/")
if resources.not_empty and resources.first.is_empty then resources.shift
if resources.length != 1 then
super
return
end
var resource = resources.first
"""
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
# 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 {{{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
t.add """
var in_{{{param.name}}} = request.string_arg("{{{param.name}}}")
"""
var mtype = param.mtype
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
if mtype.needs_type_check then
isas.add "{arg} isa {mtype.name}"
end
t.add "\n"
end
if isas.not_empty then t.add """
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 """
end
"""
end
t.add """
super
end
end"""
end
# Write support module
if module_path != null then
# To file
nit_module.write_to_file module_path
else
# To stdout
nit_module.write_to stdout
end
src/nitrestful.nit:15,1--356,3