Haskell-OpenAPI-Code-Generator / Haskell-OpenAPI-Client-Code-Generator

Generate Haskell client code from an OpenAPI 3 specification
46 stars 19 forks source link

Odd path parameter generation #99

Closed pbrisbin closed 5 months ago

pbrisbin commented 5 months ago

I have a path specified as,

{
    "/{content-area}/standard-sets/{id}/{lang}/standards.json": {
      "parameters":
          {
            "in": "path",
            "name": "content-area",
            "required": true,
            "schema": {
              "enum": [
                "math",
                "ela"
              ],
              "type": "string"
            }
          },
}

The generated enumeration looks OK, complete with ToJSON,

data
  Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_area
  = -- | This case is used if the value encountered during decoding does not match any of the provided cases in the specification.
    Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaOther
      Data.Aeson.Types.Internal.Value
  | -- | This constructor can be used to send values to the server which are not present in the specification yet.
    Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaTyped
      Data.Text.Internal.Text
  | -- | Represents the JSON value @"math"@
    Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaEnumMath
  | -- | Represents the JSON value @"ela"@
    Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaEnumEla
  deriving (GHC.Show.Show, GHC.Classes.Eq)

instance
  Data.Aeson.Types.ToJSON.ToJSON
    Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_area
  where
  toJSON ( Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaOther
            val
          ) = val
  toJSON ( Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaTyped
            val
          ) = Data.Aeson.Types.ToJSON.toJSON val
  toJSON ( Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaEnumMath
          ) = "math"
  toJSON ( Get__content_area__standard_sets__id___lang__standards'jsonParametersPathContent_areaEnumEla
          ) = "ela"

But when I make a request, I see the following path is used:

path = \"/Get__content_area__standard_sets__id___lang__standards%27jsonParametersPathContent_areaEnumMath/standard-sets/FR-CCSS-1/Get__content_area__standard_sets__id___lang__standards%27jsonParametersPathLangTyped%20%22es%22/standards.json\"

Looking in the code, it's meant to use StringifyModel, but there is no instance for any models anywhere, meaning (I think) it's using the default Show a-based instance, which is why it's producing a path like this. It seems to happen everywhere making the client pretty useless.

Am I missing some way to make this work?


I'm currently using a un-released version, since the released version does not work with aeson-2,

  - github: Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator
    commit: 027a1dd85182e5d7b40e1d79e363ff5819d7487c
    subdirs:
      - openapi3-code-generator
pbrisbin commented 5 months ago

FWIW, changing StringifyModel to assume (a string-like) ToJSON rather than Show makes it work for me,

class Aeson.ToJSON a => StringifyModel a where
  -- | Stringifies a showable value
  --
  -- >>> stringifyModel "Test"
  -- "Test"
  --
  -- >>> stringifyModel 123
  -- "123"
  stringifyModel :: a -> Text

-- instance StringifyModel String where
--   -- stringifyModel :: String -> String
--   stringifyModel = T.pack

-- instance StringifyModel Text where
--   -- stringifyModel :: Text -> String
--   stringifyModel = id

instance {-# OVERLAPS #-} Aeson.ToJSON a => StringifyModel a where
  -- stringifyModel :: Show a => a -> String
  stringifyModel x = case Aeson.toJSON x of
    Aeson.String t -> t
    v -> error $ "Value did not encode to a JSON string: " <> show v

Relying on any super-class and an overlaps instance feels kind of sketch, but if you're going to do it, I think ToJSON does make more sense than Show for this -- at least, it's far more likely to do the right thing for any given type, IMO.

joel-bach commented 5 months ago

Hey @pbrisbin Thank you for the report, this is a good catch! I have implemented a fix based on your suggestion here: https://github.com/Haskell-OpenAPI-Code-Generator/Haskell-OpenAPI-Client-Code-Generator/pull/100 I'll close the issue but let me know if it does not work for your 🙏

pbrisbin commented 5 months ago

Thanks for the quick fix! I'll be able to test it out later this week.