autodocodec 0.5.0.0 → 0.6.0.0
raw patch · 5 files changed
+106/−11 lines, 5 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Autodocodec.Codec: StringBounds :: !Maybe Natural -> !Maybe Natural -> StringBounds
+ Autodocodec.Codec: [stringBoundsMaxLength] :: StringBounds -> !Maybe Natural
+ Autodocodec.Codec: [stringBoundsMinLength] :: StringBounds -> !Maybe Natural
+ Autodocodec.Codec: checkStringBounds :: StringBounds -> Text -> Either String Text
+ Autodocodec.Codec: data StringBounds
+ Autodocodec.Codec: emptyStringBounds :: StringBounds
+ Autodocodec.Codec: instance Data.Validity.Validity Autodocodec.Codec.StringBounds
+ Autodocodec.Codec: instance GHC.Classes.Eq Autodocodec.Codec.StringBounds
+ Autodocodec.Codec: instance GHC.Classes.Ord Autodocodec.Codec.StringBounds
+ Autodocodec.Codec: instance GHC.Generics.Generic Autodocodec.Codec.StringBounds
+ Autodocodec.Codec: instance GHC.Show.Show Autodocodec.Codec.StringBounds
+ Autodocodec.Codec: stringWithBoundsCodec :: StringBounds -> JSONCodec String
+ Autodocodec.Codec: textWithBoundsCodec :: StringBounds -> JSONCodec Text
- Autodocodec: [StringCodec] :: forall input output. (Coercible input Text, Coercible output Text) => Maybe Text -> Codec Value input output
+ Autodocodec: [StringCodec] :: forall input output. (Coercible input Text, Coercible output Text) => Maybe Text -> StringBounds -> Codec Value input output
- Autodocodec.Codec: [StringCodec] :: forall input output. (Coercible input Text, Coercible output Text) => Maybe Text -> Codec Value input output
+ Autodocodec.Codec: [StringCodec] :: forall input output. (Coercible input Text, Coercible output Text) => Maybe Text -> StringBounds -> Codec Value input output
Files
- CHANGELOG.md +16/−0
- autodocodec.cabal +2/−2
- src/Autodocodec/Aeson/Decode.hs +10/−4
- src/Autodocodec/Aeson/Encode.hs +2/−2
- src/Autodocodec/Codec.hs +76/−3
CHANGELOG.md view
@@ -1,5 +1,21 @@ # Changelog +## [0.6.0.0] - 2026-07-14++This is technically a breaking change but it's unlikely that you'll need to+change any of your codecs.++Thanks to Andreas Ländle (@alaendle) for contributing this feature.++### Changed++* Changed `StringCodec` to include `StringBounds` to represent the minimal/maximal length of a string.++### Added++* `textWithBoundsCodec`+* `stringWithBoundsCodec`+ ## [0.5.0.0] - 2025-06-20 This is technically a breaking change but it's unlikely that you'll need to
autodocodec.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.36.1.+-- This file has been generated from package.yaml by hpack version 0.38.3. -- -- see: https://github.com/sol/hpack name: autodocodec-version: 0.5.0.0+version: 0.6.0.0 synopsis: Self-documenting encoder and decoder homepage: https://github.com/NorfairKing/autodocodec#readme bug-reports: https://github.com/NorfairKing/autodocodec/issues
src/Autodocodec/Aeson/Decode.hs view
@@ -29,7 +29,6 @@ import qualified Data.HashMap.Strict as HashMap import Data.Map (Map) import Data.Scientific as Scientific-import Data.Text (Text) import qualified Data.Text as T import Data.Vector (Vector) import qualified Data.Vector as V@@ -66,9 +65,16 @@ BoolCodec mname -> case mname of Nothing -> coerce (parseJSON value :: JSON.Parser Bool) Just name -> coerce $ withBool (T.unpack name) pure value- StringCodec mname -> case mname of- Nothing -> coerce (parseJSON value :: JSON.Parser Text)- Just name -> coerce $ withText (T.unpack name) pure value+ StringCodec mname bounds ->+ coerce $+ ( \f -> case mname of+ Nothing -> parseJSON value >>= f+ Just name -> withText (T.unpack name) f value+ )+ ( \s -> case checkStringBounds bounds s of+ Left err -> fail err+ Right s' -> pure s'+ ) IntegerCodec mname bounds -> coerce $ ( \f -> do
src/Autodocodec/Aeson/Encode.hs view
@@ -74,7 +74,7 @@ go a = \case NullCodec -> JSON.Null BoolCodec _ -> toJSON (coerce a :: Bool)- StringCodec _ -> toJSON (coerce a :: Text)+ StringCodec _ _ -> toJSON (coerce a :: Text) IntegerCodec _ _ -> toJSON (coerce a :: Integer) NumberCodec _ _ -> toJSON (coerce a :: Scientific) ArrayOfCodec _ c -> toJSON (fmap (`go` c) (coerce a :: Vector _))@@ -130,7 +130,7 @@ go a = \case NullCodec -> JSON.null_ BoolCodec _ -> JSON.bool (coerce a :: Bool)- StringCodec _ -> JSON.text (coerce a :: Text)+ StringCodec _ _ -> JSON.text (coerce a :: Text) IntegerCodec _ _ -> JSON.scientific (fromInteger (coerce a :: Integer) :: Scientific) NumberCodec _ _ -> JSON.scientific (coerce a :: Scientific) ArrayOfCodec _ c -> JSON.list (`go` c) (V.toList (coerce a :: Vector _))
src/Autodocodec/Codec.hs view
@@ -98,6 +98,8 @@ (Coercible a Text, Coercible b Text) => -- | Name of the @string@, for error messages and documentation. Maybe Text ->+ -- | Bounds for the string, these are checked and documented+ StringBounds -> ValueCodec a b -- | Encode 'Integer' to a @number@ value, and decode a @number@ value as an 'Integer'. --@@ -296,6 +298,32 @@ ObjectCodec input output -> ObjectCodec input newOutput +data StringBounds = StringBounds+ { -- | Lower bound, inclusive. A string is valid if its length is greater than, or equal to, this value.+ stringBoundsMinLength :: !(Maybe Natural),+ -- | Upper bound, inclusive. A string is valid if its length is less than, or equal to, this value.+ stringBoundsMaxLength :: !(Maybe Natural)+ }+ deriving (Show, Eq, Ord, Generic)++instance Validity StringBounds where+ validate StringBounds {..} =+ mconcat+ [ validate stringBoundsMinLength,+ validate stringBoundsMaxLength+ ]++emptyStringBounds :: StringBounds+emptyStringBounds = StringBounds Nothing Nothing++checkStringBounds :: StringBounds -> Text -> Either String Text+checkStringBounds StringBounds {..} s =+ case stringBoundsMinLength of+ Just lo | T.length s < fromIntegral lo -> Left $ unwords ["String", show s, "is shorter than the lower bound", show lo]+ _ -> case stringBoundsMaxLength of+ Just hi | T.length s > fromIntegral hi -> Left $ unwords ["String", show s, "is longer than the upper bound", show hi]+ _ -> Right s+ data Bounds a = Bounds { -- | Lower bound, inclusive boundsLower :: !(Maybe a),@@ -378,7 +406,7 @@ -- | A codec within the 'JSON.Object' context. ----- An 'Object' can be used to turn a Haskell value into a 'JSON.Object' or to parse a 'JSON.Object' into a haskell value.+-- An 'ObjectCodec' can be used to turn a Haskell value into a 'JSON.Object' or to parse a 'JSON.Object' into a haskell value. -- -- This cannot be used in certain places where 'ValueCodec' could be used, and vice versa. type ObjectCodec = Codec JSON.Object@@ -410,7 +438,7 @@ go d = \case NullCodec -> pure $ showString "NullCodec" BoolCodec mName -> pure $ showParen (d > 10) $ showString "BoolCodec " . showsPrec 11 mName- StringCodec mName -> pure $ showParen (d > 10) $ showString "StringCodec " . showsPrec 11 mName+ StringCodec mName mbs -> pure $ showParen (d > 10) $ showString "StringCodec " . showsPrec 11 mName . showString " " . showsPrec 11 mbs IntegerCodec mName mbs -> pure $ showParen (d > 10) $ showString "IntegerCodec " . showsPrec 11 mName . showString " " . showsPrec 11 mbs NumberCodec mName mbs -> pure $ showParen (d > 10) $ showString "NumberCodec " . showsPrec 11 mName . showString " " . showsPrec 11 mbs ArrayOfCodec mName c -> (\s -> showParen (d > 10) $ showString "ArrayOfCodec " . showsPrec 11 mName . showString " " . s) <$> go 11 c@@ -1328,8 +1356,24 @@ -- -- > textCodec = StringCodec Nothing textCodec :: JSONCodec Text-textCodec = StringCodec Nothing+textCodec = StringCodec Nothing emptyStringBounds +-- | Codec for 'Text' values with bounds+--+-- During parsing, only 'Text' values within the given boundaries are accepted.+--+-- During rendering, the value is not checked and is simply output as is.+--+-- === Example usage+--+-- >>> JSON.parseMaybe (parseJSONVia (textWithBoundsCodec StringBounds { stringBoundsMinLength = Just 1, stringBoundsMaxLength = Just 10})) (String "hello")+-- Just "hello"+--+-- >>> JSON.parseMaybe (parseJSONVia (textWithBoundsCodec StringBounds { stringBoundsMinLength = Just 1, stringBoundsMaxLength = Just 4})) (String "hello")+-- Nothing+textWithBoundsCodec :: StringBounds -> JSONCodec Text+textWithBoundsCodec bounds = StringCodec Nothing bounds+ -- | Codec for 'String' values -- --@@ -1352,6 +1396,35 @@ -- This is a 'String' version of 'textCodec'. stringCodec :: JSONCodec String stringCodec = dimapCodec T.unpack T.pack textCodec++-- | Codec for 'String' values with bounds+--+-- During parsing, only 'String' values within the given boundaries are accepted.+--+-- During rendering, the value is not checked and is simply output as is.+--+-- === Example usage+--+-- >>> JSON.parseMaybe (parseJSONVia (stringWithBoundsCodec StringBounds { stringBoundsMinLength = Just 1, stringBoundsMaxLength = Just 10})) (String "hello")+-- Just "hello"+--+-- >>> JSON.parseMaybe (parseJSONVia (stringWithBoundsCodec StringBounds { stringBoundsMinLength = Just 1, stringBoundsMaxLength = Just 4})) (String "hello")+-- Nothing+--+--+-- === WARNING+--+-- This codec uses 'T.unpack' and 'T.pack' to dimap a 'textWithBoundsCodec', so it __does not roundtrip__.+--+-- >>> toJSONVia (stringWithBoundsCodec StringBounds { stringBoundsMinLength = Just 1, stringBoundsMaxLength = Just 10}) "\55296"+-- String "\65533"+--+--+-- ==== API Note+--+-- This is a 'String' version of 'textWithBoundsCodec'.+stringWithBoundsCodec :: StringBounds -> JSONCodec String+stringWithBoundsCodec bounds = dimapCodec T.unpack T.pack (textWithBoundsCodec bounds) -- | Codec for 'Scientific' values --