aeson-injector (empty) → 1.0.0.0
raw patch · 5 files changed
+665/−0 lines, 5 filesdep +HUnitdep +aesondep +aeson-injectorsetup-changed
Dependencies added: HUnit, aeson, aeson-injector, base, deepseq, lens, swagger2, text, unordered-containers
Files
- LICENSE +20/−0
- Setup.hs +2/−0
- aeson-injector.cabal +49/−0
- src/Data/Aeson/WithField.hs +338/−0
- test/Main.hs +256/−0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2016 Anton Gushcha++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aeson-injector.cabal view
@@ -0,0 +1,49 @@+name: aeson-injector+version: 1.0.0.0+synopsis: Injecting fields into aeson values+description: See Readme.md+license: MIT+license-file: LICENSE+author: Anton Gushcha +maintainer: ncrashed@gmail.com+copyright: 2016 Anton Gushcha+category: Data, JSON, Web+build-type: Simple+cabal-version: >=1.18+tested-with: + GHC == 7.10.3+ , GHC == 8.0.1+ , GHC == 8.0.2++source-repository head+ type: git+ location: https://github.com/NCrashed/aeson-injector++library+ default-language: Haskell2010+ hs-source-dirs: src+ exposed-modules:+ Data.Aeson.WithField+ + build-depends: + base >= 4.7 && < 4.10+ , aeson >= 0.11 && < 0.13+ , deepseq >= 1.4 && < 2+ , lens >= 4.13 && < 5+ , swagger2 >= 2.0 && < 3.0+ , text >= 1.2 && < 2.0+ , unordered-containers >= 0.2.7 && < 0.3++test-suite test-aeson-injector+ default-language: Haskell2010+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Main.hs+ build-depends: + base+ , aeson+ , aeson-injector+ , HUnit >= 1.3+ , lens+ , swagger2 + , text
+ src/Data/Aeson/WithField.hs view
@@ -0,0 +1,338 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-|+Module : Data.Aeson.WithField+Description : Provides utility to inject fields into aeson values.+Copyright : (c) Anton Gushcha, 2016+License : MIT+Maintainer : ncrashed@gmail.com+Stability : experimental+Portability : Portable++When builds a RESTful API one often faces the problem that some methods+need inbound data without unique identifier (for instance, a creation of+new resource) and some methods need the same outbound data with additional+fields attached to the response.++The module provides you with 'WithField' and 'WithFields' data types that+help you to solve the issue without code duplication. ++It is small utility library that is intented to be used in RESTful APIs, +especially with <http://haskell-servant.readthedocs.io/en/stable/ servant> +and <http://swagger.io/ Swagger>. Its main purpose is simple injection of +fields into JSONs produced by <https://hackage.haskell.org/package/aeson aeson> +library.++Consider the following common data type in web service developing:++@+data News = News {+ title :: Text+, body :: Text+, author :: Text+, timestamp :: UTCTime +}++-- Consider we have simple 'ToJSON' and 'FromJSON' instances+$(deriveJSON defaultOptions ''News) +@++'ToJSON' instance produces JSON's like:++@+{+ "title": "Awesome piece of news!"+, "body": "Big chunk of text"+, "author": "Just Me"+, "timestamp": "2016-07-26T18:54:42.678999Z"+}+@++Now one can create a simple web server with servant DSL:++> type NewsId = Word +> +> type NewsAPI = +> ReqBody '[JSON] News :> Post '[JSON] NewsId+> :<|> Capture "news-id" NewsId :> Get '[JSON] News+> :<|> "list" :> Get '[JSON] [News]+++All seems legit, but, wait a second, an API user definitely would +like to know id of news in the "list" method. One way to do this is declare +new data type @NewsInfo@ with additional field, but it is bad solution as requires +to code duplication for each resource. ++So, here @aeson-injector@ steps in, now you can write:++> type NewsAPI = +> ReqBody '[JSON] News :> Post '[JSON] NewsId+> :<|> Capture "news-id" NewsId :> Get '[JSON] News+> :<|> "list" :> Get '[JSON] [WithField "id" NewsId News]++@'WithField' "id" NewsId News@ or simply @'WithId' NewsId News@ wraps you data type +and injects "id" field in produced JSON values:++>>> encode (WithField 42 myNews :: WithField "id" NewsId News)++> {+> "id": 42+> , "title": "Awesome piece of news!"+> , "body": "Big chunk of text"+> , "author": "Just Me"+> , "timestamp": "2016-07-26T18:54:42.678999Z"+> }++'WithField' data type has `FromJSON` instance for seamless parsing of data with +injected fields and 'ToSchema' instance for <https://hackage.haskell.org/package/servant-swagger servant-swagger> support.++= Injecting multiple values++The library also has more general data type 'WithFields a b' that injects fields of 'toJSON a' into 'toJSON b'. ++@ haskell+data NewsPatch = NewsPatch {+ taggs :: [Text]+, rating :: Double+}+$(deriveJSON defaultOptions ''NewsPatch) +@++@ haskell+let myNewsPatch = NewsPatch ["tag1", "tag2"] 42 +in encode $ WithFields myNewsPatch myNews+@++> {+> "title": "Awesome piece of news!"+> , "body": "Big chunk of text"+> , "author": "Just Me"+> , "timestamp": "2016-07-26T18:54:42.678999Z"+> , "tags": ["tag1", "tag2"]+> , "rating": 42.0+> }++= Corner cases++Unfortunately, we cannot inject in non object values of produced JSON, +so the library creates a wrapper object around non-object value:++@+encode (WithId 0 "non-object" :: WithId Int String)+@++@+{+ "id": 0 +, "value": "non-object"+}+@++The same story is about 'WithFields' data type:++@+encode (WithFields 0 "non-object" :: WithFields Int String)+@++@+{+ "injected": 0 +, "value": "non-object"+}+@++-}+module Data.Aeson.WithField(+ -- * Single field injector+ WithField(..)+ , WithId+ -- * Multiple fields injector+ , WithFields(..)+ ) where ++import Control.Applicative+import Control.DeepSeq+import Control.Lens hiding ((.=))+import Control.Monad +import Data.Aeson +import Data.Monoid +import Data.Proxy +import Data.Swagger +import GHC.Generics +import GHC.TypeLits ++import qualified Data.HashMap.Strict as H +import qualified Data.Text as T ++-- | Injects field 'a' into 'b' with tag 's'. It has+-- special instances for 'ToJSON' and 'FromJSON' for +-- such injection and corresponding Swagger 'ToSchema'+-- instance.+--+-- For instance:+--+-- >>> encode (WithField "val" (Left 42) :: WithField "injected" String (Either Int Int))+-- "{\"Left\":42,\"id\":\"val\"}"+--+-- If the instance cannot inject field (in case of single values and arrays),+-- it wraps the result in the following way:+--+-- >>> encode (WithField "val" 42 :: WithField "injected" String Int)+-- "{\"value\":42,\"injected\":\"val\"}"+data WithField (s :: Symbol) a b = WithField !a !b + deriving (Generic, Eq, Show, Read)++instance (NFData a, NFData b) => NFData (WithField s a b)++-- | Workaround for a problem that is discribed as:+-- sometimes I need a id with the data, sometimes not.+--+-- The important note that 'ToJSON' and 'FromJSON' instances+-- behaves as it is 'a' but with additional 'id' field.+type WithId i a = WithField "id" i a ++-- | Note: the instance injects field only in 'Object' case.+-- In other cases it forms a wrapper around the 'Value' produced +-- by 'toJSON' of inner 'b' body.+--+-- Example of wrapper:+--+-- > { "id": 0, "value": [1, 2, 3] }+instance (KnownSymbol s, ToJSON a, ToJSON b) => ToJSON (WithField s a b) where + toJSON (WithField a b) = let+ jsonb = toJSON b + field = T.pack $ symbolVal (Proxy :: Proxy s)+ in case toJSON b of + Object vs -> Object $ H.insert field (toJSON a) vs + _ -> object [+ field .= a + , "value" .= jsonb+ ]++-- | Note: the instance tries to parse the json as object with +-- additional field value, if it fails it assumes that it is a+-- wrapper produced by corresponding 'ToJSON' instance.+instance (KnownSymbol s, FromJSON a, FromJSON b) => FromJSON (WithField s a b) where + parseJSON val@(Object o) = injected <|> wrapper + where + field = T.pack $ symbolVal (Proxy :: Proxy s)+ injected = WithField + <$> o .: field+ <*> parseJSON val+ wrapper = WithField + <$> o .: field+ <*> o .: "value"+ parseJSON _ = mzero++-- | Note: the instance tries to generate schema of the json as object with +-- additional field value, if it fails it assumes that it is a+-- wrapper produced by corresponding 'ToJSON' instance.+instance (KnownSymbol s, ToSchema a, ToSchema b) => ToSchema (WithField s a b) where + declareNamedSchema _ = do + NamedSchema n s <- declareNamedSchema (Proxy :: Proxy b)+ if s ^. type_ == SwaggerObject then inline n s + else wrapper n s+ where + field = T.pack $ symbolVal (Proxy :: Proxy s)+ namePrefix = "WithField '" <> field <> "' "+ wrapper n s = do + indexSchema <- declareSchema (Proxy :: Proxy a)+ return $ NamedSchema (fmap (namePrefix <>) n) $ mempty+ & type_ .~ SwaggerObject+ & properties .~+ [ (field, Inline indexSchema)+ , ("value", Inline s)+ ]+ & required .~ [ field, "value" ]+ inline n s = do + indexSchema <- declareSchema (Proxy :: Proxy a)+ return $ NamedSchema (fmap (namePrefix <>) n) $ s+ & properties %~ ([(field, Inline indexSchema)] <>)+ & required %~ ([field] <>)++-- | Merge fields of 'a' into 'b', more general version of 'WithField'.+--+-- The usual mode of the data type assumes that 'ToJSON' instances of 'a' and 'b' +-- produce 'Object' subtype of aeson 'Value'. If it is not true, a wrapper +-- layer is introduced.+--+-- If 'a' is not a 'Object', the wrapper contains 'injected' field with body of 'a'.+-- If 'b' is not a 'Object', the wrapper contains 'value' field with body of 'b'.+-- If both are not 'Object', the wrapper contains 'injected' and 'value' keys with+-- 'a' and 'b' respectively.+data WithFields a b = WithFields !a !b + deriving (Generic, Eq, Show, Read)++instance (NFData a, NFData b) => NFData (WithFields a b)++-- | Note: the instance injects field only in 'Object' case.+-- In other cases it forms a wrapper around the 'Value' produced +-- by 'toJSON' of inner 'b' body.+--+-- Example of wrapper when 'b' is not a 'Object', 'b' goes into "value" field:+-- +-- > { "field1": 0, "field2": "val", "value": [1, 2, 3] }+--+-- Example of wrapper when 'a' is not a 'Object', but 'b' is. 'a' goes into +-- "injected" field:+-- +-- > { "field1": 0, "field2": "val", "injected": [1, 2, 3] }+--+-- Example of wrapper when as 'a' is not a 'Object', as 'b' is not. 'a' goes into +-- "injected" field, 'b' goes into "value" field:+--+-- > { "value": 42, "injected": [1, 2, 3] }+instance (ToJSON a, ToJSON b) => ToJSON (WithFields a b) where + toJSON (WithFields a b) = let+ jsonb = toJSON b + jsona = toJSON a+ in case jsonb of + Object bvs -> case jsona of + Object avs -> Object $ H.union avs bvs+ _ -> Object $ H.insert "injected" jsona bvs + _ -> case jsona of + Object avs -> Object $ H.insert "value" jsonb avs+ _ -> object [+ "injected" .= jsona+ , "value" .= jsonb+ ]++-- | Note: the instance tries to parse the json as object with +-- additional field value, if it fails it assumes that it is a+-- wrapper produced by corresponding 'ToJSON' instance.+instance (FromJSON a, FromJSON b) => FromJSON (WithFields a b) where + parseJSON val@(Object o) = WithFields+ <$> (parseJSON val <|> o .: "injected")+ <*> (parseJSON val <|> o .: "value")+ parseJSON _ = mzero++-- | Note: the instance tries to generate schema of the json as object with +-- additional field value, if it fails it assumes that it is a+-- wrapper produced by corresponding 'ToJSON' instance.+instance (ToSchema a, ToSchema b) => ToSchema (WithFields a b) where + declareNamedSchema _ = do + NamedSchema nb sb <- declareNamedSchema (Proxy :: Proxy b)+ NamedSchema na sa <- declareNamedSchema (Proxy :: Proxy a)+ let newName = combinedName <$> na <*> nb+ return . NamedSchema newName $ case (sa ^. type_ , sb ^. type_) of + (SwaggerObject, SwaggerObject) -> sa <> sb + (SwaggerObject, _) -> sa <> bwrapper sb+ (_, SwaggerObject) -> awrapper sa <> sb+ _ -> awrapper sa <> bwrapper sb+ where+ combinedName a b = "WithFields_" <> a <> "_" <> b+ awrapper nas = mempty+ & type_ .~ SwaggerObject+ & properties .~ [ ("injected", Inline nas) ]+ & required .~ [ "injected" ]+ bwrapper nbs = mempty+ & type_ .~ SwaggerObject+ & properties .~ [ ("value", Inline nbs) ]+ & required .~ [ "value" ]+
+ test/Main.hs view
@@ -0,0 +1,256 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Main where++import Control.Lens hiding ((.=))+import Control.Monad +import Data.Aeson+import Data.Aeson.WithField+import Data.Swagger+import Data.Swagger.Internal.Schema+import Data.Text +import Data.Proxy+import Test.HUnit ++main :: IO ()+main = runTestTT tests >> return ()+ where+ tests = TestList [+ (TestLabel "WithField tests" withFieldTests)+ , (TestLabel "WithFields tests" withFieldsTests)+ ]++data TestObj = TestObj !Text + deriving (Eq, Show)++instance ToJSON TestObj where + toJSON (TestObj t) = object ["field" .= t]+instance FromJSON TestObj where + parseJSON (Object o) = TestObj <$> o .: "field"+ parseJSON _ = mzero+instance ToSchema TestObj where + declareNamedSchema prx = do + t <- declareSchema (Proxy :: Proxy Text)+ return $ NamedSchema (Just "TestObj") $ mempty + & type_ .~ SwaggerObject+ & properties .~ [("field", Inline t)]+ & required .~ ["field"]++withFieldTests :: Test+withFieldTests = TestList [+ TestLabel "toJSON" testsToJSON+ , TestLabel "fromJSON" testsFromJSON+ , TestLabel "toSchema" testsToSchema+ ]+ where+ testsToJSON = TestList [+ TestLabel "Inline mode: atomic field" $ TestCase $ do + let expected = object ["a" .= (0 :: Int), "field" .= ("val" :: Text)]+ let actual = toJSON (WithField 0 (TestObj "val") :: WithField "a" Int TestObj)+ expected @=? actual+ , TestLabel "Inline mode: complex field" $ TestCase $ do + let expected = object [+ "a" .= object ["field" .= ("key" :: Text)]+ , "field" .= ("val" :: Text)]+ let actual = toJSON (WithField (TestObj "key") (TestObj "val") + :: WithField "a" TestObj TestObj)+ expected @=? actual+ , TestLabel "Wrapper mode: atomic" $ TestCase $ do + let expected = object ["a" .= (0 :: Int), "value" .= ("val" :: Text)]+ let actual = toJSON (WithField 0 "val" :: WithField "a" Int String)+ expected @=? actual+ , TestLabel "Wrapper mode: array" $ TestCase $ do + let expected = object ["a" .= (0 :: Int), "value" .= (["val1", "val2"] :: [Text])]+ let actual = toJSON (WithField 0 ["val1", "val2"] :: WithField "a" Int [String])+ expected @=? actual+ , TestLabel "Wrapper mode: complex field" $ TestCase $ do + let expected = object [+ "a" .= object ["field" .= ("key" :: Text)]+ , "value" .= ("val":: Text)]+ let actual = toJSON (WithField (TestObj "key") "val" :: WithField "a" TestObj String)+ expected @=? actual+ ]+ testsFromJSON = TestList [+ TestLabel "Inline mode: atomic field" $ TestCase $ do + let Success (expected :: WithField "a" Int TestObj) = fromJSON $ object [+ "a" .= (0 :: Int)+ , "field" .= ("val" :: Text)]+ let actual = WithField 0 (TestObj "val") :: WithField "a" Int TestObj+ expected @=? actual+ , TestLabel "Inline mode: complex field" $ TestCase $ do + let Success (expected :: WithField "a" TestObj TestObj) = fromJSON $ object [+ "a" .= object ["field" .= ("key" :: Text)]+ , "field" .= ("val" :: Text)]+ let actual = WithField (TestObj "key") (TestObj "val") :: WithField "a" TestObj TestObj+ expected @=? actual+ , TestLabel "Wrapper mode: atomic" $ TestCase $ do + let Success (expected :: WithField "a" Int String) = fromJSON $ object [+ "a" .= (0 :: Int)+ , "value" .= ("val" :: Text)]+ let actual = WithField 0 "val" :: WithField "a" Int String+ expected @=? actual+ , TestLabel "Wrapper mode: array" $ TestCase $ do + let Success (expected :: WithField "a" Int [String]) = fromJSON $ object [+ "a" .= (0 :: Int)+ , "value" .= (["val1", "val2"] :: [Text]) ]+ let actual = WithField 0 ["val1", "val2"] :: WithField "a" Int [String]+ expected @=? actual+ , TestLabel "Wrapper mode: complex field" $ TestCase $ do + let Success (expected :: WithField "a" TestObj String) = fromJSON $ object [+ "a" .= object ["field" .= ("key" :: Text)]+ , "value" .= ("val":: Text)]+ let actual = WithField (TestObj "key") "val" :: WithField "a" TestObj String+ expected @=? actual+ ]+ testsToSchema = TestList [+ TestLabel "Inline mode: atomic field" $ TestCase $ do + let expected = [+ ("a", Inline $ toSchema (Proxy :: Proxy Int))+ , ("field", Inline $ toSchema (Proxy :: Proxy String))]+ let actual = toSchema (Proxy :: Proxy (WithField "a" Int TestObj))+ expected @=? (actual ^. properties)+ , TestLabel "Inline mode: complex field" $ TestCase $ do + let expected = [+ ("a", Inline $ toSchema (Proxy :: Proxy TestObj))+ , ("field", Inline $ toSchema (Proxy :: Proxy String))]+ let actual = toSchema (Proxy :: Proxy (WithField "a" TestObj TestObj))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: atomic" $ TestCase $ do + let expected = [+ ("a", Inline $ toSchema (Proxy :: Proxy Int))+ , ("value", Inline $ toSchema (Proxy :: Proxy String))]+ let actual = toSchema (Proxy :: Proxy (WithField "a" Int String))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: array" $ TestCase $ do + let expected = [+ ("a", Inline $ toSchema (Proxy :: Proxy Int))+ , ("value", Inline $ toSchema (Proxy :: Proxy [String]))]+ let actual = toSchema (Proxy :: Proxy (WithField "a" Int [String]))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: complex field" $ TestCase $ do + let expected = [+ ("a", Inline $ toSchema (Proxy :: Proxy TestObj))+ , ("value", Inline $ toSchema (Proxy :: Proxy String))]+ let actual = toSchema (Proxy :: Proxy (WithField "a" TestObj String))+ expected @=? (actual ^. properties)+ ]++data TestObj1 = TestObj1 !Text + deriving (Eq, Show)++instance ToJSON TestObj1 where + toJSON (TestObj1 t) = object ["field1" .= t]+instance FromJSON TestObj1 where + parseJSON (Object o) = TestObj1 <$> o .: "field1"+ parseJSON _ = mzero+instance ToSchema TestObj1 where + declareNamedSchema prx = do + t <- declareSchema (Proxy :: Proxy Text)+ return $ NamedSchema (Just "TestObj1") $ mempty + & type_ .~ SwaggerObject+ & properties .~ [("field1", Inline t)]+ & required .~ ["field1"]++data TestObj2 = TestObj2 !Text + deriving (Eq, Show)++instance ToJSON TestObj2 where + toJSON (TestObj2 t) = object ["field2" .= t]+instance FromJSON TestObj2 where + parseJSON (Object o) = TestObj2 <$> o .: "field2"+ parseJSON _ = mzero+instance ToSchema TestObj2 where + declareNamedSchema prx = do + t <- declareSchema (Proxy :: Proxy Text)+ return $ NamedSchema (Just "TestObj2") $ mempty + & type_ .~ SwaggerObject+ & properties .~ [("field2", Inline t)]+ & required .~ ["field2"]++withFieldsTests :: Test+withFieldsTests = TestList [+ TestLabel "toJSON" testsToJSON+ , TestLabel "fromJSON" testsFromJSON+ , TestLabel "toSchema" testsToSchema+ ]+ where+ testsToJSON = TestList [+ TestLabel "Inline mode" $ TestCase $ do + let expected = object [+ "field1" .= ("val1" :: Text)+ , "field2" .= ("val2" :: Text) ]+ let actual = toJSON (WithFields (TestObj1 "val1") (TestObj2 "val2"))+ expected @=? actual+ , TestLabel "Wrapper mode: first" $ TestCase $ do + let expected = object [+ "injected" .= ("val1" :: Text)+ , "field2" .= ("val2" :: Text) ]+ let actual = toJSON (WithFields ("val1" :: Text) (TestObj2 "val2"))+ expected @=? actual+ , TestLabel "Wrapper mode: second" $ TestCase $ do + let expected = object [+ "field1" .= ("val1" :: Text)+ , "value" .= ("val2" :: Text) ]+ let actual = toJSON (WithFields (TestObj1 "val1") ("val2" :: Text))+ expected @=? actual+ , TestLabel "Wrapper mode: both" $ TestCase $ do + let expected = object [+ "injected" .= ("val1" :: Text)+ , "value" .= ("val2" :: Text) ]+ let actual = toJSON (WithFields ("val1" :: Text) ("val2" :: Text))+ expected @=? actual+ ]+ testsFromJSON = TestList [+ TestLabel "Inline mode" $ TestCase $ do + let Success (expected :: WithFields TestObj1 TestObj2) = fromJSON $ object [+ "field1" .= ("val1" :: Text)+ , "field2" .= ("val2" :: Text)]+ let actual = WithFields (TestObj1 "val1") (TestObj2 "val2")+ expected @=? actual+ , TestLabel "Wrapper mode: first" $ TestCase $ do + let Success (expected :: WithFields Text TestObj2) = fromJSON $ object [+ "injected" .= ("val1" :: Text)+ , "field2" .= ("val2" :: Text)]+ let actual = WithFields ("val1" :: Text) (TestObj2 "val2")+ expected @=? actual+ , TestLabel "Wrapper mode: second" $ TestCase $ do + let Success (expected :: WithFields TestObj1 Text) = fromJSON $ object [+ "field1" .= ("val1" :: Text)+ , "value" .= ("val2" :: Text)]+ let actual = WithFields (TestObj1 "val1") ("val2" :: Text)+ expected @=? actual+ , TestLabel "Wrapper mode: both" $ TestCase $ do + let Success (expected :: WithFields Text Text) = fromJSON $ object [+ "injected" .= ("val1" :: Text)+ , "value" .= ("val2" :: Text)]+ let actual = WithFields ("val1" :: Text) ("val2" :: Text)+ expected @=? actual+ ]+ testsToSchema = TestList [+ TestLabel "Inline mode" $ TestCase $ do + let expected = [+ ("field1", Inline $ toSchema (Proxy :: Proxy Text))+ , ("field2", Inline $ toSchema (Proxy :: Proxy Text))]+ let actual = toSchema (Proxy :: Proxy (WithFields TestObj1 TestObj2))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: first" $ TestCase $ do + let expected = [+ ("injected", Inline $ toSchema (Proxy :: Proxy Text))+ , ("field2", Inline $ toSchema (Proxy :: Proxy Text))]+ let actual = toSchema (Proxy :: Proxy (WithFields Text TestObj2))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: second" $ TestCase $ do + let expected = [+ ("field1", Inline $ toSchema (Proxy :: Proxy Text))+ , ("value", Inline $ toSchema (Proxy :: Proxy Text))]+ let actual = toSchema (Proxy :: Proxy (WithFields TestObj1 Text))+ expected @=? (actual ^. properties)+ , TestLabel "Wrapper mode: both" $ TestCase $ do + let expected = [+ ("injected", Inline $ toSchema (Proxy :: Proxy Text))+ , ("value", Inline $ toSchema (Proxy :: Proxy Text))]+ let actual = toSchema (Proxy :: Proxy (WithFields Text Text))+ expected @=? (actual ^. properties)+ ]