packages feed

autodocodec 0.2.3.0 → 0.3.0.0

raw patch · 6 files changed

+232/−87 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Functor.Const.Const a b)
+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Monoid.First a)
+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Monoid.Last a)
+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Semigroup.First a)
+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Semigroup.Internal.Dual a)
+ Autodocodec.Class: instance Autodocodec.Class.HasCodec a => Autodocodec.Class.HasCodec (Data.Semigroup.Last a)
+ Autodocodec.Codec: BitSInt :: !Word8 -> NumberBoundsSymbolic
+ Autodocodec.Codec: BitUInt :: !Word8 -> NumberBoundsSymbolic
+ Autodocodec.Codec: MinusPowerOf2 :: !Word8 -> ScientificSymbolic
+ Autodocodec.Codec: MinusPowerOf2MinusOne :: !Word8 -> ScientificSymbolic
+ Autodocodec.Codec: OtherDouble :: !Double -> ScientificSymbolic
+ Autodocodec.Codec: OtherInteger :: !Integer -> ScientificSymbolic
+ Autodocodec.Codec: OtherNumberBounds :: !ScientificSymbolic -> !ScientificSymbolic -> NumberBoundsSymbolic
+ Autodocodec.Codec: PowerOf2 :: !Word8 -> ScientificSymbolic
+ Autodocodec.Codec: PowerOf2MinusOne :: !Word8 -> ScientificSymbolic
+ Autodocodec.Codec: Result :: Either String a -> Result a
+ Autodocodec.Codec: Zero :: ScientificSymbolic
+ Autodocodec.Codec: [runResult] :: Result a -> Either String a
+ Autodocodec.Codec: data NumberBoundsSymbolic
+ Autodocodec.Codec: data ScientificSymbolic
+ Autodocodec.Codec: guessNumberBoundsSymbolic :: NumberBounds -> NumberBoundsSymbolic
+ Autodocodec.Codec: guessScientificSymbolic :: Scientific -> ScientificSymbolic
+ Autodocodec.Codec: instance Control.Monad.Fail.MonadFail Autodocodec.Codec.Result
+ Autodocodec.Codec: instance GHC.Base.Applicative Autodocodec.Codec.Result
+ Autodocodec.Codec: instance GHC.Base.Functor Autodocodec.Codec.Result
+ Autodocodec.Codec: instance GHC.Base.Monad Autodocodec.Codec.Result
+ Autodocodec.Codec: newtype Result a
+ Autodocodec.Codec: optionalKeyCodec :: Text -> ValueCodec input output -> Maybe Text -> ObjectCodec (Maybe input) (Maybe output)
+ Autodocodec.Codec: optionalKeyWithDefaultCodec :: Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value value
+ Autodocodec.Codec: unsafeCodecViaAesonString :: (FromJSON a, ToJSON a) => Text -> JSONCodec a
- Autodocodec: [ArrayOfCodec] :: Maybe Text -> ValueCodec input output -> ValueCodec (Vector input) (Vector output)
+ Autodocodec: [ArrayOfCodec] :: (Coercible a (Vector input), Coercible b (Vector output)) => Maybe Text -> ValueCodec input output -> ValueCodec a b
- Autodocodec: [BoolCodec] :: Maybe Text -> JSONCodec Bool
+ Autodocodec: [BoolCodec] :: (Coercible a Bool, Coercible b Bool) => Maybe Text -> ValueCodec a b
- Autodocodec: [EitherCodec] :: !Union -> Codec context input1 output1 -> Codec context input2 output2 -> Codec context (Either input1 input2) (Either output1 output2)
+ Autodocodec: [EitherCodec] :: (Coercible a (Either input1 input2), Coercible b (Either output1 output2)) => !Union -> Codec context input1 output1 -> Codec context input2 output2 -> Codec context a b
- Autodocodec: [EqCodec] :: (Show value, Eq value) => value -> JSONCodec value -> JSONCodec value
+ Autodocodec: [EqCodec] :: (Show value, Eq value, Coercible a value, Coercible b value) => value -> JSONCodec value -> ValueCodec a b
- Autodocodec: [HashMapCodec] :: (Eq k, Hashable k, FromJSONKey k, ToJSONKey k) => JSONCodec v -> JSONCodec (HashMap k v)
+ Autodocodec: [HashMapCodec] :: (Eq k, Hashable k, FromJSONKey k, ToJSONKey k, Coercible a (HashMap k v), Coercible b (HashMap k v)) => JSONCodec v -> ValueCodec a b
- Autodocodec: [MapCodec] :: (Ord k, FromJSONKey k, ToJSONKey k) => JSONCodec v -> JSONCodec (Map k v)
+ Autodocodec: [MapCodec] :: (Ord k, FromJSONKey k, ToJSONKey k, Coercible a (Map k v), Coercible b (Map k v)) => JSONCodec v -> ValueCodec a b
- Autodocodec: [NullCodec] :: ValueCodec () ()
+ Autodocodec: [NullCodec] :: (Coercible a (), Coercible b ()) => ValueCodec a b
- Autodocodec: [NumberCodec] :: Maybe Text -> Maybe NumberBounds -> JSONCodec Scientific
+ Autodocodec: [NumberCodec] :: (Coercible a Scientific, Coercible b Scientific) => Maybe Text -> Maybe NumberBounds -> ValueCodec a b
- Autodocodec: [OptionalKeyCodec] :: Text -> ValueCodec input output -> Maybe Text -> ObjectCodec (Maybe input) (Maybe output)
+ Autodocodec: [OptionalKeyCodec] :: (Coercible a (Maybe input), Coercible b (Maybe output)) => Text -> ValueCodec input output -> Maybe Text -> ObjectCodec a b
- Autodocodec: [OptionalKeyWithDefaultCodec] :: Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value value
+ Autodocodec: [OptionalKeyWithDefaultCodec] :: Coercible b value => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value b
- Autodocodec: [OptionalKeyWithOmittedDefaultCodec] :: Eq value => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value value
+ Autodocodec: [OptionalKeyWithOmittedDefaultCodec] :: (Eq value, Coercible a value, Coercible b value) => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec a b
- Autodocodec: [StringCodec] :: Maybe Text -> JSONCodec Text
+ Autodocodec: [StringCodec] :: (Coercible a Text, Coercible b Text) => Maybe Text -> ValueCodec a b
- Autodocodec: [ValueCodec] :: JSONCodec Value
+ Autodocodec: [ValueCodec] :: (Coercible Value a, Coercible Value b) => ValueCodec a b
- Autodocodec.Codec: [ArrayOfCodec] :: Maybe Text -> ValueCodec input output -> ValueCodec (Vector input) (Vector output)
+ Autodocodec.Codec: [ArrayOfCodec] :: (Coercible a (Vector input), Coercible b (Vector output)) => Maybe Text -> ValueCodec input output -> ValueCodec a b
- Autodocodec.Codec: [BoolCodec] :: Maybe Text -> JSONCodec Bool
+ Autodocodec.Codec: [BoolCodec] :: (Coercible a Bool, Coercible b Bool) => Maybe Text -> ValueCodec a b
- Autodocodec.Codec: [EitherCodec] :: !Union -> Codec context input1 output1 -> Codec context input2 output2 -> Codec context (Either input1 input2) (Either output1 output2)
+ Autodocodec.Codec: [EitherCodec] :: (Coercible a (Either input1 input2), Coercible b (Either output1 output2)) => !Union -> Codec context input1 output1 -> Codec context input2 output2 -> Codec context a b
- Autodocodec.Codec: [EqCodec] :: (Show value, Eq value) => value -> JSONCodec value -> JSONCodec value
+ Autodocodec.Codec: [EqCodec] :: (Show value, Eq value, Coercible a value, Coercible b value) => value -> JSONCodec value -> ValueCodec a b
- Autodocodec.Codec: [HashMapCodec] :: (Eq k, Hashable k, FromJSONKey k, ToJSONKey k) => JSONCodec v -> JSONCodec (HashMap k v)
+ Autodocodec.Codec: [HashMapCodec] :: (Eq k, Hashable k, FromJSONKey k, ToJSONKey k, Coercible a (HashMap k v), Coercible b (HashMap k v)) => JSONCodec v -> ValueCodec a b
- Autodocodec.Codec: [MapCodec] :: (Ord k, FromJSONKey k, ToJSONKey k) => JSONCodec v -> JSONCodec (Map k v)
+ Autodocodec.Codec: [MapCodec] :: (Ord k, FromJSONKey k, ToJSONKey k, Coercible a (Map k v), Coercible b (Map k v)) => JSONCodec v -> ValueCodec a b
- Autodocodec.Codec: [NullCodec] :: ValueCodec () ()
+ Autodocodec.Codec: [NullCodec] :: (Coercible a (), Coercible b ()) => ValueCodec a b
- Autodocodec.Codec: [NumberCodec] :: Maybe Text -> Maybe NumberBounds -> JSONCodec Scientific
+ Autodocodec.Codec: [NumberCodec] :: (Coercible a Scientific, Coercible b Scientific) => Maybe Text -> Maybe NumberBounds -> ValueCodec a b
- Autodocodec.Codec: [OptionalKeyCodec] :: Text -> ValueCodec input output -> Maybe Text -> ObjectCodec (Maybe input) (Maybe output)
+ Autodocodec.Codec: [OptionalKeyCodec] :: (Coercible a (Maybe input), Coercible b (Maybe output)) => Text -> ValueCodec input output -> Maybe Text -> ObjectCodec a b
- Autodocodec.Codec: [OptionalKeyWithDefaultCodec] :: Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value value
+ Autodocodec.Codec: [OptionalKeyWithDefaultCodec] :: Coercible b value => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value b
- Autodocodec.Codec: [OptionalKeyWithOmittedDefaultCodec] :: Eq value => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec value value
+ Autodocodec.Codec: [OptionalKeyWithOmittedDefaultCodec] :: (Eq value, Coercible a value, Coercible b value) => Text -> ValueCodec value value -> value -> Maybe Text -> ObjectCodec a b
- Autodocodec.Codec: [StringCodec] :: Maybe Text -> JSONCodec Text
+ Autodocodec.Codec: [StringCodec] :: (Coercible a Text, Coercible b Text) => Maybe Text -> ValueCodec a b
- Autodocodec.Codec: [ValueCodec] :: JSONCodec Value
+ Autodocodec.Codec: [ValueCodec] :: (Coercible Value a, Coercible Value b) => ValueCodec a b

Files

CHANGELOG.md view
@@ -1,5 +1,25 @@ # Changelog +## [0.3.0.0] - 2024-07-19++### Added++* `HasCodec` instances for:+    * `Const`+    * `Dual`+    * `Semigroup.First`+    * `Semigroup.Last`+    * `Monoid.First`+    * `Monoid.Last`+++### Changed++* Refactored `Codec` so it's input and output parameters have a representative, not nominal role.+  This means one can now use `deriving newtype` with the `HasCodec` class.+* Fixed infinitely looping `Identity` instance+* Improve the documentation of `time`-related codecs to show `<string>` instead of `<any>`.+ ## [0.2.3.0] - 2024-06-23  ### Added
autodocodec.cabal view
@@ -5,13 +5,13 @@ -- see: https://github.com/sol/hpack  name:           autodocodec-version:        0.2.3.0+version:        0.3.0.0 synopsis:       Self-documenting encoder and decoder homepage:       https://github.com/NorfairKing/autodocodec#readme bug-reports:    https://github.com/NorfairKing/autodocodec/issues author:         Tom Sydney Kerckhove maintainer:     syd@cs-syd.eu-copyright:      2021-2023 Tom Sydney Kerckhove+copyright:      2021-2024 Tom Sydney Kerckhove license:        MIT license-file:   LICENSE build-type:     Simple
src/Autodocodec/Aeson/Decode.hs view
@@ -25,9 +25,11 @@ import Control.Monad import Data.Aeson as JSON import Data.Aeson.Types as JSON+import Data.Coerce (coerce) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap import Data.Map (Map)+import Data.Text (Text) import qualified Data.Text as T import Data.Vector (Vector) import qualified Data.Vector as V@@ -59,34 +61,36 @@     go :: context -> Codec context void a -> JSON.Parser a     go value = \case       NullCodec -> case (value :: JSON.Value) of-        Null -> pure ()+        Null -> coerce (pure () :: JSON.Parser ())         _ -> typeMismatch "Null" value       BoolCodec mname -> case mname of-        Nothing -> parseJSON value-        Just name -> withBool (T.unpack name) pure value+        Nothing -> coerce (parseJSON value :: JSON.Parser Bool)+        Just name -> coerce $ withBool (T.unpack name) pure value       StringCodec mname -> case mname of-        Nothing -> parseJSON value-        Just name -> withText (T.unpack name) pure value+        Nothing -> coerce (parseJSON value :: JSON.Parser Text)+        Just name -> coerce $ withText (T.unpack name) pure value       NumberCodec mname mBounds ->-        ( \f -> case mname of-            Nothing -> parseJSON value >>= f-            Just name -> withScientific (T.unpack name) f value-        )-          ( \s -> case maybe Right checkNumberBounds mBounds s of-              Left err -> fail err-              Right s' -> pure s'+        coerce $+          ( \f -> case mname of+              Nothing -> parseJSON value >>= f+              Just name -> withScientific (T.unpack name) f value           )+            ( \s -> case maybe Right checkNumberBounds mBounds s of+                Left err -> fail err+                Right s' -> pure s'+            )       ArrayOfCodec mname c ->         ( \f -> case mname of             Nothing -> parseJSON value >>= f             Just name -> withArray (T.unpack name) f value         )           ( \vector ->-              forM-                (V.indexed (vector :: Vector JSON.Value))-                ( \(ix, v) ->-                    go v c JSON.<?> Index ix-                )+              coerce $+                forM+                  (V.indexed (vector :: Vector JSON.Value))+                  ( \(ix, v) ->+                      go v c JSON.<?> Index ix+                  )           )       ObjectOfCodec mname c ->         ( \f -> case mname of@@ -94,13 +98,13 @@             Just name -> withObject (T.unpack name) f value         )           (\object_ -> (`go` c) (object_ :: JSON.Object))-      HashMapCodec c -> Compat.liftParseJSON (`go` c) (`go` listCodec c) value :: JSON.Parser (HashMap _ _)-      MapCodec c -> Compat.liftParseJSON (`go` c) (`go` listCodec c) value :: JSON.Parser (Map _ _)-      ValueCodec -> pure (value :: JSON.Value)+      HashMapCodec c -> coerce (Compat.liftParseJSON (`go` c) (`go` listCodec c) value :: JSON.Parser (HashMap _ _))+      MapCodec c -> coerce (Compat.liftParseJSON (`go` c) (`go` listCodec c) value :: JSON.Parser (Map _ _))+      ValueCodec -> pure $ coerce value       EqCodec expected c -> do         actual <- go value c         if expected == actual-          then pure actual+          then pure (coerce actual)           else fail $ unwords ["Expected", show expected, "but got", show actual]       BimapCodec f _ c -> do         old <- go value c@@ -110,7 +114,7 @@       EitherCodec u c1 c2 ->         let leftParser v = Left <$> go v c1             rightParser v = Right <$> go v c2-         in case u of+         in coerce $ case u of               PossiblyJointUnion ->                 case parseEither leftParser value of                   Right l -> pure l@@ -141,11 +145,11 @@       OptionalKeyCodec k c _ -> do         let key = Compat.toKey k             mValueAtKey = Compat.lookupKey key (value :: JSON.Object)-        forM mValueAtKey $ \valueAtKey -> go (valueAtKey :: JSON.Value) c JSON.<?> Key key+        coerce $ forM mValueAtKey $ \valueAtKey -> go (valueAtKey :: JSON.Value) c JSON.<?> Key key       OptionalKeyWithDefaultCodec k c defaultValue _ -> do         let key = Compat.toKey k             mValueAtKey = Compat.lookupKey key (value :: JSON.Object)-        case mValueAtKey of+        coerce $ case mValueAtKey of           Nothing -> pure defaultValue           Just valueAtKey -> go (valueAtKey :: JSON.Value) c JSON.<?> Key key       OptionalKeyWithOmittedDefaultCodec k c defaultValue mDoc -> go value $ OptionalKeyWithDefaultCodec k c defaultValue mDoc
src/Autodocodec/Aeson/Encode.hs view
@@ -25,6 +25,7 @@ import Data.Aeson (toJSON) import qualified Data.Aeson as JSON import qualified Data.Aeson.Encoding as JSON+import Data.Coerce (coerce) import Data.HashMap.Strict (HashMap) import Data.Map (Map) import Data.Scientific@@ -45,17 +46,17 @@     go :: a -> ObjectCodec a void -> JSON.Object     go a = \case       RequiredKeyCodec k c _ -> Compat.toKey k JSON..= toJSONVia c a-      OptionalKeyCodec k c _ -> case (a :: Maybe _) of+      OptionalKeyCodec k c _ -> case (coerce a :: Maybe _) of         Nothing -> mempty         Just b -> Compat.toKey k JSON..= toJSONVia c b-      OptionalKeyWithDefaultCodec k c _ mdoc -> go (Just a) (OptionalKeyCodec k c mdoc)+      OptionalKeyWithDefaultCodec k c _ mdoc -> go (Just a) (optionalKeyCodec k c mdoc)       OptionalKeyWithOmittedDefaultCodec k c defaultValue mdoc ->-        if a == defaultValue+        if coerce a == defaultValue           then mempty-          else go a (OptionalKeyWithDefaultCodec k c defaultValue mdoc)+          else go a (optionalKeyWithDefaultCodec k (coerce c) (coerce defaultValue) mdoc)       BimapCodec _ g c -> go (g a) c       PureCodec _ -> mempty-      EitherCodec _ c1 c2 -> case (a :: Either _ _) of+      EitherCodec _ c1 c2 -> case (coerce a :: Either _ _) of         Left a1 -> go a1 c1         Right a2 -> go a2 c2       DiscriminatedUnionCodec propertyName mapping _ ->@@ -73,17 +74,17 @@     go :: a -> ValueCodec a void -> JSON.Value     go a = \case       NullCodec -> JSON.Null-      BoolCodec _ -> toJSON (a :: Bool)-      StringCodec _ -> toJSON (a :: Text)-      NumberCodec _ _ -> toJSON (a :: Scientific)-      ArrayOfCodec _ c -> toJSON (fmap (`go` c) (a :: Vector _))+      BoolCodec _ -> toJSON (coerce a :: Bool)+      StringCodec _ -> toJSON (coerce a :: Text)+      NumberCodec _ _ -> toJSON (coerce a :: Scientific)+      ArrayOfCodec _ c -> toJSON (fmap (`go` c) (coerce a :: Vector _))       ObjectOfCodec _ oc -> JSON.Object (toJSONObjectVia oc a)-      HashMapCodec c -> Compat.liftToJSON (`go` c) (`go` listCodec c) (a :: HashMap _ _)-      MapCodec c -> Compat.liftToJSON (`go` c) (`go` listCodec c) (a :: Map _ _)-      ValueCodec -> (a :: JSON.Value)+      HashMapCodec c -> Compat.liftToJSON (`go` c) (`go` listCodec c) (coerce a :: HashMap _ _)+      MapCodec c -> Compat.liftToJSON (`go` c) (`go` listCodec c) (coerce a :: Map _ _)+      ValueCodec -> (coerce a :: JSON.Value)       EqCodec value c -> go value c       BimapCodec _ g c -> go (g a) c-      EitherCodec _ c1 c2 -> case (a :: Either _ _) of+      EitherCodec _ c1 c2 -> case (coerce a :: Either _ _) of         Left a1 -> go a1 c1         Right a2 -> go a2 c2       CommentCodec _ c -> go a c@@ -102,17 +103,17 @@     goObject :: a -> ObjectCodec a void -> JSON.Series     goObject a = \case       RequiredKeyCodec k c _ -> JSON.pair (Compat.toKey k) (toEncodingVia c a)-      OptionalKeyCodec k c _ -> case (a :: Maybe _) of+      OptionalKeyCodec k c _ -> case (coerce a :: Maybe _) of         Nothing -> mempty :: JSON.Series         Just b -> JSON.pair (Compat.toKey k) (toEncodingVia c b)-      OptionalKeyWithDefaultCodec k c _ mdoc -> goObject (Just a) (OptionalKeyCodec k c mdoc)+      OptionalKeyWithDefaultCodec k c _ mdoc -> goObject (Just a) (optionalKeyCodec k c mdoc)       OptionalKeyWithOmittedDefaultCodec k c defaultValue mdoc ->-        if a == defaultValue+        if coerce a == defaultValue           then mempty-          else goObject a (OptionalKeyWithDefaultCodec k c defaultValue mdoc)+          else goObject a (optionalKeyWithDefaultCodec k (coerce c) (coerce defaultValue) mdoc)       PureCodec _ -> mempty :: JSON.Series       BimapCodec _ g c -> goObject (g a) c-      EitherCodec _ c1 c2 -> case (a :: Either _ _) of+      EitherCodec _ c1 c2 -> case (coerce a :: Either _ _) of         Left a1 -> goObject a1 c1         Right a2 -> goObject a2 c2       DiscriminatedUnionCodec propertyName mapping _ ->@@ -128,17 +129,17 @@     go :: a -> ValueCodec a void -> JSON.Encoding     go a = \case       NullCodec -> JSON.null_-      BoolCodec _ -> JSON.bool (a :: Bool)-      StringCodec _ -> JSON.text (a :: Text)-      NumberCodec _ _ -> JSON.scientific (a :: Scientific)-      ArrayOfCodec _ c -> JSON.list (`go` c) (V.toList (a :: Vector _))+      BoolCodec _ -> JSON.bool (coerce a :: Bool)+      StringCodec _ -> JSON.text (coerce a :: Text)+      NumberCodec _ _ -> JSON.scientific (coerce a :: Scientific)+      ArrayOfCodec _ c -> JSON.list (`go` c) (V.toList (coerce a :: Vector _))       ObjectOfCodec _ oc -> JSON.pairs (toSeriesVia oc a)-      HashMapCodec c -> Compat.liftToEncoding (`go` c) (`go` listCodec c) (a :: HashMap _ _)-      MapCodec c -> Compat.liftToEncoding (`go` c) (`go` listCodec c) (a :: Map _ _)-      ValueCodec -> JSON.value (a :: JSON.Value)+      HashMapCodec c -> Compat.liftToEncoding (`go` c) (`go` listCodec c) (coerce a :: HashMap _ _)+      MapCodec c -> Compat.liftToEncoding (`go` c) (`go` listCodec c) (coerce a :: Map _ _)+      ValueCodec -> JSON.value (coerce a :: JSON.Value)       EqCodec value c -> go value c       BimapCodec _ g c -> go (g a) c-      EitherCodec _ c1 c2 -> case (a :: Either _ _) of+      EitherCodec _ c1 c2 -> case (coerce a :: Either _ _) of         Left a1 -> go a1 c1         Right a2 -> go a2 c2       CommentCodec _ c -> go a c
src/Autodocodec/Class.hs view
@@ -1,9 +1,12 @@ {-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-} -- Because Eq is a superclass of Hashable in newer versions. {-# OPTIONS_GHC -Wno-redundant-constraints #-} @@ -16,13 +19,17 @@ #if MIN_VERSION_aeson(2,0,0) import Data.Aeson.KeyMap (KeyMap) #endif+import Data.Functor.Const (Const (Const)) import Data.Functor.Identity import Data.HashMap.Strict (HashMap) import Data.Hashable (Hashable) import Data.Int import Data.List.NonEmpty (NonEmpty (..)) import Data.Map (Map)+import qualified Data.Monoid as Monoid import Data.Scientific+import Data.Semigroup (Dual (Dual))+import qualified Data.Semigroup as Semigroup import Data.Set (Set) import qualified Data.Set as S import Data.Text (Text)@@ -52,7 +59,7 @@   {-# MINIMAL codec #-}  instance HasCodec Void where-  codec = bimapCodec (\_ -> Left "Cannot decode a Void.") absurd ValueCodec+  codec = bimapCodec (\_ -> Left "Cannot decode a Void.") absurd valueCodec  instance HasCodec Bool where   codec = boolCodec@@ -117,11 +124,22 @@   codec = naturalCodec  instance HasCodec JSON.Value where-  codec = ValueCodec+  codec = valueCodec -instance (HasCodec a) => HasCodec (Identity a) where-  codec = dimapCodec runIdentity Identity codec+deriving newtype instance (HasCodec a) => HasCodec (Identity a) +deriving newtype instance (HasCodec a) => HasCodec (Dual a)++deriving newtype instance (HasCodec a) => HasCodec (Semigroup.First a)++deriving newtype instance (HasCodec a) => HasCodec (Semigroup.Last a)++deriving newtype instance (HasCodec a) => HasCodec (Monoid.First a)++deriving newtype instance (HasCodec a) => HasCodec (Monoid.Last a)++deriving newtype instance (HasCodec a) => HasCodec (Const a b)+ instance (HasCodec a) => HasCodec (Maybe a) where   codec = maybeCodec codec @@ -144,31 +162,30 @@   codec = dimapCodec S.fromList S.toList codec  instance (Ord k, FromJSONKey k, ToJSONKey k, HasCodec v) => HasCodec (Map k v) where-  codec = MapCodec codec+  codec = mapCodec codec  instance (Eq k, Hashable k, FromJSONKey k, ToJSONKey k, HasCodec v) => HasCodec (HashMap k v) where-  codec = HashMapCodec codec+  codec = hashMapCodec codec  #if MIN_VERSION_aeson(2,0,0) instance HasCodec v => HasCodec (KeyMap v) where   codec = keyMapCodec codec #endif --- TODO make these instances better once aeson exposes its @Data.Aeson.Parser.Time@ or @Data.Attoparsec.Time@ modules. instance HasCodec Day where-  codec = codecViaAeson "Day"+  codec = unsafeCodecViaAesonString "Day"  instance HasCodec LocalTime where-  codec = codecViaAeson "LocalTime"+  codec = unsafeCodecViaAesonString "LocalTime"  instance HasCodec UTCTime where-  codec = codecViaAeson "LocalTime"+  codec = unsafeCodecViaAesonString "UTCTime"  instance HasCodec TimeOfDay where-  codec = codecViaAeson "TimeOfDay"+  codec = unsafeCodecViaAesonString "TimeOfDay"  instance HasCodec ZonedTime where-  codec = codecViaAeson "ZonedTime"+  codec = unsafeCodecViaAesonString "ZonedTime"  instance HasCodec NominalDiffTime where   codec = dimapCodec realToFrac realToFrac (codec :: JSONCodec Scientific)@@ -276,7 +293,7 @@   -- | Documentation   Text ->   ObjectCodec (Maybe output) (Maybe output)-optionalFieldOrNull key doc = orNullHelper $ OptionalKeyCodec key (maybeCodec codec) (Just doc)+optionalFieldOrNull key doc = orNullHelper $ optionalKeyCodec key (maybeCodec codec) (Just doc)  -- | Like 'optionalFieldOrNull', but without documentation optionalFieldOrNull' ::@@ -285,7 +302,7 @@   -- | Key   Text ->   ObjectCodec (Maybe output) (Maybe output)-optionalFieldOrNull' key = orNullHelper $ OptionalKeyCodec key (maybeCodec codec) Nothing+optionalFieldOrNull' key = orNullHelper $ optionalKeyCodec key (maybeCodec codec) Nothing  optionalFieldWithOmittedDefault ::   (Eq output, HasCodec output) =>
src/Autodocodec/Codec.hs view
@@ -1,9 +1,13 @@ {-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiWayIf #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE RoleAnnotations #-}@@ -19,11 +23,8 @@ import Control.Monad.State import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey) import qualified Data.Aeson as JSON-#if MIN_VERSION_aeson(2,0,0)-import Data.Aeson.KeyMap (KeyMap)-import qualified Data.Aeson.KeyMap as KM-#endif import qualified Data.Aeson.Types as JSON+import Data.Coerce (Coercible) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap import Data.Hashable@@ -41,8 +42,13 @@ import Data.Vector (Vector) import qualified Data.Vector as V import Data.Void+import Data.Word import GHC.Generics (Generic) import Numeric.Natural+#if MIN_VERSION_aeson(2,0,0)+import Data.Aeson.KeyMap (KeyMap)+import qualified Data.Aeson.KeyMap as KM+#endif  -- $setup -- >>> import Autodocodec.Aeson (toJSONVia, toJSONViaCodec, toJSONObjectVia, toJSONObjectViaCodec, parseJSONVia, parseJSONViaCodec, parseJSONObjectVia, parseJSONObjectViaCodec)@@ -71,22 +77,27 @@ -- * The @input@ parameter is used for the type that is used during encoding of a value, so it's the @input@ to the codec. -- * The @output@ parameter is used for the type that is used during decoding of a value, so it's the @output@ of the codec. -- * Both parameters are unused during documentation.+type role Codec _ representational representational+ data Codec context input output where   -- | Encode '()' to the @null@ value, and decode @null@ as '()'.   NullCodec ::-    ValueCodec () ()+    (Coercible a (), Coercible b ()) =>+    ValueCodec a b   -- | Encode a 'Bool' to a @boolean@ value, and decode a @boolean@ value as a 'Bool'.   BoolCodec ::+    (Coercible a Bool, Coercible b Bool) =>     -- | Name of the @bool@, for error messages and documentation.     Maybe Text ->-    JSONCodec Bool+    ValueCodec a b   -- | Encode 'Text' to a @string@ value, and decode a @string@ value as a 'Text'.   --   -- This is named after the primitive type "String" in json, not after the haskell type string.   StringCodec ::+    (Coercible a Text, Coercible b Text) =>     -- | Name of the @string@, for error messages and documentation.     Maybe Text ->-    JSONCodec Text+    ValueCodec a b   -- | Encode 'Scientific' to a @number@ value, and decode a @number@ value as a 'Scientific'.   --   -- The number has optional 'NumberBounds'.@@ -94,30 +105,33 @@   --   -- NOTE: We use 'Scientific' here because that is what aeson uses.   NumberCodec ::+    (Coercible a Scientific, Coercible b Scientific) =>     -- | Name of the @number@, for error messages and documentation.     Maybe Text ->     -- | Bounds for the number, these are checked and documented     Maybe NumberBounds ->-    JSONCodec Scientific+    ValueCodec a b   -- | Encode a 'HashMap', and decode any 'HashMap'.   HashMapCodec ::-    (Eq k, Hashable k, FromJSONKey k, ToJSONKey k) =>+    (Eq k, Hashable k, FromJSONKey k, ToJSONKey k, Coercible a (HashMap k v), Coercible b (HashMap k v)) =>     JSONCodec v ->-    JSONCodec (HashMap k v)+    ValueCodec a b   -- | Encode a 'Map', and decode any 'Map'.   MapCodec ::-    (Ord k, FromJSONKey k, ToJSONKey k) =>+    (Ord k, FromJSONKey k, ToJSONKey k, Coercible a (Map k v), Coercible b (Map k v)) =>     JSONCodec v ->-    JSONCodec (Map k v)+    ValueCodec a b   -- | Encode a 'JSON.Value', and decode any 'JSON.Value'.   ValueCodec ::-    JSONCodec JSON.Value+    (Coercible JSON.Value a, Coercible JSON.Value b) =>+    ValueCodec a b   -- | Encode a 'Vector' of values as an @array@ value, and decode an @array@ value as a 'Vector' of values.   ArrayOfCodec ::+    (Coercible a (Vector input), Coercible b (Vector output)) =>     -- | Name of the @array@, for error messages and documentation.     Maybe Text ->     ValueCodec input output ->-    ValueCodec (Vector input) (Vector output)+    ValueCodec a b   -- | Encode a value as a an @object@ value using the given 'ObjectCodec', and decode an @object@ value as a value using the given 'ObjectCodec'.   ObjectOfCodec ::     -- | Name of the @object@, for error messages and documentation.@@ -126,12 +140,12 @@     ValueCodec input output   -- | Match a given value using its 'Eq' instance during decoding, and encode exactly that value during encoding.   EqCodec ::-    (Show value, Eq value) =>+    (Show value, Eq value, Coercible a value, Coercible b value) =>     -- | Value to match     value ->     -- | Codec for the value     JSONCodec value ->-    JSONCodec value+    ValueCodec a b   -- | Map a codec in both directions.   --   -- This is not strictly dimap, because the decoding function is allowed to fail,@@ -156,13 +170,14 @@   -- In particular, you should prefer using it for values rather than objects,   -- because those docs are easier to generate.   EitherCodec ::+    (Coercible a (Either input1 input2), Coercible b (Either output1 output2)) =>     -- | What type of union we encode and decode     !Union ->     -- | Codec for the 'Left' side     Codec context input1 output1 ->     -- | Codec for the 'Right' side     Codec context input2 output2 ->-    Codec context (Either input1 input2) (Either output1 output2)+    Codec context a b   -- | Encode/decode a discriminated union of objects   --   -- The type of object being encoded/decoded is discriminated by@@ -217,14 +232,16 @@     Maybe Text ->     ObjectCodec input output   OptionalKeyCodec ::+    (Coercible a (Maybe input), Coercible b (Maybe output)) =>     -- | Key     Text ->     -- | Codec for the value     ValueCodec input output ->     -- | Documentation     Maybe Text ->-    ObjectCodec (Maybe input) (Maybe output)+    ObjectCodec a b   OptionalKeyWithDefaultCodec ::+    (Coercible b value) =>     -- | Key     Text ->     -- | Codec for the value@@ -233,9 +250,9 @@     value ->     -- | Documentation     Maybe Text ->-    ObjectCodec value value+    ObjectCodec value b   OptionalKeyWithOmittedDefaultCodec ::-    (Eq value) =>+    (Eq value, Coercible a value, Coercible b value) =>     -- | Key     Text ->     -- | Codec for the value@@ -244,7 +261,7 @@     value ->     -- | Documentation     Maybe Text ->-    ObjectCodec value value+    ObjectCodec a b   -- | To implement 'pure' from 'Applicative'.   --   -- Pure is not available for non-object codecs because there is no 'mempty' for 'JSON.Value', which we would need during encoding.@@ -270,6 +287,18 @@  instance Validity NumberBounds +optionalKeyWithDefaultCodec ::+  -- | Key+  Text ->+  -- | Codec for the value+  ValueCodec value value ->+  -- | Default value+  value ->+  -- | Documentation+  Maybe Text ->+  ObjectCodec value value+optionalKeyWithDefaultCodec = OptionalKeyWithDefaultCodec+ -- | Check if a number falls within given 'NumberBounds'. checkNumberBounds :: NumberBounds -> Scientific -> Either String Scientific checkNumberBounds NumberBounds {..} s =@@ -280,6 +309,43 @@         else Left $ unwords ["Number", show s, "is bigger than the upper bound", show numberBoundsUpper]     else Left $ unwords ["Number", show s, "is smaller than the lower bound", show numberBoundsUpper] +data NumberBoundsSymbolic+  = BitUInt !Word8 -- w bit unsigned int+  | BitSInt !Word8 -- w bit signed int+  | OtherNumberBounds !ScientificSymbolic !ScientificSymbolic++guessNumberBoundsSymbolic :: NumberBounds -> NumberBoundsSymbolic+guessNumberBoundsSymbolic NumberBounds {..} =+  case (guessScientificSymbolic numberBoundsLower, guessScientificSymbolic numberBoundsUpper) of+    (Zero, PowerOf2MinusOne w) -> BitUInt w+    (MinusPowerOf2 w1, PowerOf2MinusOne w2) | w1 == w2 -> BitSInt (succ w1)+    (l, u) -> OtherNumberBounds l u++data ScientificSymbolic+  = Zero+  | PowerOf2 !Word8 -- 2^w+  | PowerOf2MinusOne !Word8 -- 2^w -1+  | MinusPowerOf2 !Word8 -- - 2^w+  | MinusPowerOf2MinusOne !Word8 -- - (2^w -1)+  | OtherInteger !Integer+  | OtherDouble !Double++guessScientificSymbolic :: Scientific -> ScientificSymbolic+guessScientificSymbolic s = case floatingOrInteger s of+  Left d -> OtherDouble d+  Right i ->+    let log2Rounded :: Word8+        log2Rounded = round (logBase 2 (fromInteger (abs i)) :: Double)+        guess :: Integer+        guess = 2 ^ log2Rounded+     in if+          | i == 0 -> Zero+          | guess == i -> PowerOf2 log2Rounded+          | (guess - 1) == i -> PowerOf2MinusOne log2Rounded+          | -guess == i -> MinusPowerOf2 log2Rounded+          | -(guess - 1) == i -> MinusPowerOf2MinusOne log2Rounded+          | otherwise -> OtherInteger i+ -- | What type of union the encoding uses data Union   = -- | Not disjoint, see 'possiblyJointEitherCodec'.@@ -794,6 +860,10 @@ listCodec :: ValueCodec input output -> ValueCodec [input] [output] listCodec = dimapCodec V.toList V.fromList . vectorCodec +-- Some restricted constructors+optionalKeyCodec :: Text -> ValueCodec input output -> Maybe Text -> ObjectCodec (Maybe input) (Maybe output)+optionalKeyCodec = OptionalKeyCodec+ -- | Build a codec for nonempty lists of values from a codec for a single value. -- --@@ -1841,3 +1911,36 @@   Text ->   JSONCodec a codecViaAeson doc = bimapCodec (JSON.parseEither JSON.parseJSON) JSON.toJSON valueCodec <?> doc++-- Could get this from https://hackage.haskell.org/package/either-result-0.3.1.0/docs/Control-Monad-Result.html#t:Result+-- but just reimplementing here to avoid a dependency, as it's not exported anyway+-- (well it is actually, until we give this module an explicit export list).+-- We need to do this because `Either String a` doesn't have a `MonadFail` instance,+-- but `Time.iso8601ParseM` expects it's return value to have a `MonadFail` instance.+newtype Result a = Result {runResult :: Either String a}+  deriving newtype (Functor, Applicative, Monad)++instance MonadFail Result where+  fail = Result . Left++-- TODO 'aeson' has it's own custom datetime serialising code in the module @Data.Aeson.Encoding.Builder@:+-- The core function here is `Data.Aeson.Encoding.Builder.timeOfDay64`.+-- However, this module is private.+-- There is @Data.Aeson.Encoding.Internal@, which interestingly isn't private, but it's only exposed functions+-- wrap the return bytestring in a quotes. Only for the quotes to be removed in `Data.Aeson.Types.ToJSON.stringEncoding`+-- This all seems a bit silly.+-- I think `aeson` should just expose @Data.Aeson.Encoding.Builder@ and it's datetime instances should just take those builders,+-- convert them to Text and be done with it.+-- I plan to submit a PR to 'aeson' to do this.+-- In the meantime, I think the best way to ensure we are exactly behaving as 'aeson' is just to _assume_ aeson is returning a string+-- This is a correct assumption for any of the datetime types, but using this function generally is unsafe.+unsafeCodecViaAesonString ::+  (FromJSON a, ToJSON a) =>+  -- | Name+  Text ->+  JSONCodec a+unsafeCodecViaAesonString doc = bimapCodec (JSON.parseEither JSON.parseJSON . JSON.String) (unsafeAesonValueToString . JSON.toJSON) textCodec <?> doc+  where+    unsafeAesonValueToString v = case v of+      JSON.String s -> s+      _ -> error $ "unsafeAesonValueToString failed.\n " ++ show v ++ "\n is not a JSON string."