-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Wrap Data.Aeson.Value in a newtype SafeValue #44
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,9 +9,11 @@ | |
{-# LANGUAGE AllowAmbiguousTypes #-} | ||
{-# LANGUAGE CPP #-} | ||
{-# LANGUAGE DefaultSignatures #-} | ||
{-# LANGUAGE DeriveGeneric #-} | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE FlexibleInstances #-} | ||
{-# LANGUAGE GADTs #-} | ||
{-# LANGUAGE GeneralizedNewtypeDeriving #-} | ||
{-# LANGUAGE LambdaCase #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE ScopedTypeVariables #-} | ||
|
@@ -46,7 +48,7 @@ | |
import Data.Functor.Identity (Identity(..)) | ||
import Data.Functor.Compose (Compose (..)) | ||
import Data.Functor.Product (Product (..)) | ||
import Data.Functor.Sum (Sum(..)) | ||
Check warning on line 51 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
Check warning on line 51 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver nightly
Check warning on line 51 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-22
Check warning on line 51 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-19
Check warning on line 51 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-20
|
||
import Data.Hashable (Hashable) | ||
import Data.HashMap.Strict (HashMap) | ||
import qualified Data.HashSet as HS (HashSet, fromList, toList) | ||
|
@@ -55,7 +57,7 @@ | |
import Data.IntSet (IntSet) | ||
import qualified Data.List as List (intercalate, lookup) | ||
import Data.List.NonEmpty (NonEmpty(..)) | ||
import Data.Map as M (Map, singleton) | ||
Check warning on line 60 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
Check warning on line 60 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver nightly
Check warning on line 60 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-22
Check warning on line 60 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-19
Check warning on line 60 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-20
|
||
import Data.Maybe (fromMaybe, isJust, isNothing) | ||
#if MIN_VERSION_base(4,11,0) | ||
import Data.Monoid (Dual(..)) | ||
|
@@ -90,6 +92,7 @@ | |
import Data.Void (Void) | ||
import Data.Word (Word8, Word16, Word32, Word64) | ||
import Foreign.C.Types (CTime) | ||
import GHC.Generics (Generic) | ||
import Numeric.Natural (Natural) | ||
import Test.Tasty.QuickCheck (Arbitrary(..), shrinkIntegral) | ||
|
||
|
@@ -103,6 +106,9 @@ | |
import qualified Data.HashMap.Strict as Map (delete, insert, lookup, size, toList) | ||
#endif | ||
|
||
newtype SafeValue = SafeValue {unSafeValue :: Value} | ||
deriving (Eq, Read, Show, Generic, Hashable, FromJSON, ToJSON) | ||
|
||
-- | 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 | ||
|
@@ -131,9 +137,9 @@ | |
-- can be modified if need be. | ||
-- | ||
-- This function cannot be used directly. Use 'safeToJSON', instead. | ||
safeTo :: a -> Contained Value | ||
default safeTo :: ToJSON a => a -> Contained Value | ||
safeTo = contain . toJSON | ||
safeTo :: a -> Contained SafeValue | ||
default safeTo :: ToJSON a => a -> Contained SafeValue | ||
safeTo = contain . SafeValue .toJSON | ||
|
||
-- | This method defines how a value should be parsed without also worrying | ||
-- about writing out the version tag. The default implementation uses 'parseJSON', | ||
|
@@ -251,8 +257,8 @@ | |
-- * the version field did not have a number | ||
-- | ||
-- @since 1.2.0.0 | ||
getVersion :: Value -> Maybe Int32 | ||
getVersion (Object o) = | ||
getVersion :: SafeValue -> Maybe Int32 | ||
getVersion (SafeValue (Object o)) = | ||
case Map.toList o of | ||
[(k1, v1), (k2, v2)] | ||
| k1 == dataVersionField && k2 == dataField -> parseInt v1 | ||
|
@@ -268,17 +274,17 @@ | |
-- "{\"~v\":0,\"~d\":\"test\"}" | ||
-- | ||
-- @since 1.0.0 | ||
setVersion' :: forall a. SafeJSON a => Version a -> Value -> Value | ||
setVersion' :: forall a. SafeJSON a => Version a -> SafeValue -> SafeValue | ||
Check warning on line 277 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
Check warning on line 277 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver nightly
Check warning on line 277 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-22
Check warning on line 277 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-19
Check warning on line 277 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-20
|
||
setVersion' (Version mVersion) val = | ||
case mVersion of | ||
Nothing -> val | ||
Just i -> case val of | ||
Object o -> | ||
SafeValue (Object o) -> | ||
let vField = maybe versionField | ||
(const dataVersionField) | ||
$ dataVersionField `Map.lookup` o | ||
in Object $ Map.insert vField (toJSON i) o | ||
other -> object | ||
in SafeValue $ Object $ Map.insert vField (toJSON i) o | ||
other -> SafeValue $ object | ||
[ dataVersionField .= i | ||
, dataField .= other | ||
] | ||
|
@@ -315,7 +321,7 @@ | |
-- @ | ||
-- | ||
-- @since 1.0.0 | ||
setVersion :: forall a. SafeJSON a => Value -> Value | ||
setVersion :: forall a. SafeJSON a => SafeValue -> SafeValue | ||
setVersion = setVersion' (version @a) | ||
|
||
-- | /CAUTION: Only use this function if you know what you're doing./ | ||
|
@@ -328,17 +334,32 @@ | |
-- should be hidden. | ||
-- | ||
-- @since 1.0.0 | ||
removeVersion :: Value -> Value | ||
removeVersion = \case | ||
-- removeVersion :: SafeValue -> Value | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This commented-out section can be removed, right? |
||
-- removeVersion = \case | ||
-- SafeValue (Object o) -> go o | ||
-- -- Recursively find all version tags and remove them. | ||
-- SafeValue (Array a) -> SafeValue $ Array $ removeVersion <$> a | ||
-- other -> unSafeValue other | ||
-- -- Recursively find all version tags and remove them. | ||
-- where go o = maybe regular removeVersion $ do | ||
-- _ <- dataVersionField `Map.lookup` o | ||
-- dataField `Map.lookup` o | ||
-- where regular = SafeValue $ Object $ removeVersion <$> Map.delete versionField o | ||
|
||
removeVersion :: SafeValue -> Value | ||
removeVersion = removeVersion' . unSafeValue | ||
|
||
removeVersion' :: Value -> Value | ||
removeVersion' = \case | ||
Object o -> go o | ||
-- Recursively find all version tags and remove them. | ||
Array a -> Array $ removeVersion <$> a | ||
Array a -> Array $ removeVersion' <$> a | ||
other -> other | ||
-- Recursively find all version tags and remove them. | ||
where go o = maybe regular removeVersion $ do | ||
where go o = maybe regular removeVersion' $ do | ||
_ <- dataVersionField `Map.lookup` o | ||
dataField `Map.lookup` o | ||
where regular = Object $ removeVersion <$> Map.delete versionField o | ||
where regular = Object $ removeVersion' <$> Map.delete versionField o | ||
|
||
instance Show (Version a) where | ||
show (Version mi) = "Version " ++ showV mi | ||
|
@@ -389,14 +410,14 @@ | |
|
||
-- | Used to define 'kind'. | ||
-- Extends a previous version. | ||
extension :: (SafeJSON a, Migrate a) => Kind a | ||
Check warning on line 413 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
Check warning on line 413 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver nightly
Check warning on line 413 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-22
Check warning on line 413 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-19
Check warning on line 413 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-20
|
||
extension = Extends Proxy | ||
|
||
-- | 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 | ||
Check warning on line 420 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
Check warning on line 420 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver nightly
Check warning on line 420 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-22
Check warning on line 420 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-19
Check warning on line 420 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-20
|
||
extended_base = Extended base | ||
|
||
-- | Used to define 'kind'. | ||
|
@@ -446,7 +467,7 @@ | |
-- __This function does not check consistency of the 'SafeJSON' instances.__ | ||
-- __It is advised to always 'Data.SafeJSON.Test.testConsistency' for all__ | ||
-- __your instances in a production setting.__ | ||
safeToJSON :: forall a. SafeJSON a => a -> Value | ||
safeToJSON :: forall a. SafeJSON a => a -> SafeValue | ||
safeToJSON a = case thisKind of | ||
Base | isNothing i -> tojson | ||
Extended Base | isNothing i -> tojson | ||
|
@@ -932,7 +953,7 @@ | |
safeFrom val = contain $ | ||
parseJSON val >>= traverse safeFromJSON | ||
-- Nothing means do whatever Aeson thinks Nothing should be | ||
safeTo Nothing = contain $ toJSON (Nothing :: Maybe Value) | ||
safeTo Nothing = contain $ SafeValue $ toJSON (Nothing :: Maybe Value) | ||
-- If there's something, keep it safe | ||
safeTo (Just a) = contain $ safeToJSON a | ||
typeName = typeName1 | ||
|
@@ -944,8 +965,8 @@ | |
case eVal of | ||
Left a -> Left <$> safeFromJSON a | ||
Right b -> Right <$> safeFromJSON b | ||
safeTo (Left a) = contain $ toJSON (Left $ safeToJSON a :: Either Value Void) | ||
safeTo (Right b) = contain $ toJSON (Right $ safeToJSON b :: Either Void Value) | ||
safeTo (Left a) = contain $ SafeValue $ toJSON (Left $ safeToJSON a :: Either SafeValue Void) | ||
safeTo (Right b) = contain $ SafeValue $ toJSON (Right $ safeToJSON b :: Either Void SafeValue) | ||
typeName = typeName2 | ||
version = noVersion | ||
|
||
|
@@ -968,8 +989,8 @@ | |
v <- parseJSON val | ||
VG.convert <$> VG.mapM safeFromJSON (v :: V.Vector Value) | ||
|
||
toGenericVector :: (SafeJSON a, VG.Vector v a) => v a -> Contained Value | ||
toGenericVector = contain . toJSON . fmap safeToJSON . VG.toList | ||
toGenericVector :: (SafeJSON a, VG.Vector v a) => v a -> Contained SafeValue | ||
toGenericVector = contain . SafeValue . toJSON . fmap safeToJSON . VG.toList | ||
|
||
instance SafeJSON a => SafeJSON (V.Vector a) where | ||
safeFrom = fromGenericVector | ||
|
@@ -1005,15 +1026,15 @@ | |
instance {-# OVERLAPPABLE #-} SafeJSON a => SafeJSON [a] where | ||
safeFrom val = contain $ | ||
parseJSON val >>= traverse safeFromJSON | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName1 | ||
version = noVersion | ||
|
||
#define BASIC_UNARY_FUNCTOR(T) \ | ||
instance SafeJSON a => SafeJSON (T a) where { \ | ||
safeFrom val = contain $ \ | ||
parseJSON val >>= traverse safeFromJSON; \ | ||
safeTo as = contain . toJSON $ safeToJSON <$> as; \ | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as; \ | ||
typeName = typeName1; \ | ||
version = noVersion } | ||
|
||
|
@@ -1024,42 +1045,42 @@ | |
instance SafeJSON a => SafeJSON (IntMap a) where | ||
safeFrom val = contain $ | ||
IM.fromList <$> safeFromJSON val | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName1 | ||
version = noVersion | ||
|
||
instance (SafeJSON a) => SafeJSON (DList a) where | ||
safeFrom val = contain $ | ||
DList.fromList <$> safeFromJSON val | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName1 | ||
version = noVersion | ||
|
||
instance (SafeJSON a, Ord a) => SafeJSON (S.Set a) where | ||
safeFrom val = contain $ | ||
S.fromList <$> safeFromJSON val | ||
safeTo as = contain . toJSON $ safeToJSON <$> S.toList as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> S.toList as | ||
typeName = typeName1 | ||
version = noVersion | ||
|
||
instance (Ord k, FromJSONKey k, ToJSONKey k, SafeJSON a) => SafeJSON (Map k a) where | ||
safeFrom val = contain $ | ||
parseJSON val >>= traverse safeFromJSON | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName2 | ||
version = noVersion | ||
|
||
instance (SafeJSON a, Eq a, Hashable a) => SafeJSON (HS.HashSet a) where | ||
Check warning on line 1073 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
|
||
safeFrom val = contain $ | ||
HS.fromList <$> safeFromJSON val | ||
safeTo as = contain . toJSON $ safeToJSON <$> HS.toList as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> HS.toList as | ||
typeName = typeName1 | ||
version = noVersion | ||
|
||
instance (Hashable a, FromJSONKey a, ToJSONKey a, Eq a, SafeJSON b) => SafeJSON (HashMap a b) where | ||
Check warning on line 1080 in src/Data/SafeJSON/Internal.hs GitHub Actions / stack --resolver lts-21
|
||
safeFrom val = contain $ | ||
parseJSON val >>= traverse safeFromJSON | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName2 | ||
version = noVersion | ||
|
||
|
@@ -1072,7 +1093,7 @@ | |
#endif | ||
parseJSON val >>= | ||
traverse safeFromJSON | ||
safeTo as = contain . toJSON $ safeToJSON <$> as | ||
safeTo as = contain . SafeValue . toJSON $ safeToJSON <$> as | ||
typeName = typeName1 | ||
version = noVersion | ||
#endif | ||
|
@@ -1083,7 +1104,7 @@ | |
a <- safeFromJSON a' | ||
b <- safeFromJSON b' | ||
pure (a,b) | ||
safeTo (a,b) = contain $ toJSON (safeToJSON a, safeToJSON b) | ||
safeTo (a,b) = contain $ SafeValue $ toJSON (safeToJSON a, safeToJSON b) | ||
typeName = typeName2 | ||
version = noVersion | ||
|
||
|
@@ -1094,7 +1115,7 @@ | |
b <- safeFromJSON b' | ||
c <- safeFromJSON c' | ||
pure (a,b,c) | ||
safeTo (a,b,c) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c) | ||
safeTo (a,b,c) = contain $ SafeValue $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c) | ||
typeName = typeName3 | ||
version = noVersion | ||
|
||
|
@@ -1106,7 +1127,7 @@ | |
c <- safeFromJSON c' | ||
d <- safeFromJSON d' | ||
pure (a,b,c,d) | ||
safeTo (a,b,c,d) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d) | ||
safeTo (a,b,c,d) = contain $ SafeValue $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d) | ||
typeName = typeName4 | ||
version = noVersion | ||
|
||
|
@@ -1119,7 +1140,7 @@ | |
d <- safeFromJSON d' | ||
e <- safeFromJSON e' | ||
pure (a,b,c,d,e) | ||
safeTo (a,b,c,d,e) = contain $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d, safeToJSON e) | ||
safeTo (a,b,c,d,e) = contain $ SafeValue $ toJSON (safeToJSON a, safeToJSON b, safeToJSON c, safeToJSON d, safeToJSON e) | ||
typeName = typeName5 | ||
version = noVersion | ||
|
||
|
@@ -1131,23 +1152,23 @@ | |
version = noVersion | ||
|
||
-- | @since 1.1.2.0 | ||
instance (SafeJSON (f a), SafeJSON (g a)) => SafeJSON (Sum f g a) where | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why did you comment out this instance? |
||
safeFrom = containWithObject "Sum" $ \o -> do | ||
case Map.toList o of | ||
[("InL", val)] -> InL <$> safeFromJSON val | ||
[("InR", val)] -> InR <$> safeFromJSON val | ||
_ -> fail "Sum expects an object with one field: \"InL\" or \"InR\"" | ||
safeTo = contain . safeToJSON . uncurry M.singleton . \case | ||
InL fa -> ("InL" :: String, safeToJSON fa) | ||
InR ga -> ("InR" :: String, safeToJSON ga) | ||
typeName _ = "Sum" | ||
version = noVersion | ||
-- instance (SafeJSON (f a), SafeJSON (g a)) => SafeJSON (Sum f g a) where | ||
-- safeFrom = containWithObject "Sum" $ \o -> do | ||
-- case Map.toList o of | ||
-- [("InL", val)] -> InL <$> safeFromJSON val | ||
-- [("InR", val)] -> InR <$> safeFromJSON val | ||
-- _ -> fail "Sum expects an object with one field: \"InL\" or \"InR\"" | ||
-- safeTo = contain . safeToJSON . uncurry M.singleton . \case | ||
-- InL fa -> ("InL" :: String, safeToJSON fa) | ||
-- InR ga -> ("InR" :: String, safeToJSON ga) | ||
-- typeName _ = "Sum" | ||
-- version = noVersion | ||
|
||
-- | @since 1.1.2.0 | ||
instance (SafeJSON (f a), SafeJSON (g a)) => SafeJSON (Product f g a) where | ||
safeFrom val = contain $ do | ||
(f, g) <- parseJSON val | ||
Pair <$> safeFromJSON f <*> safeFromJSON g | ||
safeTo (Pair f g) = contain $ toJSON (safeToJSON f, safeToJSON g) | ||
safeTo (Pair f g) = contain $ SafeValue $ toJSON (safeToJSON f, safeToJSON g) | ||
typeName _ = "Product" | ||
version = noVersion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I designed the
setVersion
functions to mainly be used to set the version when the value does not have one (taking a format from a third party who doesn't need to know about the versioning, for example)So I'd say it would be more logical for the type to be
Value -> SafeValue