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

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

Generate HasField instances for record-dot-preprocessor usage #40

Closed brendanhay closed 3 years ago

brendanhay commented 3 years ago

Note: this is more of a show and tell than a serious attempt at getting something merged. Maybe someone else will find this useful given the changes are easily applied to a generator fork, if desired.

record-dot-preprocessor is something I use pervasively to improve the ergonomics of generated code, as a stop gap until RecordDotSyntax syntax starts shipping with GHC. It improves Haskell's record situation and particularly important for generated code, allows the use of duplicate field names with good inference without polluting your namespace(s) with ambiguous record selectors.

This PR generates HasField instances that are forwards/backwards compatible with RecordDotSyntax and allows you to use the original Stripe API field names which are both considerably briefer than the generated prefixes and also align nicely with the Stripe API documentation.

Code generation:

# No changes to the currently generated data type:
data AccountCapabilities = AccountCapabilities {
  accountCapabilitiesCardIssuing :: (GHC.Maybe.Maybe AccountCapabilitiesCardIssuing'),
  accountCapabilitiesCardPayments :: (GHC.Maybe.Maybe AccountCapabilitiesCardPayments'),
  accountCapabilitiesLegacyPayments :: (GHC.Maybe.Maybe AccountCapabilitiesLegacyPayments'),
  accountCapabilitiesTransfers :: (GHC.Maybe.Maybe AccountCapabilitiesTransfers')
  }

# A HasField instance is generated per record selector, matching the JSON property name:
instance HasField "card_issuing" AccountCapabilities (Maybe AccountCapabilitiesCardIssuing') where
  hasField r = (\a -> r{accountCapabilitiesCardIssuing = a}, accountCapabilitiesCardIssuing r)

...

Usage with record-dot-preprocessor enabled:

let foo = AccountCapabilities{ 
    card_issuing = Nothing, 
    card_payments = Nothing, 
    legacy_payments = Nothing, 
    transfers = Nothing 
  }

let bar = foo.card_issuing
let baz = foo.transfers
...

Impact:

See:

brendanhay commented 3 years ago

A version of stripeapi generated using this PR against the latest 2020-08-27 version of the Stripe API is available here.

brendanhay commented 3 years ago

On master the *Parameter data types for operations have the JSON keys prefixed with the schema name in the {To,From}JSON instances, FWIW. You end up with JSON keys of the form "pathGetSubscriptionIdParametersExpand": ....

NorfairKing commented 3 years ago

Hey @brendanhay! (Big fan of your work, keep it up :)) This project was made by two students for their bachelor thesis. They have since finished it and will probably not spend much time maintaining it. I guess that means that I'm supposed to be maintaining this but frankly I have other things to do a well.

As for this PR: I'd like to wait to start using language features until at least the compiler supports it, if you don't mind..