-- Hoogle documentation, generated by Haddock -- See Hoogle, http://www.haskell.org/hoogle/ -- | Automatic JSON format versioning -- -- This library aims to make the updating of JSON formats or contents, -- while keeping backward compatibility, as painless as possible. The way -- this is achieved is through versioning and defined migration functions -- to migrate older (or newer) versions to the one used. -- -- The library mainly consists of two classes: -- --
-- testConsistency @MyType --testConsistency :: forall a. SafeJSON a => Assertion -- | Useful in test suites. Will fail if anything in the chain of your -- types is inconsistent. testConsistency' :: forall a. SafeJSON a => Proxy a -> Assertion -- | Migration test. Mostly useful as regression test. -- -- First argument is the older type which should turn into the second -- argument after migrating using migrate. -- --
-- Just (migrate a) == parseMaybe safeFromJSON (safeToJSON a) --testMigration :: (Show a, Eq a, Migrate a) => MigrateFrom a -> a -> Assertion -- | Similar to testMigration, but using Migrate (Reverse -- a). -- -- The first argument here is the newer type, which will be migrated back -- to the expected second argument (older type). -- --
-- Just (unReverse $ migrate a) == parseMaybe safeFromJSON (safeToJSON a) --testReverseMigration :: (Show a, Eq a, Migrate (Reverse a)) => MigrateFrom (Reverse a) -> a -> Assertion -- | Operator synonymous with testMigration (<=?) :: (Show a, Eq a, Migrate a) => MigrateFrom a -> a -> Assertion infix 1 <=? -- | Operator synonymous with testReverseMigration (>=?) :: (Show a, Eq a, Migrate (Reverse a)) => MigrateFrom (Reverse a) -> a -> Assertion infix 1 >=? -- | Tests that the following holds: -- --
-- Just a == parseMaybe safeFromJSON (safeToJSON a) --testRoundTrip :: forall a. (Show a, Eq a, SafeJSON a) => a -> Assertion -- | This test verifies that direct migration, and migration through -- encoding and decoding to the newer type, is equivalent. migrateRoundTrip :: forall a. (Eq a, Show a, SafeJSON a, Migrate a) => MigrateFrom a -> Assertion -- | Similar to migrateRoundTrip, but tests the migration from a -- newer type to the older type, in case of a Migrate -- (Reverse a) instance migrateReverseRoundTrip :: forall a. (Eq a, Show a, SafeJSON a, Migrate (Reverse a)) => MigrateFrom (Reverse a) -> Assertion -- | Constraints for migrating from a previous version type TestMigrate a b = (Eq a, Show b, Arbitrary b, SafeJSON a, Migrate a, MigrateFrom a ~ b) -- | Constraints for migrating from a future version type TestReverseMigrate a b = (Eq a, Show b, Arbitrary b, SafeJSON a, Migrate (Reverse a), MigrateFrom (Reverse a) ~ b) -- | Tests that the following holds for all a: -- --
-- Just a == parseMaybe safeFromJSON (safeToJSON a) ---- --
-- testRoundTripProp @MyType s --testRoundTripProp :: forall a. (Eq a, Show a, Arbitrary a, SafeJSON a) => String -> TestTree -- | This test verifies that direct migration, and migration through -- encoding and decoding to the newer type, is equivalent for all -- a. -- --
-- Just (migrate a) == parseMaybe safeFromJSON (safeToJSON a) ---- --
-- migrateRoundTripProp @NewType @OldType s --migrateRoundTripProp :: forall a b. TestMigrate a b => String -> TestTree -- | Similar to migrateRoundTripProp, but tests the migration from a -- newer type to the older type, in case of a Migrate -- (Reverse a) instance. -- --
-- Just (unReverse $ migrate a) == parseMaybe safeFromJSON (safeToJSON a) ---- --
-- migrateReverseRoundTripProp @OldType @NewType s --migrateReverseRoundTripProp :: forall a b. TestReverseMigrate a b => String -> TestTree -- | Tests that the following holds for all a: -- --
-- Just a == parseMaybe safeFromJSON (safeToJSON a) --testRoundTripProp' :: forall a. (Eq a, Show a, Arbitrary a, SafeJSON a) => Proxy a -> String -> TestTree -- | This test verifies that direct migration, and migration through -- encoding and decoding to the newer type, is equivalent for all -- a. -- --
-- Just (migrate a) == parseMaybe safeFromJSON (safeToJSON a) --migrateRoundTripProp' :: forall a b. TestMigrate a b => Proxy (a, b) -> String -> TestTree -- | Similar to migrateRoundTripProp, but tests the migration from a -- newer type to the older type, in case of a Migrate -- (Reverse a) instance. -- --
-- Just (unReverse $ migrate a) == parseMaybe safeFromJSON (safeToJSON a) --migrateReverseRoundTripProp' :: forall a b. TestReverseMigrate a b => Proxy (a, b) -> String -> TestTree data Proxy k (t :: k) :: forall k. k -> * Proxy :: Proxy k -- | Please read the -- -- README on GitHub -- -- for an extensive explanation of this library, why and how to use it, -- and examples. module Data.SafeJSON -- | Use this exactly how you would use toJSON from -- Data.Aeson. Though most use cases will probably use one of the -- encode functions from Data.Aeson.Safe. -- -- safeToJSON will add a version tag to the Value created. -- If the Value resulting from safeTo (by default the same -- as toJSON) is an Object, an extra field with -- the version number will be added. -- --
-- Example value:
-- {"type":"test", "data":true}
--
-- Resulting object:
-- {"!v": 1, "type":"test", "data":true}
--
--
-- If the resulting Value is not an Object, it
-- will be wrapped in one, with a version field:
--
--
-- Example value:
-- "arbitrary string"
--
-- Resulting object:
-- {"~v": 1, "~d": "arbitrary string"}
--
--
-- This function does not check consistency of the SafeJSON
-- instances. It is advised to always testConsistency for
-- all your instances in a production setting.
safeToJSON :: forall a. SafeJSON a => a -> Value
-- | Use this exactly how you would use parseJSON from
-- Data.Aeson. Though most use cases will probably use one of the
-- decode functions from Data.Aeson.Safe.
--
-- safeFromJSON tries to find the version number in the JSON
-- Value provided, find the appropriate parser and migrate the
-- parsed result back to the requested type using Migrate
-- instances.
--
-- If there is no version number (that means this can also happen with
-- completely unrelated JSON messages), and there is a SafeJSON
-- instance in the chain that has version defined as
-- noVersion, it will try to parse that type.
--
-- N.B. If the consistency of the SafeJSON instance in
-- question is faulty, this will always fail.
safeFromJSON :: forall a. SafeJSON a => Value -> Parser a
-- | A type that can be converted from and to JSON with versioning baked
-- in, using Migrate to automate migration between versions,
-- reducing headaches when the need arrises to modify JSON formats while
-- old formats can't simply be disregarded.
class SafeJSON a where version = 0 kind = Base safeTo = contain . toJSON safeFrom = contain . parseJSON typeName = typeName0 internalConsistency = computeConsistency Proxy objectProfile = mkProfile Proxy
-- | The version of the type.
--
-- Only used as a key so it must be unique (this is checked at
-- run-time)
--
-- Version numbering doesn't have to be sequential or continuous.
--
-- The default version is 0 (zero).
version :: SafeJSON a => Version a
-- | The kind specifies how versions are dealt with. By default, values are
-- tagged with version 0 and don't have any previous versions.
--
-- The default kind is base
kind :: SafeJSON a => Kind a
-- | This method defines how a value should be serialized without worrying
-- about adding the version. The default implementation uses
-- toJSON, but can be modified if need be.
--
-- This function cannot be used directly. Use safeToJSON, instead.
safeTo :: SafeJSON a => a -> Contained Value
-- | This method defines how a value should be serialized without worrying
-- about adding the version. The default implementation uses
-- toJSON, but can be modified if need be.
--
-- This function cannot be used directly. Use safeToJSON, instead.
safeTo :: (SafeJSON a, ToJSON a) => a -> Contained Value
-- | This method defines how a value should be parsed without also worrying
-- about writing out the version tag. The default implementation uses
-- parseJSON, but can be modified if need be.
--
-- This function cannot be used directly. Use safeFromJSON,
-- instead.
safeFrom :: SafeJSON a => Value -> Contained (Parser a)
-- | This method defines how a value should be parsed without also worrying
-- about writing out the version tag. The default implementation uses
-- parseJSON, but can be modified if need be.
--
-- This function cannot be used directly. Use safeFromJSON,
-- instead.
safeFrom :: (SafeJSON a, FromJSON a) => Value -> Contained (Parser a)
-- | The name of the type. This is used in error message strings and the
-- Profile report.
--
-- Doesn't have to be defined if your type is Typeable. The
-- default implementation is typeName0. (cf. typeName1,
-- typeName2, etc.)
typeName :: SafeJSON a => Proxy a -> String
-- | The name of the type. This is used in error message strings and the
-- Profile report.
--
-- Doesn't have to be defined if your type is Typeable. The
-- default implementation is typeName0. (cf. typeName1,
-- typeName2, etc.)
typeName :: (SafeJSON a, Typeable a) => Proxy a -> String
-- | Version profile.
--
-- Shows the current version of the type and all supported versions it
-- can migrate from.
objectProfile :: SafeJSON a => Profile a
-- | This is an impenetrable container. A security measure used to ensure
-- safeFrom and safeTo are never used directly. Instead,
-- always use safeFromJSON and safeToJSON.
data Contained a
-- | Used when defining safeFrom or safeTo.
contain :: a -> Contained a
-- | Similar to withObject, but contained to be used in
-- safeFrom definitions
containWithObject :: String -> (Object -> Parser a) -> Value -> Contained (Parser a)
-- | Similar to withArray, but contained to be used in
-- safeFrom definitions
containWithArray :: String -> (Array -> Parser a) -> Value -> Contained (Parser a)
-- | Similar to withText, but contained to be used in
-- safeFrom definitions
containWithText :: String -> (Text -> Parser a) -> Value -> Contained (Parser a)
-- | Similar to withScientific, but contained to be used in
-- safeFrom definitions
containWithScientific :: String -> (Scientific -> Parser a) -> Value -> Contained (Parser a)
-- | Similar to withBool, but contained to be used in
-- safeFrom definitions
containWithBool :: String -> (Bool -> Parser a) -> Value -> Contained (Parser a)
-- | Similar to .:, but uses safeFromJSON instead of
-- parseJSON to parse the value in the given field.
(.:$) :: SafeJSON a => Object -> Text -> Parser a
-- | Similar to .:?, but uses safeFromJSON instead of
-- parseJSON to maybe parse the value in the given field.
(.:$?) :: SafeJSON a => Object -> Text -> Parser (Maybe a)
-- | Similar to .:!, but uses safeFromJSON instead of
-- parseJSON to maybe parse the value in the given field.
(.:$!) :: SafeJSON a => Object -> Text -> Parser (Maybe a)
-- | Similarly to .=, but uses safeToJSON instead of toJSON
-- to convert the value in that key-value pair.
(.=$) :: (SafeJSON a, KeyValue kv) => Text -> a -> kv
-- | A simple numeric version id.
--
-- Version has a Num instance and should be declared using
-- integer literals: version = 2
data Version a
-- | This is used for types that don't have a version tag.
--
-- This is used for primitive values that are not tagged with a version
-- number, like Int, Text, [a], etc.
--
-- But also when implementing SafeJSON after the fact, when a
-- format is already in use, but you still want to be able to
-- migrate from it to a newer type or format.
--
-- N.B. version = noVersion is
-- distinctively different from version =
-- 0, which will add a version tag with the number 0
-- (zero), whereas noVersion will not add a
-- version tag.
noVersion :: Version a
-- | CAUTION: Only use this function if you know what you're doing.
-- The version will be set top-level, without inspection of the
-- Value!
--
-- (cf. removeVersion) In some rare cases, you might want to
-- interpret a versionless Value as a certain type/version.
-- setVersion allows you to (unsafely) insert a version field.
--
-- If possible, it is advised to use a FromJSON instance
-- instead. (One that doesn't also use safeFromJSON in its
-- methods!)
--
-- This might be needed when data sent to an API endpoint doesn't need to
-- implement SafeJSON standards. E.g. in the case of endpoints for third
-- parties or customers.
--
--
-- USAGE:
--
-- {-# LANGUAGE TypeApplications #-}
-- data Test = Test String
-- instance SafeJSON Test where ...
--
-- >>> val = String "test" :: Value
-- String "test"
-- >>> encode val
-- ""test""
-- >>> encode $ setVersion @Test val
-- "{"~v":0,"~d":"test"}"
-- >>> parseMaybe safeFromJSON $ setVersion @Test val
-- Just (Test "test")
--
setVersion :: forall a. SafeJSON a => Value -> Value
-- | Same as setVersion, but requires a Version parameter.
--
--
-- >>> 'encode' $ 'setVersion'' ('version' :: 'Version' Test) val
-- "{\"~v\":0,\"~d\":\"test\"}"
--
setVersion' :: forall a. SafeJSON a => Version a -> Value -> Value
-- | CAUTION: Only use this function if you know what you're doing.
--
-- (cf. setVersion) removeVersion removes all the
-- SafeJSON versioning from a JSON Value. Even recursively.
--
-- This might be necessary if the resulting JSON is sent to a third party
-- (e.g. customer) and the SafeJSON versioning should be hidden.
removeVersion :: Value -> Value
-- | The kind of a SafeJSON type determines how it can be
-- migrated to.
data Kind a
-- | Used to define kind. Base types do not extend any
-- type.
base :: Kind a
-- | Used to define kind. Extends a previous version.
extension :: (SafeJSON a, Migrate a) => Kind a
-- | Used to define kind. Types that are extended_base, are
-- extended by a future version and as such can migrate backward from
-- that future version. (cf. extended_extension, base)
extended_base :: (SafeJSON a, Migrate (Reverse a)) => Kind a
-- | Used to define kind. Types that are extended_extension
-- are extended by a future version and as such can migrate from that
-- future version, but they also extend a previous version. (cf.
-- extended_base, extension)
extended_extension :: (SafeJSON a, Migrate a, Migrate (Reverse a)) => Kind a
-- | Type name string representation of a nullary type constructor.
typeName0 :: Typeable a => Proxy a -> String
-- | Type name string representation of a unary type constructor.
typeName1 :: forall t a. Typeable t => Proxy (t a) -> String
-- | Type name string representation of a binary type constructor.
typeName2 :: forall t a b. Typeable t => Proxy (t a b) -> String
-- | Type name string representation of a ternary type constructor.
typeName3 :: forall t a b c. Typeable t => Proxy (t a b c) -> String
-- | Type name string representation of a 4-ary type constructor.
typeName4 :: forall t a b c d. Typeable t => Proxy (t a b c d) -> String
-- | Type name string representation of a 5-ary type constructor.
typeName5 :: forall t a b c d e. Typeable t => Proxy (t a b c d e) -> String
-- | Profile of the internal consistency of a SafeJSON instance.
--
-- N.B. noVersion shows as null instead of a
-- number.
data Profile a
-- | There is something wrong with versioning
InvalidProfile :: String -> Profile a
-- | Profile of consistent versions
Profile :: ProfileVersions -> Profile a
-- | Version profile of a consistent SafeJSON instance.
data ProfileVersions
ProfileVersions :: Maybe Int32 -> [(Maybe Int32, String)] -> ProfileVersions
-- | Version of the type checked for consistency.
[profileCurrentVersion] :: ProfileVersions -> Maybe Int32
-- | All versions in the chain with their type names.
[profileSupportedVersions] :: ProfileVersions -> [(Maybe Int32, String)]
-- | This instance is needed to handle the migration between older and
-- newer versions.
--
-- Note that, where (Migrate a) migrates from the
-- previous version to the type a, (Migrate
-- (Reverse a)) migrates from the future version to the type
-- a.
--
-- -- instance Migrate NewType where -- type MigrateFrom NewType = OldType -- migrate OldType = NewType -- -- instance Migrate (Reverse OldType) where -- type MigrateFrom (Reverse OldType) = NewType -- migrate NewType = Reverse OldType --class SafeJSON (MigrateFrom a) => Migrate a where type MigrateFrom a where { type family MigrateFrom a; } -- | The migration from the previous version to the current type -- a. OR, in case of a (Reverse a), the -- migration from the future version back to the current type a migrate :: Migrate a => MigrateFrom a -> a -- | This is a wrapper type used migrating backwards in the chain of -- compatible types. -- -- This is useful when running updates in production where new-format -- JSON will be received by old-format expecting programs. newtype Reverse a Reverse :: a -> Reverse a [unReverse] :: Reverse a -> a -- | This module contains homonyms of the Data.Aeson library's -- encoding and decoding functions that, instead, use -- Data.SafeJSON's conversions. This way, switching from -- Data.Aeson to Data.SafeJSON is very easy. After any -- Data.Aeson imports, just add .Safe. -- -- It also exports Data.Aeson and Data.SafeJSON itself for -- convenience, but still hides parseJSON and toJSON so you -- will get errors if you use them anywhere. That way you can explicitly -- decide where to switch to safeFromJSON or safeToJSON, or -- keep the current Data.Aeson functions. module Data.Aeson.Safe -- | Try to decode a ByteString to a SafeJSON value. decode :: SafeJSON a => ByteString -> Maybe a -- | Try to decode a ByteString to a SafeJSON value. decode' :: SafeJSON a => ByteString -> Maybe a -- | Try to decode a ByteString to a SafeJSON value. Produces -- an error message on failure. eitherDecode :: SafeJSON a => ByteString -> Either String a -- | Try to decode a ByteString to a SafeJSON value. Produces -- an error message on failure. eitherDecode' :: SafeJSON a => ByteString -> Either String a -- | Encode a SafeJSON value to a ByteString. encode :: SafeJSON a => a -> ByteString -- | Try to decode a ByteString to a SafeJSON value. decodeStrict :: SafeJSON a => ByteString -> Maybe a -- | Try to decode a ByteString to a SafeJSON value. decodeStrict' :: SafeJSON a => ByteString -> Maybe a -- | Try to decode a ByteString to a SafeJSON value. Produces -- an error message on failure. eitherDecodeStrict :: SafeJSON a => ByteString -> Either String a -- | Try to decode a ByteString to a SafeJSON value. Produces -- an error message on failure. eitherDecodeStrict' :: SafeJSON a => ByteString -> Either String a -- | Same as encode, but also calls toStrict, for -- convenience. encodeStrict :: SafeJSON a => a -> ByteString -- | Try to decode a file to a SafeJSON value. decodeFileStrict :: SafeJSON a => FilePath -> IO (Maybe a) -- | Try to decode a file to a SafeJSON value. decodeFileStrict' :: SafeJSON a => FilePath -> IO (Maybe a) -- | Try to decode a file to a SafeJSON value. Produces an error -- message on failure. eitherDecodeFileStrict :: SafeJSON a => FilePath -> IO (Either String a) -- | Try to decode a file to a SafeJSON value. Produces an error -- message on failure. eitherDecodeFileStrict' :: SafeJSON a => FilePath -> IO (Either String a) -- | Encode a SafeJSON value to a file. encodeFile :: SafeJSON a => FilePath -> a -> IO ()