# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # 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[MClass] 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 # 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 var mclass = mclassdef.mclass mclass.restful_methods.add mproperty restful_classes.add mclass end end redef class MClass # Methods with the `restful` annotation in this class private var restful_methods = new Array[MMethod] 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}}}) """ 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.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 mclass in phase.restful_classes do var t = new Template nit_module.content.add t t.add """ redef class {{{mclass}}} redef fun answer(request, truncated_uri) do var verbs = truncated_uri.split("/") if verbs.not_empty and verbs.first.is_empty then verbs.shift if verbs.length != 1 then return super var verb = verbs.first """ var methods = mclass.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 " t.add """if verb == "{{{method.name}}}" then """ 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 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 not {{{isas.join(" or not ")}}} then return super end """ var sig = "" if args.not_empty then sig = "({args.join(", ")})" t.add """ return {{{method.name}}}{{{sig}}} """ end t.add """ end return 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