Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generics/TH deriving #2

Closed
fizruk opened this issue Nov 5, 2015 · 3 comments
Closed

Generics/TH deriving #2

fizruk opened this issue Nov 5, 2015 · 3 comments

Comments

@fizruk
Copy link
Member

fizruk commented Nov 5, 2015

First question is which classes should gain Generics/TH implementation?

My thoughts:

  • ToSwaggerParamType might have a Generics-based default implementation;
  • ToSwaggerModel should have a TH derivation (Generics will not be a good choice here because users will most likely want to customize generated instance with something similar to deriveJSON options);
  • ToModelExample might have a Generics-based default implementation (BTW, is this class used anywhere?);
  • all other classes should not have Generics/TH implementation.

I would say that ToSwaggerModel is responsible for the most of the boilerplate currently (apart from RouteInfo), so it should be addressed first.

Does this make sense?

@dmjio
Copy link
Member

dmjio commented Nov 5, 2015

@fizruk, For the most part, yes.

Apart from body, a parameter in Swagger must have one of the following types.

The value MUST be one of "string", "number", "integer", "boolean", "array" or "file"

ToSwaggerParamType was meant to model this. The assumption was that most parameters of this type will be created with newtype and could be derived via GeneralizedNewtypeDeriving.

data SwaggerParamType =
    StringSwagParam
  | NumberSwagParam
  | IntegerSwagParam
  | BooleanSwagParam
  | ArraySwagParam 
  | FileSwagParam

class ToSwaggerParamType a where toSwaggerParamType :: Proxy a -> SwaggerParamType
instance ToSwaggerParamType Int where toSwaggerParamType = const IntegerSwagParam
instance ToSwaggerParamType Integer where toSwaggerParamType = const IntegerSwagParam
instance ToSwaggerParamType UUID.UUID where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType String where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType Text where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType L.Text where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType BL8.ByteString where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType B8.ByteString where toSwaggerParamType = const StringSwagParam
instance ToSwaggerParamType Double where toSwaggerParamType = const NumberSwagParam
instance ToSwaggerParamType Float where toSwaggerParamType = const NumberSwagParam
instance ToSwaggerParamType Bool where toSwaggerParamType Proxy = BooleanSwagParam
instance
  ToSwaggerParamType a => ToSwaggerParamType [a] where
    toSwaggerParamType _ = ArraySwagParam

So it would be easy to do something like,

newtype UserID = UserID Int deriving (ToSwaggerParamType)

So I think ToSwaggerParamType might not need a generics implementation, since we provide most cases. Unless I'm being short-sighted.

In regards to ToSwaggerModel, it is possible to use generics and provide default information regarding the derivation (i.e adding a prefix / suffix to the field names) - I believe getopt-generics does this. I think users will want to have the field names of the ToJSON representations of objects. The reason being that a swagger model is a json-schema (http://json-schema.org/). So if there was a way to get the name of the ToJSON representation of fields, along with the ToSwaggerType of the type of each field, we could be pretty accurate. @kosmikus has provided a default implementation here of how this would be possible with generics-sop. (https://gist.github.com/kosmikus/6ccdda13fd7938b2857e). Getting this code right is what will make or break this library I believe.

I'd like to avoid template haskell if possible, since it breaks modularity of definitions in a module.

@kosmikus
Copy link

kosmikus commented Nov 5, 2015

FWIW, I can confirm that there's no reason to switch from generics to TH just because you need some additional parameterization. Even aeson allows this (in one of several conceivable ways). In aeson, you have generic implementations of genericToJSON and genericParseJSON. Both take an argument of type Options that you can use for customization. If you give empty instance declarations for FromJSON and ToJSON, the generic defaults are used, applied to defaultOptions. But you can give manual instances like

instance FromJSON Foo where
  parseJSON = genericParseJSON myOptions

to use different options. That's still rather verbose compared to an empty instance declaration, but not exactly boilerplate. There are a number of ways to reduce the syntactic clutter somewhat more. Which one's best depends a bit on personal taste. I can go into more details here or on IRC.

@fizruk
Copy link
Member Author

fizruk commented Dec 31, 2015

Closed in #9.

@fizruk fizruk closed this as completed Dec 31, 2015
maksbotan added a commit to biocad/servant-swagger that referenced this issue Mar 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants