json-api 0.1.0.3 → 0.1.0.4
raw patch · 12 files changed
+321/−219 lines, 12 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Network.JSONApi.Document: ResourceId :: Text -> ResourceId
- Network.JSONApi.Document: ResourceObject :: ResourceId -> ResourceType -> a -> Maybe Links -> Maybe (Meta b) -> ResourceObject a b
- Network.JSONApi.Document: ResourceType :: Text -> ResourceType
- Network.JSONApi.Document: [getResourceId] :: ResourceObject a b -> ResourceId
- Network.JSONApi.Document: [getResourceType] :: ResourceObject a b -> ResourceType
- Network.JSONApi.Document: data ResourceObject a b
- Network.JSONApi.Document: instance (Data.Aeson.Types.Class.FromJSON a, Data.Aeson.Types.Class.FromJSON b) => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.Resource a b)
- Network.JSONApi.Document: instance (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.Resource a b)
- Network.JSONApi.Document: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (Network.JSONApi.Document.Resource a b)
- Network.JSONApi.Document: instance (GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Network.JSONApi.Document.ErrorDocument a b)
- Network.JSONApi.Document: instance (GHC.Classes.Eq c, GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Network.JSONApi.Document.Document a b c)
- Network.JSONApi.Document: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Network.JSONApi.Document.Resource a b)
- Network.JSONApi.Document: instance (GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Network.JSONApi.Document.ErrorDocument a b)
- Network.JSONApi.Document: instance (GHC.Show.Show c, GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Network.JSONApi.Document.Document a b c)
- Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.Resource a b)
- Network.JSONApi.Document: newtype ResourceId
- Network.JSONApi.Document: newtype ResourceType
- Network.JSONApi.ResourceObject: ResourceId :: Text -> ResourceId
- Network.JSONApi.ResourceObject: ResourceObject :: ResourceId -> ResourceType -> a -> Maybe Links -> Maybe (Meta b) -> ResourceObject a b
- Network.JSONApi.ResourceObject: ResourceType :: Text -> ResourceType
- Network.JSONApi.ResourceObject: [getLinks] :: ResourceObject a b -> Maybe Links
- Network.JSONApi.ResourceObject: [getMetaData] :: ResourceObject a b -> Maybe (Meta b)
- Network.JSONApi.ResourceObject: [getResourceId] :: ResourceObject a b -> ResourceId
- Network.JSONApi.ResourceObject: [getResourceType] :: ResourceObject a b -> ResourceType
- Network.JSONApi.ResourceObject: [getResource] :: ResourceObject a b -> a
- Network.JSONApi.ResourceObject: data ResourceObject a b
- Network.JSONApi.ResourceObject: instance (Data.Aeson.Types.Class.FromJSON a, Data.Aeson.Types.Class.FromJSON b) => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance (GHC.Classes.Eq b, GHC.Classes.Eq a) => GHC.Classes.Eq (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance (GHC.Classes.Ord b, GHC.Classes.Ord a) => GHC.Classes.Ord (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance (GHC.Show.Show b, GHC.Show.Show a) => GHC.Show.Show (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance Data.Aeson.Types.Class.FromJSON Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance Data.Aeson.Types.Class.FromJSON Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: instance Data.Aeson.Types.Class.ToJSON Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance Data.Aeson.Types.Class.ToJSON Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: instance GHC.Classes.Eq Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance GHC.Classes.Eq Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: instance GHC.Classes.Ord Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance GHC.Classes.Ord Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: instance GHC.Generics.Generic (Network.JSONApi.ResourceObject.ResourceObject a b)
- Network.JSONApi.ResourceObject: instance GHC.Generics.Generic Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance GHC.Generics.Generic Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: instance GHC.Show.Show Network.JSONApi.ResourceObject.ResourceId
- Network.JSONApi.ResourceObject: instance GHC.Show.Show Network.JSONApi.ResourceObject.ResourceType
- Network.JSONApi.ResourceObject: newtype ResourceId
- Network.JSONApi.ResourceObject: newtype ResourceType
+ Network.JSONApi.Document: Identifier :: Text -> Text -> Identifier
+ Network.JSONApi.Document: Resource :: Identifier -> a -> Maybe Links -> Maybe (Meta b) -> Maybe (Map Text Relationship) -> Resource a b
+ Network.JSONApi.Document: [_id] :: Identifier -> Text
+ Network.JSONApi.Document: [_type] :: Identifier -> Text
+ Network.JSONApi.Document: [getIdentifier] :: Resource a b -> Identifier
+ Network.JSONApi.Document: [getRelationships] :: Resource a b -> Maybe (Map Text Relationship)
+ Network.JSONApi.Document: data Identifier
+ Network.JSONApi.Document: data Relationship
+ Network.JSONApi.Document: data ResourceData a b
+ Network.JSONApi.Document: instance (Data.Aeson.Types.Class.FromJSON a, Data.Aeson.Types.Class.FromJSON b) => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.ResourceData a b)
+ Network.JSONApi.Document: instance (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.ResourceData a b)
+ Network.JSONApi.Document: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (Network.JSONApi.Document.ErrorDocument a b)
+ Network.JSONApi.Document: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (Network.JSONApi.Document.ResourceData a b)
+ Network.JSONApi.Document: instance (GHC.Classes.Eq a, GHC.Classes.Eq b, GHC.Classes.Eq c) => GHC.Classes.Eq (Network.JSONApi.Document.Document a b c)
+ Network.JSONApi.Document: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Network.JSONApi.Document.ErrorDocument a b)
+ Network.JSONApi.Document: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Network.JSONApi.Document.ResourceData a b)
+ Network.JSONApi.Document: instance (GHC.Show.Show a, GHC.Show.Show b, GHC.Show.Show c) => GHC.Show.Show (Network.JSONApi.Document.Document a b c)
+ Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.ResourceData a b)
+ Network.JSONApi.Document: mkRelationship :: Maybe Identifier -> Maybe Links -> Maybe Relationship
+ Network.JSONApi.Resource: Identifier :: Text -> Text -> Identifier
+ Network.JSONApi.Resource: Resource :: Identifier -> a -> Maybe Links -> Maybe (Meta b) -> Maybe (Map Text Relationship) -> Resource a b
+ Network.JSONApi.Resource: [_id] :: Identifier -> Text
+ Network.JSONApi.Resource: [_type] :: Identifier -> Text
+ Network.JSONApi.Resource: [getIdentifier] :: Resource a b -> Identifier
+ Network.JSONApi.Resource: [getLinks] :: Resource a b -> Maybe Links
+ Network.JSONApi.Resource: [getMetaData] :: Resource a b -> Maybe (Meta b)
+ Network.JSONApi.Resource: [getRelationships] :: Resource a b -> Maybe (Map Text Relationship)
+ Network.JSONApi.Resource: [getResource] :: Resource a b -> a
+ Network.JSONApi.Resource: data Identifier
+ Network.JSONApi.Resource: data Relationship
+ Network.JSONApi.Resource: data Resource a b
+ Network.JSONApi.Resource: instance (Data.Aeson.Types.Class.FromJSON a, Data.Aeson.Types.Class.FromJSON b) => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi.Resource: instance (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi.Resource: instance (GHC.Classes.Eq a, GHC.Classes.Eq b) => GHC.Classes.Eq (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi.Resource: instance (GHC.Show.Show a, GHC.Show.Show b) => GHC.Show.Show (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.FromJSON Network.JSONApi.Resource.Identifier
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.FromJSON Network.JSONApi.Resource.Relationship
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.ToJSON Network.JSONApi.Resource.Identifier
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.ToJSON Network.JSONApi.Resource.Relationship
+ Network.JSONApi.Resource: instance GHC.Classes.Eq Network.JSONApi.Resource.Identifier
+ Network.JSONApi.Resource: instance GHC.Classes.Eq Network.JSONApi.Resource.Relationship
+ Network.JSONApi.Resource: instance GHC.Generics.Generic (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi.Resource: instance GHC.Generics.Generic Network.JSONApi.Resource.Identifier
+ Network.JSONApi.Resource: instance GHC.Generics.Generic Network.JSONApi.Resource.Relationship
+ Network.JSONApi.Resource: instance GHC.Show.Show Network.JSONApi.Resource.Identifier
+ Network.JSONApi.Resource: instance GHC.Show.Show Network.JSONApi.Resource.Relationship
+ Network.JSONApi.Resource: mkRelationship :: Maybe Identifier -> Maybe Links -> Maybe Relationship
- Network.JSONApi.Document: Document :: Resource a b -> Maybe Links -> Maybe (Meta c) -> Document a b c
+ Network.JSONApi.Document: Document :: ResourceData a b -> Maybe Links -> Maybe (Meta c) -> Document a b c
- Network.JSONApi.Document: List :: [ResourceObject a b] -> Resource a b
+ Network.JSONApi.Document: List :: [Resource a b] -> ResourceData a b
- Network.JSONApi.Document: Singleton :: (ResourceObject a b) -> Resource a b
+ Network.JSONApi.Document: Singleton :: (Resource a b) -> ResourceData a b
- Network.JSONApi.Document: [_data] :: Document a b c -> Resource a b
+ Network.JSONApi.Document: [_data] :: Document a b c -> ResourceData a b
- Network.JSONApi.Document: [getLinks] :: ResourceObject a b -> Maybe Links
+ Network.JSONApi.Document: [getLinks] :: Resource a b -> Maybe Links
- Network.JSONApi.Document: [getMetaData] :: ResourceObject a b -> Maybe (Meta b)
+ Network.JSONApi.Document: [getMetaData] :: Resource a b -> Maybe (Meta b)
- Network.JSONApi.Document: [getResource] :: ResourceObject a b -> a
+ Network.JSONApi.Document: [getResource] :: Resource a b -> a
Files
- README.md +4/−4
- example/README.md +82/−49
- example/example.cabal +3/−2
- example/stack.yaml +1/−1
- json-api.cabal +3/−3
- src/Network/JSONApi/Document.hs +14/−13
- src/Network/JSONApi/Resource.hs +113/−0
- src/Network/JSONApi/ResourceObject.hs +0/−57
- test/Network/JSONApi/DocumentSpec.hs +7/−7
- test/Network/JSONApi/ResourceObjectSpec.hs +0/−60
- test/Network/JSONApi/ResourceSpec.hs +71/−0
- test/TestHelpers.hs +23/−23
README.md view
@@ -51,16 +51,16 @@ mkDocument :: User -> Links -> Document User Text Int mkDocument usr links = Document- (Singleton $ toResourceObject usr)+ (Singleton $ toResource usr) (Just links) Nothing -- Helper function to convert a User into a resource object -- This could be our canonical serialization function for a User in any -- response payload-toResourceObject :: User -> ResourceObject User Text-toResourceObject user =- ResourceObject resourceId resourceType user resourceLinks resourceMetaData+toResource :: User -> Resource User Text+toResource user =+ Resource resourceId resourceType user resourceLinks resourceMetaData where resourceId = ResourceId . pack . show . userId $ user resourceType = ResourceType "User"
example/README.md view
@@ -31,40 +31,62 @@ GET /users {- "data":[- {- "attributes":{- "userFirstName":"Isaac",- "userLastName":"Newton",- "userId":1- },- "id":"1",- "meta":null,- "type":"User",- "links":{- "self":"/users/1"- }- },- {- "attributes":{- "userFirstName":"Albert",- "userLastName":"Einstein",- "userId":2+ "data" : [+ {+ "type" : "User",+ "relationships" : {+ "email" : {+ "links" : {+ "self" : "/emails/42"+ },+ "data" : {+ "type" : "Email",+ "id" : "42"+ }+ }+ },+ "links" : {+ "self" : "/users/1"+ },+ "meta" : null,+ "id" : "1",+ "attributes" : {+ "userFirstName" : "Isaac",+ "userLastName" : "Newton",+ "userId" : 1+ } },- "id":"2",- "meta":null,- "type":"User",- "links":{- "self":"/users/2"+ {+ "relationships" : {+ "email" : {+ "links" : {+ "self" : "/emails/88"+ },+ "data" : {+ "id" : "88",+ "type" : "Email"+ }+ }+ },+ "type" : "User",+ "attributes" : {+ "userFirstName" : "Albert",+ "userLastName" : "Einstein",+ "userId" : 2+ },+ "meta" : null,+ "links" : {+ "self" : "/users/2"+ },+ "id" : "2" }- }- ],- "meta":{- "user-count":2- },- "links":{- "self":"/users"- }+ ],+ "meta" : {+ "user-count" : 2+ },+ "links" : {+ "self" : "/users"+ } } ``` @@ -75,23 +97,34 @@ GET /users/1 {- "data":{- "attributes":{- "userFirstName":"Isaac",- "userLastName":"Newton",- "userId":1- },- "id":"1",- "meta":null,- "type":"User",- "links":{- "self":"/users/1"- }- },- "meta":null,- "links":{- "self":"/users/1"- }+ "meta" : null,+ "data" : {+ "id" : "1",+ "attributes" : {+ "userFirstName" : "Isaac",+ "userId" : 1,+ "userLastName" : "Newton"+ },+ "meta" : null,+ "links" : {+ "self" : "/users/1"+ },+ "type" : "User",+ "relationships" : {+ "email" : {+ "links" : {+ "self" : "/emails/42"+ },+ "data" : {+ "id" : "42",+ "type" : "Email"+ }+ }+ }+ },+ "links" : {+ "self" : "/users/1"+ } } ```
example/example.cabal view
@@ -1,5 +1,5 @@ name: example-version: 0.1.0.0+version: 0.1.0.3 synopsis: Initial project template from stack description: Please see README.md homepage: https://github.com/toddmohney/example#readme@@ -19,6 +19,7 @@ exposed-modules: Lib+ Emails Users Users.Controller @@ -28,7 +29,7 @@ build-depends: base >= 4.7 && < 5 , aeson >= 0.11.2.0 && < 0.11.3.0- , containers >= 0.5.6.2 && < 0.5.7.1+ , containers >= 0.5.7.1 && < 0.5.8.0 , data-default >= 0.5.3 && < 5.6.0 , json-api , servant-server >= 0.7.1 && < 0.8.0
example/stack.yaml view
@@ -2,7 +2,7 @@ # For more information, see: http://docs.haskellstack.org/en/stable/yaml_configuration/ # Specifies the GHC version and set of packages available (e.g., lts-3.5, nightly-2015-09-21, ghc-7.10.2)-resolver: lts-6.4+resolver: nightly-2016-07-03 # Local packages, usually specified by relative directory name packages:
json-api.cabal view
@@ -1,5 +1,5 @@ name: json-api-version: 0.1.0.3+version: 0.1.0.4 homepage: https://github.com/toddmohney/json-api bug-reports: https://github.com/toddmohney/json-api/issues license: MIT@@ -56,7 +56,7 @@ Network.JSONApi.Document Network.JSONApi.Meta Network.JSONApi.Link- Network.JSONApi.ResourceObject+ Network.JSONApi.Resource other-modules: @@ -104,7 +104,7 @@ Network.JSONApi.ErrorSpec Network.JSONApi.DocumentSpec Network.JSONApi.MetaSpec- Network.JSONApi.ResourceObjectSpec+ Network.JSONApi.ResourceSpec TestHelpers type: exitcode-stdio-1.0
src/Network/JSONApi/Document.hs view
@@ -7,13 +7,14 @@ ( Document (..) , ErrorDocument (..) , E.Error (..)-, Resource (..)-, RO.ResourceId (..)-, RO.ResourceObject (..)-, RO.ResourceType (..)+, ResourceData (..)+, R.Relationship+, R.Resource (..)+, R.Identifier (..) , L.Links , M.Meta (..) , L.toLinks+, R.mkRelationship ) where import Control.Monad (mzero)@@ -29,8 +30,8 @@ import qualified Network.JSONApi.Error as E import Network.JSONApi.Link as L import Network.JSONApi.Meta as M-import Network.JSONApi.ResourceObject (ResourceObject)-import qualified Network.JSONApi.ResourceObject as RO+import Network.JSONApi.Resource (Resource)+import qualified Network.JSONApi.Resource as R {- | The @Document@ type represents the top-level JSON-API requirement.@@ -41,28 +42,28 @@ For more information see: <http://jsonapi.org/format/#document-top-level> -} data Document a b c = Document- { _data :: Resource a b+ { _data :: ResourceData a b , _links :: Maybe Links , _meta :: Maybe (Meta c) } deriving (Show, Eq, G.Generic) {- |-The @Resource@ type encapsulates the underlying 'ResourceObject'+The @Resource@ type encapsulates the underlying 'Resource' Included in the top-level 'Document', the @Resource@ may be either a singleton resource or a list. For more information see: <http://jsonapi.org/format/#document-top-level> -}-data Resource a b = Singleton (ResourceObject a b)- | List [ResourceObject a b]- deriving (Show, Eq, G.Generic)+data ResourceData a b = Singleton (Resource a b)+ | List [Resource a b]+ deriving (Show, Eq, G.Generic) -instance (ToJSON a, ToJSON b) => ToJSON (Resource a b) where+instance (ToJSON a, ToJSON b) => ToJSON (ResourceData a b) where toJSON (Singleton res) = AE.toJSON res toJSON (List res) = AE.toJSON res -instance (FromJSON a, FromJSON b) => FromJSON (Resource a b) where+instance (FromJSON a, FromJSON b) => FromJSON (ResourceData a b) where parseJSON (AE.Object v) = Singleton <$> (AE.parseJSON (AE.Object v)) parseJSON (AE.Array v) = List <$> (AE.parseJSON (AE.Array v)) parseJSON _ = mzero
+ src/Network/JSONApi/Resource.hs view
@@ -0,0 +1,113 @@+{- |+Module representing a JSON-API resource object.++Specification: <http://jsonapi.org/format/#document-resource-objects>+-}+module Network.JSONApi.Resource+( Identifier (..)+, Resource (..)+, Relationship+, mkRelationship+) where++import Data.Aeson (ToJSON, FromJSON, (.=), (.:), (.:?))+import qualified Data.Aeson as AE+import qualified Data.Aeson.Types as AE+import Data.Map (Map)+import Data.Text (Text)+import qualified GHC.Generics as G+import Network.JSONApi.Link (Links)+import Network.JSONApi.Meta (Meta)+import Prelude hiding (id)++{- |+Type representing a JSON-API resource object.++A Resource supplies standardized data and metadata about a+resource.++Specification: <http://jsonapi.org/format/#document-resource-objects>+-}+data Resource a b = Resource+ { getIdentifier :: Identifier+ , getResource :: a+ , getLinks :: Maybe Links+ , getMetaData :: Maybe (Meta b)+ , getRelationships :: Maybe (Map Text Relationship)+ } deriving (Show, Eq, G.Generic)++instance (ToJSON a, ToJSON b) => ToJSON (Resource a b) where+ toJSON (Resource (Identifier resId resType) resObj linksObj metaObj rels) =+ AE.object [ "id" .= resId+ , "type" .= resType+ , "attributes" .= resObj+ , "links" .= linksObj+ , "meta" .= metaObj+ , "relationships" .= rels+ ]++instance (FromJSON a, FromJSON b) => FromJSON (Resource a b) where+ parseJSON = AE.withObject "resourceObject" $ \v -> do+ id <- v .: "id"+ typ <- v .: "type"+ attrs <- v .: "attributes"+ links <- v .:? "links"+ meta <- v .:? "meta"+ rels <- v .:? "relationships"+ return $ Resource (Identifier id typ) attrs links meta rels+++++{- |+A type representing the Relationship between 2 entities++A Relationship provides basic information for fetching further information+about a related resource.++Specification: <http://jsonapi.org/format/#document-resource-object-relationships>+-}+data Relationship = Relationship+ { _data :: Maybe Identifier+ , _links :: Maybe Links+ } deriving (Show, Eq, G.Generic)++instance ToJSON Relationship where+ toJSON = AE.genericToJSON+ AE.defaultOptions { AE.fieldLabelModifier = drop 1 }++instance FromJSON Relationship where+ parseJSON = AE.genericParseJSON+ AE.defaultOptions { AE.fieldLabelModifier = drop 1 }++{- |+Constructor function for creating a Relationship record++A relationship must contain either an Identifier or a Links record+-}+mkRelationship :: Maybe Identifier -> Maybe Links -> Maybe Relationship+mkRelationship Nothing Nothing = Nothing+mkRelationship resId links = Just $ Relationship resId links++++{- |+Identifiers are used to encapsulate the minimum amount of information+to uniquely identify a resource.++This object will be found at multiple levels of the JSON-API structure++Specification: <http://jsonapi.org/format/#document-resource-identifier-objects>+-}+data Identifier = Identifier+ { _id :: Text+ , _type :: Text+ } deriving (Show, Eq, G.Generic)++instance ToJSON Identifier where+ toJSON = AE.genericToJSON+ AE.defaultOptions { AE.fieldLabelModifier = drop 1 }++instance FromJSON Identifier where+ parseJSON = AE.genericParseJSON+ AE.defaultOptions { AE.fieldLabelModifier = drop 1 }
− src/Network/JSONApi/ResourceObject.hs
@@ -1,57 +0,0 @@-{- |-Module representing a JSON-API resource object.--Specification: <http://jsonapi.org/format/#document-resource-objects>--}-module Network.JSONApi.ResourceObject-( ResourceId (..)-, ResourceObject (..)-, ResourceType (..)-) where--import Data.Aeson (ToJSON, FromJSON, (.=), (.:), (.:?))-import qualified Data.Aeson as AE-import Data.Text (Text)-import qualified GHC.Generics as G-import Network.JSONApi.Link (Links)-import Network.JSONApi.Meta (Meta)--{- |-Type representing a JSON-API resource object.--A ResourceObject supplies standardized data and metadata about a-resource.--Specification: <http://jsonapi.org/format/#document-resource-objects>--}-data ResourceObject a b = ResourceObject- { getResourceId :: ResourceId- , getResourceType :: ResourceType- , getResource :: a- , getLinks :: Maybe Links- , getMetaData :: Maybe (Meta b)- } deriving (Show, Eq, Ord, G.Generic)--newtype ResourceId = ResourceId Text- deriving (Show, Eq, Ord, ToJSON, FromJSON, G.Generic)--newtype ResourceType = ResourceType Text- deriving (Show, Eq, Ord, ToJSON, FromJSON, G.Generic)--instance (ToJSON a, ToJSON b) => ToJSON (ResourceObject a b) where- toJSON (ResourceObject resId resType resObj linksObj metaObj) =- AE.object [ "id" .= resId- , "type" .= resType- , "attributes" .= resObj- , "links" .= linksObj- , "meta" .= metaObj- ]--instance (FromJSON a, FromJSON b) => FromJSON (ResourceObject a b) where- parseJSON = AE.withObject "resourceObject" $ \v ->- ResourceObject- <$> v .: "id"- <*> v .: "type"- <*> v .: "attributes"- <*> v .:? "links"- <*> v .:? "meta"
test/Network/JSONApi/DocumentSpec.hs view
@@ -19,7 +19,7 @@ spec = describe "JSON serialization" $ do it "JSON encodes/decodes a singleton resource" $ do- let resource = toResourceObject testObject+ let resource = toResource testObject let jsonApiObj = Document (Singleton resource) emptyLinks emptyMeta let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson@@ -28,7 +28,7 @@ isRight decodedJson `shouldBe` True it "JSON encodes/decodes a list of resources" $ do- let resources = [toResourceObject testObject, toResourceObject testObject2]+ let resources = [toResource testObject, toResource testObject2] let jsonApiObj = Document (List resources) emptyLinks emptyMeta let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson@@ -37,7 +37,7 @@ isRight decodedJson `shouldBe` True it "contains the allowable top-level keys" $ do- let resource = toResourceObject testObject+ let resource = toResource testObject let jsonApiObj = Document (Singleton resource) emptyLinks emptyMeta let encodedJson = encodeDocumentObject jsonApiObj let dataObject = encodedJson ^? Lens.key "data"@@ -48,7 +48,7 @@ isJust metaObject `shouldBe` True it "allows an optional top-level links object" $ do- let resource = toResourceObject testObject+ let resource = toResource testObject let jsonApiObj = Document (Singleton resource) (Just linksObj) emptyMeta let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson@@ -57,7 +57,7 @@ isRight decodedJson `shouldBe` True it "allows an optional top-level meta object" $ do- let resource = toResourceObject testObject+ let resource = toResource testObject let jsonApiObj = Document (Singleton resource) emptyLinks (Just testMetaObj) let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson@@ -66,7 +66,7 @@ isRight decodedJson `shouldBe` True it "allows an optional top-level meta object" $ do- let resource = toResourceObject testObject+ let resource = toResource testObject let jsonApiObj = Document (Singleton resource) (Just linksObj) (Just testMetaObj) let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson@@ -75,7 +75,7 @@ isRight decodedJson `shouldBe` True decodeDocumentObject :: ByteString- -> Either String (Document TestResourceObject (Maybe Bool) (Maybe TestMetaObject))+ -> Either String (Document TestResource (Maybe Bool) (Maybe TestMetaObject)) decodeDocumentObject = AE.eitherDecode encodeDocumentObject :: (ToJSON a, ToJSON b, ToJSON c) => Document a b c -> ByteString
− test/Network/JSONApi/ResourceObjectSpec.hs
@@ -1,60 +0,0 @@-module Network.JSONApi.ResourceObjectSpec where--import qualified Data.Aeson as AE-import qualified Data.ByteString.Lazy.Char8 as BS-import qualified Data.Map as Map-import Data.Maybe (isJust, fromJust)-import Data.Text (Text, pack)-import qualified GHC.Generics as G-import Network.JSONApi.Document-import Network.URL (URL, importURL)-import TestHelpers (prettyEncode)-import Test.Hspec--data TestObject =- TestObject { myId :: Int- , myName :: Text- , myAge :: Int- , myFavoriteFood :: Text- } deriving (Show, G.Generic)--instance AE.ToJSON TestObject-instance AE.FromJSON TestObject--toResourceObject :: TestObject -> ResourceObject TestObject Int-toResourceObject obj =- ResourceObject- (ResourceId . pack . show . myId $ obj)- (ResourceType "TestObject")- obj- (Just resourceObjectLinks)- (Just resourceObjectMetaData)--resourceObjectLinks :: Links-resourceObjectLinks =- toLinks [ ("self", toURL "/me")- , ("related", toURL "/tacos/4")- ]--resourceObjectMetaData :: Meta Int-resourceObjectMetaData = Meta . Map.fromList $ [ ("extraData", 20) ]--toURL :: String -> URL-toURL = fromJust . importURL--testObject :: TestObject-testObject = TestObject 1 "Fred Armisen" 49 "Pizza"--main :: IO ()-main = hspec spec--spec :: Spec-spec =- describe "ToResourceObject" $- it "can be encoded and decoded from JSON" $ do- let encodedJson = BS.unpack . prettyEncode $ toResourceObject testObject- let decodedJson = AE.decode (BS.pack encodedJson) :: Maybe (ResourceObject TestObject (Maybe Int))- isJust decodedJson `shouldBe` True- {- putStrLn encodedJson -}- -- putStrLn $ show . fromJust $ decodedJson-
+ test/Network/JSONApi/ResourceSpec.hs view
@@ -0,0 +1,71 @@+module Network.JSONApi.ResourceSpec where++import qualified Data.Aeson as AE+import qualified Data.ByteString.Lazy.Char8 as BS+import Data.Map (Map)+import qualified Data.Map as Map+import Data.Maybe (isJust, fromJust)+import Data.Text (Text, pack)+import qualified GHC.Generics as G+import Network.JSONApi.Resource+import Network.JSONApi.Link+import Network.JSONApi.Meta+import Network.URL (URL, importURL)+import TestHelpers (prettyEncode)+import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec =+ describe "ToResource" $+ it "can be encoded and decoded from JSON" $ do+ let encodedJson = BS.unpack . prettyEncode $ toResource testObject+ let decodedJson = AE.decode (BS.pack encodedJson) :: Maybe (Resource TestObject (Maybe Int))+ isJust decodedJson `shouldBe` True+ {- putStrLn encodedJson -}+ {- putStrLn $ show . fromJust $ decodedJson -}++data TestObject = TestObject+ { myId :: Int+ , myName :: Text+ , myAge :: Int+ , myFavoriteFood :: Text+ } deriving (Show, G.Generic)++instance AE.ToJSON TestObject+instance AE.FromJSON TestObject++toResource :: TestObject -> Resource TestObject Int+toResource obj =+ Resource+ (Identifier (pack . show . myId $ obj) "TestObject")+ obj+ (Just resourceLinks)+ (Just resourceMetaData)+ (Just resourceRelationships)++resourceRelationships :: Map Text Relationship+resourceRelationships = Map.fromList $ [ ("friends", relationship) ]++relationship :: Relationship+relationship =+ fromJust $ mkRelationship+ (Just $ Identifier "42" "FriendOfTestObject")+ (Just resourceLinks)++resourceLinks :: Links+resourceLinks =+ toLinks [ ("self", toURL "/me")+ , ("related", toURL "/tacos/4")+ ]++resourceMetaData :: Meta Int+resourceMetaData = Meta . Map.fromList $ [ ("extraData", 20) ]++toURL :: String -> URL+toURL = fromJust . importURL++testObject :: TestObject+testObject = TestObject 1 "Fred Armisen" 49 "Pizza"
test/TestHelpers.hs view
@@ -17,40 +17,40 @@ prettyConfig :: AE.Config prettyConfig = AE.Config { AE.confIndent = 2, AE.confCompare = mempty } -data TestResourceObject =- TestResourceObject { myId :: Int- , myName :: Text- , myAge :: Int- , myFavoriteFood :: Text- } deriving (Show, G.Generic)+data TestResource = TestResource+ { myId :: Int+ , myName :: Text+ , myAge :: Int+ , myFavoriteFood :: Text+ } deriving (Show, G.Generic) -data TestMetaObject =- TestMetaObject { totalPages :: Int- , isSuperFun :: Bool- } deriving (Show, G.Generic)+data TestMetaObject = TestMetaObject+ { totalPages :: Int+ , isSuperFun :: Bool+ } deriving (Show, G.Generic) -instance AE.ToJSON TestResourceObject-instance AE.FromJSON TestResourceObject+instance AE.ToJSON TestResource+instance AE.FromJSON TestResource instance AE.ToJSON TestMetaObject instance AE.FromJSON TestMetaObject -toResourceObject :: TestResourceObject -> ResourceObject TestResourceObject Bool-toResourceObject obj =- ResourceObject- (ResourceId . pack . show . myId $ obj)- (ResourceType "TestResourceObject")+toResource :: TestResource -> Resource TestResource Bool+toResource obj =+ Resource+ (Identifier (pack . show . myId $ obj) "TestResource") obj (Just $ objectLinks obj) (Just $ objectMetaData obj)+ Nothing -objectLinks :: TestResourceObject -> Links+objectLinks :: TestResource -> Links objectLinks obj = toLinks [ ("self", toURL ("/me/" <> (show $ myId obj))) , ("related", toURL ("/friends/" <> (show $ myId obj))) ] -objectMetaData :: TestResourceObject -> Meta Bool+objectMetaData :: TestResource -> Meta Bool objectMetaData obj = Meta . Map.fromList $ [ ("isOld", myAge obj > 50) ] @@ -59,11 +59,11 @@ , ("related", toURL "http://some.domain.com/other/things/1") ] -testObject :: TestResourceObject-testObject = TestResourceObject 1 "Fred Armisen" 51 "Pizza"+testObject :: TestResource+testObject = TestResource 1 "Fred Armisen" 51 "Pizza" -testObject2 :: TestResourceObject-testObject2 = TestResourceObject 2 "Carrie Brownstein" 35 "Lunch"+testObject2 :: TestResource+testObject2 = TestResource 2 "Carrie Brownstein" 35 "Lunch" testMetaObj :: Meta TestMetaObject testMetaObj =