swagger-api / swagger-codegen

swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition.
http://swagger.io
Apache License 2.0
17.09k stars 6.03k forks source link

[HASKELL] Generated types do not produce/consume JSON according to spec #3033

Open runeksvendsen opened 8 years ago

runeksvendsen commented 8 years ago
Description

In a Swagger spec file, when specifying eg. "payment_data" as a field name in an object, the resulting Haskell type produces (and consumes) JSON with a field name of "PaymentData". So eg. "channel_value_left" becomes "ChannelValueLeft". This breaks compatibility with the client library.

Swagger-codegen version

master @ a3610f1c12e5d7912cf2e9f16dff2152ef40291b

Swagger declaration file content or url

https://gist.github.com/runeksvendsen/305a3018fe48723998851419c2e4393d

Command line used for generation
$ java -jar swagger-codegen/modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -l haskell -i swagger.yaml
$ java -version
    java version "1.8.0_92"
    Java(TM) SE Runtime Environment (build 1.8.0_92-b14)
    Java HotSpot(TM) 64-Bit Server VM (build 25.92-b14, mixed mode)
Steps to reproduce
wget https://gist.githubusercontent.com/runeksvendsen/305a3018fe98851419c2e4393d/raw/5d022bdef0355128c54d2235ea986aa84efa3e3e/swagger.yaml
java -jar swagger-codegen/modules/swagger-codegen-cli/target/swagger-codegen-cli.jar generate -l haskell -i swagger.yaml
cat lib/BitcoinPaymentChannelRESTProtocol/Types.hs
Suggest a Fix

Perhaps add a config option like the modelPropertyNaming option that typescript-node has, which allows choosing word separation semantics ('camelCase', 'PascalCase', 'snake_case' etc.).

runeksvendsen commented 8 years ago

Changing the hard-coded camelize default (in modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java) to snakeCase makes it work for snake case only. But I'm not sure how add an option, or how to just preserve the naming convention used in the swagger spec.

diff --git a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
index 324ca56..cbb369c 100644
--- a/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
+++ b/modules/swagger-codegen/src/main/java/io/swagger/codegen/languages/HaskellServantCodegen.java
@@ -466,9 +466,9 @@ public class HaskellServantCodegen extends DefaultCodegen implements CodegenConf
         }

         // From the model name, compute the prefix for the fields.
-        String prefix = camelize(model.classname, true);
+        String prefix = camelize(model.classname, true) + "_";
         for(CodegenProperty prop : model.vars) {
-            prop.name = prefix + camelize(fixOperatorChars(prop.name));
+            prop.name = prefix + snakeCase(fixOperatorChars(prop.name));
         }
wing328 commented 8 years ago

@runeksvendsen thanks for reporting the issue. You are correct in saying that typescript allows an option to specify the naming convention of the model property name but I would suggest using a library for mapping between property name and JSON key name.

For Java, C#, that's done by the JSON library.

For ruby and php, there's no library available to do that and we've manually handled the object serialization/deserialization ourselves.

Are you aware of any Haskell module/library that can map between property name and JSON key name?

wing328 commented 8 years ago

cc @algas

runeksvendsen commented 8 years ago

Alright, I found out how to do this.

Here's the diff for lib/Gen/Types.hs:

-import Data.Aeson.Types (Options(..), defaultOptions)
+import Data.Aeson.Types (Options(..), defaultOptions, camelTo2)

removeFieldLabelPrefix :: Bool -> String -> Options
 removeFieldLabelPrefix forParsing prefix =
   defaultOptions
-    { fieldLabelModifier = fromMaybe (error ("did not find prefix " ++ prefix)) . stripPrefix prefix . replaceSpecialChars
+    { fieldLabelModifier = camelTo2 '_' . fromMaybe (error ("did not find prefix " ++ prefix)) . stripPrefix prefix . replaceSpecialChars

So just add the camelTo2 import from Data.Aeson.Types, and add camelTo2 '_' . before fromMaybe in removeFieldLabelPrefix.

wing328 commented 7 years ago

@runeksvendsen thanks for sharing the fix. May I know if you've time to file a PR for the fix?

A good starting point is https://github.com/swagger-api/swagger-codegen/blob/master/modules/swagger-codegen/src/main/resources/haskell-servant/Types.mustache

doctau commented 7 years ago

Using camelTo2 is the fix if the swagger definition uses snake case, but it breaks if it uses camel case or anything else. A better solution is probably not use the generic JSON conversion with the name mapping function, but write out "manual" instance implementations which use the correct names from the definition.

I'll try to get a PR for that done soon.