1
+ package io .swagger .codegen .languages ;
2
+
3
+ import io .swagger .codegen .*;
4
+ import io .swagger .models .properties .*;
5
+ import io .swagger .models .Model ;
6
+ import io .swagger .models .Operation ;
7
+ import io .swagger .models .Swagger ;
8
+
9
+ import java .util .*;
10
+ import java .io .File ;
11
+
12
+ public class HaskellServantCodegen extends DefaultCodegen implements CodegenConfig {
13
+
14
+ // source folder where to write the files
15
+ protected String sourceFolder = "src" ;
16
+ protected String apiVersion = "0.0.1" ;
17
+
18
+ /**
19
+ * Configures the type of generator.
20
+ *
21
+ * @return the CodegenType for this generator
22
+ * @see io.swagger.codegen.CodegenType
23
+ */
24
+ public CodegenType getTag () {
25
+ return CodegenType .SERVER ;
26
+ }
27
+
28
+ /**
29
+ * Configures a friendly name for the generator. This will be used by the generator
30
+ * to select the library with the -l flag.
31
+ *
32
+ * @return the friendly name for the generator
33
+ */
34
+ public String getName () {
35
+ return "haskell-servant" ;
36
+ }
37
+
38
+ /**
39
+ * Returns human-friendly help for the generator. Provide the consumer with help
40
+ * tips, parameters here
41
+ *
42
+ * @return A string value for the help message
43
+ */
44
+ public String getHelp () {
45
+ return "Generates a HaskellServantCodegen library." ;
46
+ }
47
+
48
+ public HaskellServantCodegen () {
49
+ super ();
50
+
51
+ // set the output folder here
52
+ outputFolder = "generated-code/HaskellServantCodegen" ;
53
+
54
+ /**
55
+ * Models. You can write model files using the modelTemplateFiles map.
56
+ * if you want to create one template for file, you can do so here.
57
+ * for multiple files for model, just put another entry in the `modelTemplateFiles` with
58
+ * a different extension
59
+ */
60
+ modelTemplateFiles .put (
61
+ "model.mustache" , // the template to use
62
+ ".hs" ); // the extension for each file to write
63
+
64
+ /**
65
+ * Api classes. You can write classes for each Api file with the apiTemplateFiles map.
66
+ * as with models, add multiple entries with different extensions for multiple files per
67
+ * class
68
+ */
69
+ apiTemplateFiles .put (
70
+ "api.mustache" , // the template to use
71
+ ".hs" ); // the extension for each file to write
72
+
73
+ /**
74
+ * Template Location. This is the location which templates will be read from. The generator
75
+ * will use the resource stream to attempt to read the templates.
76
+ */
77
+ embeddedTemplateDir = templateDir = "haskell" ;
78
+
79
+ /**
80
+ * Api Package. Optional, if needed, this can be used in templates
81
+ */
82
+ apiPackage = "Api" ;
83
+
84
+ /**
85
+ * Model Package. Optional, if needed, this can be used in templates
86
+ */
87
+ modelPackage = "Model" ;
88
+
89
+ /**
90
+ * Reserved words. Override this with reserved words specific to your language
91
+ */
92
+ // from https://wiki.haskell.org/Keywords
93
+ reservedWords = new HashSet <String >(
94
+ Arrays .asList (
95
+ "as" , "case" , "of" ,
96
+ "class" , "data" , // "data family", "data instance",
97
+ "default" , "deriving" , // "deriving instance",
98
+ "do" ,
99
+ "forall" , "foreign" , "hiding" ,
100
+ "id" ,
101
+ "if" , "then" , "else" ,
102
+ "import" , "infix" , "infixl" , "infixr" ,
103
+ "instance" , "let" , "in" ,
104
+ "mdo" , "module" , "newtype" ,
105
+ "proc" , "qualified" , "rec" ,
106
+ "type" , // "type family", "type instance",
107
+ "where"
108
+ )
109
+ );
110
+
111
+ /**
112
+ * Additional Properties. These values can be passed to the templates and
113
+ * are available in models, apis, and supporting files
114
+ */
115
+ additionalProperties .put ("apiVersion" , apiVersion );
116
+
117
+ /**
118
+ * Supporting Files. You can write single files for the generator with the
119
+ * entire object tree available. If the input file has a suffix of `.mustache
120
+ * it will be processed by the template engine. Otherwise, it will be copied
121
+ */
122
+ supportingFiles .add (new SupportingFile ("README.mustache" , "" , "README.md" ));
123
+ supportingFiles .add (new SupportingFile ("stack.mustache" , "" , "stack.yaml" ));
124
+ supportingFiles .add (new SupportingFile ("haskell-servant-codegen.mustache" , "" , "haskell-servant-codegen.cabal" ));
125
+ supportingFiles .add (new SupportingFile ("Setup.mustache" , "" , "Setup.hs" ));
126
+ supportingFiles .add (new SupportingFile ("LICENSE" , "" , "LICENSE" ));
127
+ supportingFiles .add (new SupportingFile ("Apis.mustache" , "lib" , "Apis.hs" ));
128
+ supportingFiles .add (new SupportingFile ("Utils.mustache" , "lib" , "Utils.hs" ));
129
+ supportingFiles .add (new SupportingFile ("Client.mustache" , "client" , "Main.hs" ));
130
+ supportingFiles .add (new SupportingFile ("Server.mustache" , "server" , "Main.hs" ));
131
+
132
+ /**
133
+ * Language Specific Primitives. These types will not trigger imports by
134
+ * the client generator
135
+ */
136
+ languageSpecificPrimitives = new HashSet <String >(
137
+ Arrays .asList (
138
+ "Bool" ,
139
+ "String" ,
140
+ "Int" ,
141
+ "Integer" ,
142
+ "Float" ,
143
+ "Char" ,
144
+ "Double" ,
145
+ "List" ,
146
+ "FilePath"
147
+ )
148
+ );
149
+
150
+ typeMapping .clear ();
151
+ // typeMapping.put("enum", "NSString");
152
+ typeMapping .put ("array" , "List" );
153
+ typeMapping .put ("set" , "Set" );
154
+ typeMapping .put ("boolean" , "Bool" );
155
+ typeMapping .put ("string" , "String" );
156
+ typeMapping .put ("int" , "Int" );
157
+ typeMapping .put ("long" , "Integer" );
158
+ typeMapping .put ("float" , "Float" );
159
+ // typeMapping.put("byte", "Byte");
160
+ typeMapping .put ("short" , "Int" );
161
+ typeMapping .put ("char" , "Char" );
162
+ typeMapping .put ("double" , "Double" );
163
+ typeMapping .put ("DateTime" , "Integer" );
164
+ // typeMapping.put("object", "Map");
165
+ typeMapping .put ("file" , "FilePath" );
166
+
167
+ importMapping .clear ();
168
+ importMapping .put ("Map" , "qualified Data.Map as Map" );
169
+
170
+ cliOptions .add (new CliOption (CodegenConstants .MODEL_PACKAGE , CodegenConstants .MODEL_PACKAGE_DESC ));
171
+ cliOptions .add (new CliOption (CodegenConstants .API_PACKAGE , CodegenConstants .API_PACKAGE_DESC ));
172
+ }
173
+
174
+ /**
175
+ * Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
176
+ * those terms here. This logic is only called if a variable matches the reseved words
177
+ *
178
+ * @return the escaped term
179
+ */
180
+ @ Override
181
+ public String escapeReservedWord (String name ) {
182
+ return name + "_" ;
183
+ }
184
+
185
+ /**
186
+ * Location to write model files. You can use the modelPackage() as defined when the class is
187
+ * instantiated
188
+ */
189
+ public String modelFileFolder () {
190
+ return outputFolder + File .separatorChar + "lib" + File .separatorChar + modelPackage ().replace ('.' , File .separatorChar );
191
+ }
192
+
193
+ /**
194
+ * Location to write api files. You can use the apiPackage() as defined when the class is
195
+ * instantiated
196
+ */
197
+ @ Override
198
+ public String apiFileFolder () {
199
+ return outputFolder + File .separatorChar + "lib" + File .separatorChar + apiPackage ().replace ('.' , File .separatorChar );
200
+ }
201
+
202
+ /**
203
+ * Optional - type declaration. This is a String which is used by the templates to instantiate your
204
+ * types. There is typically special handling for different property types
205
+ *
206
+ * @return a string value used as the `dataType` field for model templates, `returnType` for api templates
207
+ */
208
+ @ Override
209
+ public String getTypeDeclaration (Property p ) {
210
+ if (p instanceof ArrayProperty ) {
211
+ ArrayProperty ap = (ArrayProperty ) p ;
212
+ Property inner = ap .getItems ();
213
+ return "[" + getTypeDeclaration (inner ) + "]" ;
214
+ }
215
+ else if (p instanceof MapProperty ) {
216
+ MapProperty mp = (MapProperty ) p ;
217
+ Property inner = mp .getAdditionalProperties ();
218
+ return "Map.Map String " + getTypeDeclaration (inner );
219
+ }
220
+ return super .getTypeDeclaration (p );
221
+ }
222
+
223
+ /**
224
+ * Optional - swagger type conversion. This is used to map swagger types in a `Property` into
225
+ * either language specific types via `typeMapping` or into complex models if there is not a mapping.
226
+ *
227
+ * @return a string value of the type or complex model for this property
228
+ * @see io.swagger.models.properties.Property
229
+ */
230
+ @ Override
231
+ public String getSwaggerType (Property p ) {
232
+ String swaggerType = super .getSwaggerType (p );
233
+ String type = null ;
234
+ if (typeMapping .containsKey (swaggerType )) {
235
+ type = typeMapping .get (swaggerType );
236
+ if (languageSpecificPrimitives .contains (type ))
237
+ return toModelName (type );
238
+ }
239
+ else
240
+ type = swaggerType ;
241
+ return toModelName (type );
242
+ }
243
+
244
+ private String capturePath (String path , List <CodegenParameter > pathParams ) {
245
+ for (CodegenParameter p : pathParams ) {
246
+ String pName = "{" +p .baseName +"}" ;
247
+ if (path .indexOf (pName ) >= 0 ) {
248
+ path = path .replace (pName , "Capture " + "\" " +p .baseName +"\" " + p .dataType );
249
+ }
250
+ }
251
+ return path ;
252
+ }
253
+
254
+ private String queryPath (String path , List <CodegenParameter > queryParams ) {
255
+ for (CodegenParameter p : queryParams ) {
256
+ path += " :> QueryParam \" " + p .baseName + "\" " + p .dataType ;
257
+ }
258
+ return path ;
259
+ }
260
+
261
+ private String bodyPath (String path , List <CodegenParameter > bodyParams ) {
262
+ for (CodegenParameter p : bodyParams ) {
263
+ path += " :> ReqBody '[JSON] " + p .dataType ;
264
+ }
265
+ return path ;
266
+ }
267
+
268
+ private String formPath (String path , List <CodegenParameter > formParams ) {
269
+ String names = "Form" ;
270
+ for (CodegenParameter p : formParams ) {
271
+ if (p .dataType .equals ("FilePath" )){
272
+ // file data processing
273
+ }
274
+ names += p .baseName ;
275
+ }
276
+ if (formParams .size () > 0 ){
277
+ path += " :> ReqBody '[FormUrlEncoded] " + names ;
278
+ }
279
+ return path ;
280
+ }
281
+
282
+ private String headerPath (String path , List <CodegenParameter > headerParams ) {
283
+ for (CodegenParameter p : headerParams ) {
284
+ path += " :> Header \" " + p .baseName + "\" " + p .dataType ;
285
+ }
286
+ return path ;
287
+ }
288
+
289
+
290
+ private String filterReturnType (String rt ) {
291
+ if (rt == null || rt .equals ("null" )) {
292
+ return "()" ;
293
+ } else if (rt .indexOf (" " ) >= 0 ) {
294
+ return "(" + rt + ")" ;
295
+ }
296
+ return rt ;
297
+ }
298
+
299
+ private String addReturnPath (String path , String httpMethod , String returnType ) {
300
+ return path + " :> " + upperCaseFirst (httpMethod ) + " '[JSON] " + filterReturnType (returnType );
301
+ }
302
+
303
+ private String joinStrings (String sep , List <String > ss ) {
304
+ StringBuilder sb = new StringBuilder ();
305
+ for (String s : ss ) {
306
+ if (sb .length () > 0 ) {
307
+ sb .append (sep );
308
+ }
309
+ sb .append (s );
310
+ }
311
+ return sb .toString ();
312
+ }
313
+
314
+ private String replacePathSplitter (String path ) {
315
+ String [] ps = path .replaceFirst ("/" , "" ).split ("/" , 0 );
316
+ List <String > rs = new ArrayList <String >();
317
+ for (String p : ps ) {
318
+ if (p .indexOf ("{" ) < 0 ) {
319
+ rs .add ("\" " + p + "\" " );
320
+ } else {
321
+ rs .add (p );
322
+ }
323
+ }
324
+ return joinStrings (" :> " , rs );
325
+ }
326
+
327
+ private String upperCaseFirst (String str ) {
328
+ char [] array = str .toLowerCase ().toCharArray ();
329
+ array [0 ] = Character .toUpperCase (array [0 ]);
330
+ return new String (array );
331
+ }
332
+
333
+ private String parseScheme (String basePath ) {
334
+ return "Http" ;
335
+ }
336
+
337
+ @ Override
338
+ public CodegenOperation fromOperation (String resourcePath , String httpMethod , Operation operation , Map <String , Model > definitions , Swagger swagger ){
339
+ CodegenOperation op = super .fromOperation (resourcePath , httpMethod , operation , definitions , swagger );
340
+ String path = op .path ;
341
+ op .nickname = addReturnPath (headerPath (formPath (bodyPath (queryPath (capturePath (replacePathSplitter (path ), op .pathParams ), op .queryParams ), op .bodyParams ), op .formParams ), op .headerParams ), op .httpMethod , op .returnType );
342
+ return op ;
343
+ }
344
+
345
+ }
0 commit comments