-- 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 :: (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 (MigrateFrom a), Arbitrary (MigrateFrom a), SafeJSON a, SafeJSON (MigrateFrom a), Migrate a, MigrateFrom a ~ b) -- | Constraints for migrating from a future version type TestReverseMigrate a b = (Eq a, Show (MigrateFrom (Reverse a)), Arbitrary (MigrateFrom (Reverse a)), 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 -- | A concrete, poly-kinded proxy type 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 (ToJSON a, FromJSON a) => 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 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)
-- | 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 inpenetrable 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
-- | 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 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
-- | 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 Int64 -> [(Maybe Int64, String)] -> ProfileVersions
-- | Version of the type checked for consistency.
[profileCurrentVersion] :: ProfileVersions -> Maybe Int64
-- | All versions in the chain with their type names.
[profileSupportedVersions] :: ProfileVersions -> [(Maybe Int64, 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 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 ()