purescript-bridge
purescript-bridge copied to clipboard
Allow modifying record labels
This PR allows end users to provide options to the way that type representations are constructed. This allows users to hook into the generic machinery and make the types match some alternate representation (say formatting based on aeson encoding rather than haskell representation).
Here is an example to Illustrate:
Here is my haskell type
type LoginRequestJsonOpts = '[OmitNothingFields, FieldLabelModifier '[StripPrefix "loginRequest", ToCamel]]
data LoginRequest = LoginRequest
{ loginRequestEmail :: Email
, loginRequestPassword :: Text
}
deriving stock (Generic)
deriving (FromJSON) via (CustomJSON LoginRequestJsonOpts LoginRequest)
deriving (ToJSON) via (CustomJSON LoginRequestJsonOpts LoginRequest)
I can build my bridge like so:
withRecordModifier :: forall a. AesonOptions (a :: [Type]) => DataConstructorOpts
withRecordModifier = defaultDataConstructorOpts & recLabelModifier .~ fieldLabelModifier (aesonOptions @a)
apiTypeContract :: [SumType 'Haskell]
apiTypeContract =
[ mkSumType $ Proxy @UserId
, mkSumTypeWith (withRecordModifier @LoginRequestJsonOpts) (Proxy @LoginRequest)
, mkSumTypeWith (withRecordModifier @LoginResponseJsonOpts) $ Proxy @LoginResponse
]
and this will output purescript like this:
module Generated.Api.Login where
import Data.Argonaut.Aeson.Decode.Generic (genericDecodeAeson)
import Data.Argonaut.Aeson.Encode.Generic (genericEncodeAeson)
import Data.Argonaut.Aeson.Options as Argonaut
import Data.Argonaut.Decode.Class (class DecodeJson, class DecodeJsonField, decodeJson)
import Data.Argonaut.Encode.Class (class EncodeJson, encodeJson)
import Data.Email (Email)
import Data.Generic.Rep (class Generic)
import Data.Maybe (Maybe(..))
import Data.Newtype (class Newtype)
import Generated.Tables.Users (UserId)
import Prim (String)
import Prelude
newtype LoginRequest =
LoginRequest {
email :: Email
, password :: String
}
instance encodeJsonLoginRequest :: EncodeJson LoginRequest where
encodeJson = genericEncodeAeson Argonaut.defaultOptions
instance decodeJsonLoginRequest :: DecodeJson LoginRequest where
decodeJson = genericDecodeAeson Argonaut.defaultOptions
derive instance genericLoginRequest :: Generic LoginRequest _
derive instance newtypeLoginRequest :: Newtype LoginRequest _
newtype LoginResponse =
LoginResponse {
email :: Email
, userId :: UserId
, preferredName :: String
}
instance encodeJsonLoginResponse :: EncodeJson LoginResponse where
encodeJson = genericEncodeAeson Argonaut.defaultOptions
instance decodeJsonLoginResponse :: DecodeJson LoginResponse where
decodeJson = genericDecodeAeson Argonaut.defaultOptions
derive instance genericLoginResponse :: Generic LoginResponse _
derive instance newtypeLoginResponse :: Newtype LoginResponse _
This way my Haskell types can be the source of truth, but by modifying my aeson instances I can modify my purescript to be more idiomatic (no record field prefixes), and the JSON serialization / de-serialization stays in sync.