Search code examples
haskellswaggerbson

Servant.Swagger ToSchema for Data.Bson.ObjectId


I'm trying to build Swagger spec for my Servant project.

Part of my API definition contains internal model objects which have _id field of type Data.Bson.ObjectId

type API = rest
      :<|> "signup" :> ReqBody '[JSON] Credentials :> Post '[JSON] Account
      :<|> "signin" :> ReqBody '[JSON] Credentials :> Post '[JSON] Session

So I need to define ToSchema for Account and Session to avoid adding additional layers which will have String fields instead of ObjectId, for example.

data Account = Account
    { _id       :: ObjectId
    , user      :: User
    , createdAt :: UTCTime
    , updatedAt :: UTCTime
    } deriving (Generic, Typeable, Show, Read, Eq, Ord)

instance ToBSON Account
instance FromBSON Account

instance ToJSON Account where
    toJSON (Account oid user createdAt updatedAt) =
        object [ "_id"       .= show oid
               , "user"      .= toJSON user
               , "createdAt" .= iso8601Show createdAt
               , "updatedAt" .= iso8601Show updatedAt ]

instance FromJSON Account where
    parseJSON (Object o) =
        Account <$> o .: "_id"
                <*> o .: "user"
                <*> o .: "createdAt"
                <*> o .: "updatedAt"
instance ToSchema Account where
  declareNamedSchema proxy = genericDeclareNamedSchema defaultSchemaOptions proxy
    & mapped.schema.description ?~ "This is an example of a Account"
    & mapped.schema.example ?~ toJSON Model_Account.demo

instance ToSchema User where
  declareNamedSchema proxy = genericDeclareNamedSchema defaultSchemaOptions proxy
    & mapped.schema.description ?~ "This is an example of a User"
    & mapped.schema.example ?~ toJSON Model_User.demo

instance ToSchema Session where
  declareNamedSchema proxy = genericDeclareNamedSchema defaultSchemaOptions proxy
    & mapped.schema.description ?~ "This is an example of a Session"
    & mapped.schema.example ?~ toJSON Model_Session.demo

I also have to implement ToSchema for ObjectId and I don't understand how to do that.

instance ToSchema ObjectId where
  declareNamedSchema proxy = genericDeclareNamedSchema defaultSchemaOptions proxy
    & mapped.schema.description ?~ "This is an example of ObjectId"
    & mapped.schema.example ?~ "5e8da2d2393d8b1acd000001"

I get the following error

Data.Swagger.Internal.Schema.genericDeclareNamedSchema
:: SchemaOptions
-> Proxy ObjectId -> Declare (Definitions Schema) NamedSchema
Defined in ‘Data.Swagger.Internal.Schema’

Server.hs:210:30: error:
    • Could not deduce: Data.Swagger.Internal.TypeShape.GenericHasSimpleShape
                          ObjectId
                          "genericDeclareNamedSchemaUnrestricted"
                          (Data.Swagger.Internal.TypeShape.GenericShape (Rep ObjectId))
        arising from a use of ‘genericDeclareNamedSchema’
    • In the first argument of ‘(&)’, namely
        ‘genericDeclareNamedSchema defaultSchemaOptions proxy’
      In the first argument of ‘(&)’, namely
        ‘genericDeclareNamedSchema defaultSchemaOptions proxy
           & mapped . schema . description
               ?~ "This is an example of ObjectId"’
      In the expression:
        genericDeclareNamedSchema defaultSchemaOptions proxy
          & mapped . schema . description ?~ "This is an example of ObjectId"
          & mapped . schema . example ?~ "5e8da2d2393d8b1acd000001"
Server.hs:210:30: error:
    • No instance for (Generic ObjectId)
        arising from a use of ‘genericDeclareNamedSchema’
    • In the first argument of ‘(&)’, namely
        ‘genericDeclareNamedSchema defaultSchemaOptions proxy’
      In the first argument of ‘(&)’, namely
        ‘genericDeclareNamedSchema defaultSchemaOptions proxy
           & mapped . schema . description
               ?~ "This is an example of ObjectId"’
      In the expression:
        genericDeclareNamedSchema defaultSchemaOptions proxy
          & mapped . schema . description ?~ "This is an example of ObjectId"
          & mapped . schema . example ?~ "5e8da2d2393d8b1acd000001"

How can I add ToSchema implementation for a custom type like ObjectId which could be easily converted to String because it implements show/read?


Solution

  • • No instance for (Generic ObjectId)
    

    As its name implies, genericDeclareNamedSchema works only for data types that have an instance of Generic. ObjectId does not implement it, so your only option is to declare the schema entirely by hand, see the documentation of ToSchema for an example.