-- 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: -- -- -- -- Using these two classes, JSON serialized data types will stay -- parsable, even after format changes. -- -- For a more in-depth explanation and examples, please see the README at -- https://github.com/Vlix/safe-json#readme @package safe-json @version 0.1.0 -- | This module contains some functions to use for testing SafeJSON -- and Migrate instances. module Data.SafeJSON.Test -- | Useful in test suites. Will fail if anything in the chain of your -- types is inconsistent. -- --

Example usage:

-- --
--   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
--   
-- --

Example usage:

-- --
--   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
--   
-- --

Example usage:

-- --
--   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
--   
-- --

Example usage:

-- -- Please also note the reversing of the type applications. -- --
--   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. -- --

Example

-- -- Two types that can migrate to each other. -- -- (Don't forget to give OldType one of the extended -- kinds, and NewType one of the extension -- kinds.) -- --
--   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 ()