-- 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 1.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 :: 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)
--   
-- --

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

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