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