GetShopTV / swagger2

Swagger 2.0 data model.
http://hackage.haskell.org/package/swagger2
BSD 3-Clause "New" or "Revised" License
74 stars 59 forks source link

reuse toJSON instances #127

Open Pitometsu opened 7 years ago

Pitometsu commented 7 years ago

E.g. I have type

data PaymentOptionsCommon id =
  PaymentOptions
    { poId          :: id
    , poPaymentType :: Maybe PaymentMethod
    , poPaymentNet  :: Maybe Int
    , poInstallment :: Maybe Int  -- days
    } deriving (Generic, Eq, Ord, Show)

type PaymentOptions   = PaymentOptionsCommon ()

And ToJSON instance like this:

instance FromJSON PaymentOptions where
  parseJSON = withObject "paymentOptions" $ \o -> do
    poId          <- skip
    poPaymentType <- o .:? "type"
    poPaymentNet  <- o .:? "deffered"
    poInstallment <- o .:? "installment"
    return $! PaymentOptions{..}

instance ToJSON PaymentOptions where
  toJSON PaymentOptions{..} =
    object
      [ "type"        .= poPaymentType
      , "deffered"    .= poPaymentNet
      , "installment" .= poInstallment
      ]

And auto-generated ToSchema instance:

instance ToSchema PaymentOptions

So, swagger would looks like:

  PaymentOptionsCommon:
    required:
      - poId
    properties:
      poId:
        items: []
        type: array
      poPaymentType:
        $ref: '#/definitions/PaymentMethod'
      poPaymentNet:
        maximum: 9223372036854776000
        minimum: -9223372036854776000
        type: integer
      poInstallment:
        maximum: 9223372036854776000
        minimum: -9223372036854776000
        type: integer
    type: object

But instead I expect to see something more like:

  PaymentOptions:
    properties:
      type:
        $ref: '#/definitions/PaymentMethod'
      deffered:
        maximum: 9223372036854776000
        minimum: -9223372036854776000
        type: integer
      installment:
        maximum: 9223372036854776000
        minimum: -9223372036854776000
        type: integer
    type: object

Without prefixes

The question is: how to do this?

Pitometsu commented 7 years ago

@fizruk can you suggest solution, please?

phadej commented 7 years ago

If you write ToJSON by hand, you have to write ToSwagger by hand as well.

@fizruk We should add that note, and an example to http://hackage.haskell.org/package/swagger2-2.1.5/docs/Data-Swagger.html#g:4, shouldn't we?

Pitometsu commented 7 years ago

like

instance ToSchema PaymentOptions where
  declareNamedSchema _ = do
    mIntSchema         <- declareSchemaRef (Proxy :: Proxy (Maybe Int))
    mPaymentTypeSchema <- declareSchemaRef (Proxy :: Proxy (Maybe PaymentMethod))
    return $ NamedSchema (Just "PaymentOptions") $ mempty
      & type_ .~ SwaggerObject
      & properties .~
      [ ("type",        mPaymentTypeSchema)
      , ("deffered",    mIntSchema)
      , ("installment", mIntSchema)
      ]
      & required .~ []
fizruk commented 7 years ago

@Pitometsu yes, that seems like a valid instance. Just two comments:

FromJSON and ToJSON instances only handle decoding/encoding, however, that's not enough to construct a Schema, because parseJSON and toJSON methods are not inspectable. So yes, you need to write your own ToSchema instance and make sure it matches ToJSON (and FromJSON).

You have a few options to write your own Schema:

instance ToSchema PaymentOptions where
  declareNamedSchema = genericDeclareNamedSchema defaultSchemaOptions
    { fieldNameModifier = f }
    where
      f "poPaymentType" = "type"
      f "poPaymentNet"  = "deffered"
      f "poInstallment" = "installment"
      f name = name

I myself rely mostly on the second option (Generic-based deriving). Normally I would use some field naming convention + SchemaOptions and aeson's Options that match each other and derive all FromJSON, ToJSON and ToSwagger using those options.

In any case even with deriving mechanisms you need to ensure that ToSchema really matches ToJSON. For that I use validateToJSON. Usually inside a QuickCheck property.

If you also use servant, consider servant-swagger's Servant.Swagger.Test. Specifically validateEveryToJSON runs validateToJSON tests for every request/response JSON in your entire API! See test suite example here.

Pitometsu commented 7 years ago

Thank you a lot for detailed answer, things become much more clear now.

fizruk commented 6 years ago

I think https://github.com/GetShopTV/swagger2/issues/127#issuecomment-335711767 should be added somewhere in the How to use this library section.