1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Tool generating boilerplate code linking RESTful actions to Nit methods
22 private class RestfulPhase
25 # Classes with methods marked with the `restful` annotation
26 var restful_classes
= new HashSet[MClass]
28 redef fun process_annotated_node
(node
, nat
)
30 # Skip if we are not interested
31 var text
= nat
.n_atid
.n_id
.text
32 if text
!= "restful" then return
34 if not node
isa AMethPropdef then
35 toolcontext
.error
(nat
.location
,
36 "Syntax Error: `restful` can only be applied on method definitions")
40 var mpropdef
= node
.mpropdef
41 if mpropdef
== null then return
43 var mproperty
= mpropdef
.mproperty
44 var mclassdef
= mpropdef
.mclassdef
45 var mmodule
= mclassdef
.mmodule
47 # Test subclass of `RestfulAction`
48 var sup_class_name
= "RestfulAction"
49 var sup_class
= toolcontext
.modelbuilder
.try_get_mclass_by_name
(
50 nat
, mmodule
, sup_class_name
)
51 var in_hierarchy
= mclassdef
.in_hierarchy
52 if in_hierarchy
== null or sup_class
== null then return
53 var sup_classes
= in_hierarchy
.greaters
54 if not sup_classes
.has
(sup_class
.intro
) then
55 toolcontext
.error
(nat
.location
,
56 "Syntax Error: `restful` is only valid within subclasses of `{sup_class_name}`")
60 # Register the property
61 var mclass
= mclassdef
.mclass
62 mclass
.restful_methods
.add mproperty
63 restful_classes
.add mclass
69 # Methods with the `restful` annotation in this class
70 private var restful_methods
= new Array[MMethod]
73 redef class ToolContext
74 # Generate serialization and deserialization methods on `auto_serializable` annotated classes.
75 var restful_phase
: Phase = new RestfulPhase(self, [modelize_class_phase
])
77 # Where do we put a single result?
78 var opt_output
: OptionString = new OptionString("Output file (can also be 'stdout')", "-o", "--output")
80 # Where do we put the result?
81 var opt_dir
: OptionString = new OptionString("Output directory", "--dir")
85 option_context
.add_option
(opt_output
, opt_dir
)
91 # Write code in `template` to parse the argument `arg_name` to this parameter type
92 private fun gen_arg_convert
(template
: Template, arg_name
: String)
94 if self.name
== "String" or self.name
== "nullable String" then
95 # String are used as is
97 var out_{{{arg_name}}} = in_{{{arg_name}}}
100 # Deserialize everything else
102 var out_{{{arg_name}}} = deserialize_arg(in_{{{arg_name}}})
107 # Does this parameter type needs to be checked before calling the method?
109 # Some nullable types do not need to be check as `null` values are acceptable.
110 private fun needs_type_check
: Bool do return true
113 redef class MNullableType
114 redef fun needs_type_check
do return name
!= "nullable String" and name
!= "nullable Object"
117 var toolcontext
= new ToolContext
118 toolcontext
.tooldescription
= """
119 Usage: nitrestful [OPTION] module.nit [other_module.nit [...]]
120 Generates the boilerplate code to link RESTful request to static Nit methods."""
122 toolcontext
.process_options args
123 var arguments
= toolcontext
.option_context
.rest
126 if toolcontext
.opt_output
.value
!= null and toolcontext
.opt_dir
.value
!= null then
127 print
"Error: cannot use both --dir and --output"
130 if arguments
.length
> 1 and toolcontext
.opt_output
.value
!= null then
131 print
"Error: --output needs a single source file. Do you prefer --dir?"
135 var model
= new Model
136 var modelbuilder
= new ModelBuilder(model
, toolcontext
)
138 var mmodules
= modelbuilder
.parse
(arguments
)
139 modelbuilder
.run_phases
140 var first_mmodule
= mmodules
.first
142 # Name of the support module
145 # Path to the support module
146 var module_path
= toolcontext
.opt_output
.value
147 if module_path
== null then
148 module_name
= "{first_mmodule.name}_rest"
149 module_path
= "{module_name}.nit"
151 var dir
= toolcontext
.opt_dir
.value
152 if dir
!= null then module_path
= dir
.join_path
(module_path
)
153 else if module_path
== "stdout" then
154 module_name
= "{first_mmodule.name}_rest"
156 else if module_path
.has_suffix
(".nit") then
157 module_name
= module_path
.basename
(".nit")
159 module_name
= module_path
.basename
160 module_path
+= ".nit"
163 var nit_module
= new NitModule(module_name
)
164 nit_module
.header
= """
165 # This file is generated by nitrestful
166 # Do not modify, instead refine the generated services.
169 for mmod
in mmodules
do
170 nit_module
.imports
.add mmod
.name
173 var phase
= toolcontext
.restful_phase
174 assert phase
isa RestfulPhase
176 for mclass
in phase
.restful_classes
do
179 nit_module
.content
.add t
182 redef class {{{mclass}}}
183 redef fun answer(request, truncated_uri)
185 var verbs = truncated_uri.split("/")
186 if verbs.not_empty and verbs.first.is_empty then verbs.shift
188 if verbs.length != 1 then return super
189 var verb = verbs.first
192 var methods
= mclass
.restful_methods
193 for i
in methods
.length
.times
, method
in methods
do
194 var msig
= method
.intro
.msignature
195 if msig
== null then continue
198 if i
!= 0 then t
.add
"else "
200 t
.add
"""if verb == "{{{method.name}}}" then
203 var args
= new Array[String]
204 var isas
= new Array[String]
205 for param
in msig
.mparameters
do
208 var in_{{{param.name}}} = request.string_arg("{{{param.name}}}")
211 var mtype
= param
.mtype
212 mtype
.gen_arg_convert
(t
, param
.name
)
214 var arg
= "out_{param.name}"
217 if mtype
.needs_type_check
then
218 isas
.add
"{arg} isa {mtype.name}"
224 if isas
.not_empty
then t
.add
"""
225 if not {{{isas.join(" or not ")}}} then
231 if args
.not_empty
then sig
= "({args.join(", ")})"
234 return {{{method.name}}}{{{sig}}}
245 # Write support module
246 if module_path
!= null then
248 nit_module
.write_to_file module_path
251 nit_module
.write_to stdout