json-api 0.1.0.4 → 0.1.1.0
raw patch · 12 files changed
+310/−145 lines, 12 filesPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
API changes (from Hackage documentation)
- Network.JSONApi.Document: Document :: ResourceData a b -> Maybe Links -> Maybe (Meta c) -> Document a b c
- Network.JSONApi.Document: Error :: Maybe Text -> Maybe Links -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe (Meta a) -> Error a
- Network.JSONApi.Document: Identifier :: Text -> Text -> Identifier
- Network.JSONApi.Document: Meta :: (Map Text a) -> Meta a
- Network.JSONApi.Document: Resource :: Identifier -> a -> Maybe Links -> Maybe (Meta b) -> Maybe (Map Text Relationship) -> Resource a b
- Network.JSONApi.Document: [_data] :: Document a b c -> ResourceData a b
- Network.JSONApi.Document: [_id] :: Identifier -> Text
- Network.JSONApi.Document: [_links] :: Document a b c -> Maybe Links
- Network.JSONApi.Document: [_meta] :: Document a b c -> Maybe (Meta c)
- Network.JSONApi.Document: [_type] :: Identifier -> Text
- Network.JSONApi.Document: [code] :: Error a -> Maybe Text
- Network.JSONApi.Document: [detail] :: Error a -> Maybe Text
- Network.JSONApi.Document: [getIdentifier] :: Resource a b -> Identifier
- Network.JSONApi.Document: [getLinks] :: Resource a b -> Maybe Links
- Network.JSONApi.Document: [getMetaData] :: Resource a b -> Maybe (Meta b)
- Network.JSONApi.Document: [getRelationships] :: Resource a b -> Maybe (Map Text Relationship)
- Network.JSONApi.Document: [getResource] :: Resource a b -> a
- Network.JSONApi.Document: [id] :: Error a -> Maybe Text
- Network.JSONApi.Document: [links] :: Error a -> Maybe Links
- Network.JSONApi.Document: [meta] :: Error a -> Maybe (Meta a)
- Network.JSONApi.Document: [status] :: Error a -> Maybe Text
- Network.JSONApi.Document: [title] :: Error a -> Maybe Text
- Network.JSONApi.Document: data Error a
- Network.JSONApi.Document: data Identifier
- Network.JSONApi.Document: data Links
- Network.JSONApi.Document: data Meta a
- Network.JSONApi.Document: data Relationship
- Network.JSONApi.Document: data Resource 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.ErrorDocument 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.FromJSON a, Data.Aeson.Types.Class.FromJSON b, Data.Aeson.Types.Class.FromJSON c) => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.Document a b c)
- Network.JSONApi.Document: instance (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.ErrorDocument 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 (Data.Aeson.Types.Class.ToJSON a, Data.Aeson.Types.Class.ToJSON b, Data.Aeson.Types.Class.ToJSON c) => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.Document a b c)
- 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.Document a b c)
- Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.ErrorDocument a b)
- 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.Document: toLinks :: [(Rel, URL)] -> Links
- Network.JSONApi.Error: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.JSONApi.Error.Error a)
- Network.JSONApi.Error: instance GHC.Show.Show a => GHC.Show.Show (Network.JSONApi.Error.Error a)
- Network.JSONApi.Meta: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Meta.Meta a)
- Network.JSONApi.Meta: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Meta.Meta a)
- Network.JSONApi.Meta: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.JSONApi.Meta.Meta a)
- Network.JSONApi.Meta: instance GHC.Classes.Ord a => GHC.Classes.Ord (Network.JSONApi.Meta.Meta a)
- Network.JSONApi.Meta: instance GHC.Generics.Generic (Network.JSONApi.Meta.Meta a)
- Network.JSONApi.Meta: instance GHC.Show.Show a => GHC.Show.Show (Network.JSONApi.Meta.Meta a)
- 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 GHC.Generics.Generic (Network.JSONApi.Resource.Resource a b)
+ Network.JSONApi: Error :: Maybe Text -> Maybe Links -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Meta -> Error a
+ Network.JSONApi: ErrorDocument :: Error a -> Maybe Links -> Maybe Meta -> ErrorDocument a
+ Network.JSONApi: Identifier :: Text -> Text -> Identifier
+ Network.JSONApi: List :: [Resource a] -> ResourceData a
+ Network.JSONApi: Meta :: Object -> Meta
+ Network.JSONApi: Resource :: Identifier -> a -> Maybe Links -> Maybe Meta -> Maybe (Map Text Relationship) -> Resource a
+ Network.JSONApi: Singleton :: (Resource a) -> ResourceData a
+ Network.JSONApi: [_errorLinks] :: ErrorDocument a -> Maybe Links
+ Network.JSONApi: [_errorMeta] :: ErrorDocument a -> Maybe Meta
+ Network.JSONApi: [_error] :: ErrorDocument a -> Error a
+ Network.JSONApi: [_id] :: Identifier -> Text
+ Network.JSONApi: [_type] :: Identifier -> Text
+ Network.JSONApi: [code] :: Error a -> Maybe Text
+ Network.JSONApi: [detail] :: Error a -> Maybe Text
+ Network.JSONApi: [getIdentifier] :: Resource a -> Identifier
+ Network.JSONApi: [getLinks] :: Resource a -> Maybe Links
+ Network.JSONApi: [getMetaData] :: Resource a -> Maybe Meta
+ Network.JSONApi: [getRelationships] :: Resource a -> Maybe (Map Text Relationship)
+ Network.JSONApi: [getResource] :: Resource a -> a
+ Network.JSONApi: [id] :: Error a -> Maybe Text
+ Network.JSONApi: [links] :: Error a -> Maybe Links
+ Network.JSONApi: [meta] :: Error a -> Maybe Meta
+ Network.JSONApi: [status] :: Error a -> Maybe Text
+ Network.JSONApi: [title] :: Error a -> Maybe Text
+ Network.JSONApi: class (ToJSON a) => MetaObject a
+ Network.JSONApi: class (ToJSON a, FromJSON a) => ResourcefulEntity a where fromResource = getResource toResource a = Resource (Identifier (resourceIdentifier a) (resourceType a)) a (resourceLinks a) (resourceMetaData a) (resourceRelationships a)
+ Network.JSONApi: data Document a
+ Network.JSONApi: data Error a
+ Network.JSONApi: data ErrorDocument a
+ Network.JSONApi: data Identifier
+ Network.JSONApi: data Links
+ Network.JSONApi: data Meta
+ Network.JSONApi: data Relationship
+ Network.JSONApi: data Resource a
+ Network.JSONApi: data ResourceData a
+ Network.JSONApi: fromResource :: ResourcefulEntity a => Resource a -> a
+ Network.JSONApi: mkDocument :: ResourcefulEntity a => [a] -> Maybe Links -> Maybe Meta -> [Value] -> Document a
+ Network.JSONApi: mkMeta :: (MetaObject a) => a -> Meta
+ Network.JSONApi: mkRelationship :: Maybe Identifier -> Maybe Links -> Maybe Relationship
+ Network.JSONApi: resourceIdentifier :: ResourcefulEntity a => a -> Text
+ Network.JSONApi: resourceLinks :: ResourcefulEntity a => a -> Maybe Links
+ Network.JSONApi: resourceMetaData :: ResourcefulEntity a => a -> Maybe Meta
+ Network.JSONApi: resourceRelationships :: ResourcefulEntity a => a -> Maybe (Map Text Relationship)
+ Network.JSONApi: resourceType :: ResourcefulEntity a => a -> Text
+ Network.JSONApi: toLinks :: [(Rel, URL)] -> Links
+ Network.JSONApi: toResource :: ResourcefulEntity a => a -> Resource a
+ Network.JSONApi: typeName :: MetaObject a => a -> Text
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.Document a)
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.ErrorDocument a)
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Document.ResourceData a)
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.Document a)
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.ErrorDocument a)
+ Network.JSONApi.Document: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Document.ResourceData a)
+ Network.JSONApi.Document: instance GHC.Classes.Eq (Network.JSONApi.Document.ErrorDocument a)
+ Network.JSONApi.Document: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.JSONApi.Document.Document a)
+ Network.JSONApi.Document: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.JSONApi.Document.ResourceData a)
+ Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.Document a)
+ Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.ErrorDocument a)
+ Network.JSONApi.Document: instance GHC.Generics.Generic (Network.JSONApi.Document.ResourceData a)
+ Network.JSONApi.Document: instance GHC.Show.Show (Network.JSONApi.Document.ErrorDocument a)
+ Network.JSONApi.Document: instance GHC.Show.Show a => GHC.Show.Show (Network.JSONApi.Document.Document a)
+ Network.JSONApi.Document: instance GHC.Show.Show a => GHC.Show.Show (Network.JSONApi.Document.ResourceData a)
+ Network.JSONApi.Document: mkDocument :: ResourcefulEntity a => [a] -> Maybe Links -> Maybe Meta -> [Value] -> Document a
+ Network.JSONApi.Error: instance GHC.Classes.Eq (Network.JSONApi.Error.Error a)
+ Network.JSONApi.Error: instance GHC.Show.Show (Network.JSONApi.Error.Error a)
+ Network.JSONApi.Meta: class (ToJSON a) => MetaObject a
+ Network.JSONApi.Meta: instance Data.Aeson.Types.Class.FromJSON Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: instance Data.Aeson.Types.Class.ToJSON Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: instance GHC.Base.Monoid Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: instance GHC.Classes.Eq Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: instance GHC.Generics.Generic Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: instance GHC.Show.Show Network.JSONApi.Meta.Meta
+ Network.JSONApi.Meta: mkMeta :: (MetaObject a) => a -> Meta
+ Network.JSONApi.Meta: typeName :: MetaObject a => a -> Text
+ Network.JSONApi.Resource: class (ToJSON a, FromJSON a) => ResourcefulEntity a where fromResource = getResource toResource a = Resource (Identifier (resourceIdentifier a) (resourceType a)) a (resourceLinks a) (resourceMetaData a) (resourceRelationships a)
+ Network.JSONApi.Resource: fromResource :: ResourcefulEntity a => Resource a -> a
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.FromJSON a => Data.Aeson.Types.Class.FromJSON (Network.JSONApi.Resource.Resource a)
+ Network.JSONApi.Resource: instance Data.Aeson.Types.Class.ToJSON a => Data.Aeson.Types.Class.ToJSON (Network.JSONApi.Resource.Resource a)
+ Network.JSONApi.Resource: instance GHC.Classes.Eq a => GHC.Classes.Eq (Network.JSONApi.Resource.Resource a)
+ Network.JSONApi.Resource: instance GHC.Generics.Generic (Network.JSONApi.Resource.Resource a)
+ Network.JSONApi.Resource: instance GHC.Show.Show a => GHC.Show.Show (Network.JSONApi.Resource.Resource a)
+ Network.JSONApi.Resource: resourceIdentifier :: ResourcefulEntity a => a -> Text
+ Network.JSONApi.Resource: resourceLinks :: ResourcefulEntity a => a -> Maybe Links
+ Network.JSONApi.Resource: resourceMetaData :: ResourcefulEntity a => a -> Maybe Meta
+ Network.JSONApi.Resource: resourceRelationships :: ResourcefulEntity a => a -> Maybe (Map Text Relationship)
+ Network.JSONApi.Resource: resourceType :: ResourcefulEntity a => a -> Text
+ Network.JSONApi.Resource: toResource :: ResourcefulEntity a => a -> Resource a
- Network.JSONApi.Document: ErrorDocument :: Error a -> Maybe Links -> Maybe (Meta b) -> ErrorDocument a b
+ Network.JSONApi.Document: ErrorDocument :: Error a -> Maybe Links -> Maybe Meta -> ErrorDocument a
- Network.JSONApi.Document: List :: [Resource a b] -> ResourceData a b
+ Network.JSONApi.Document: List :: [Resource a] -> ResourceData a
- Network.JSONApi.Document: Singleton :: (Resource a b) -> ResourceData a b
+ Network.JSONApi.Document: Singleton :: (Resource a) -> ResourceData a
- Network.JSONApi.Document: [_errorLinks] :: ErrorDocument a b -> Maybe Links
+ Network.JSONApi.Document: [_errorLinks] :: ErrorDocument a -> Maybe Links
- Network.JSONApi.Document: [_errorMeta] :: ErrorDocument a b -> Maybe (Meta b)
+ Network.JSONApi.Document: [_errorMeta] :: ErrorDocument a -> Maybe Meta
- Network.JSONApi.Document: [_error] :: ErrorDocument a b -> Error a
+ Network.JSONApi.Document: [_error] :: ErrorDocument a -> Error a
- Network.JSONApi.Document: data Document a b c
+ Network.JSONApi.Document: data Document a
- Network.JSONApi.Document: data ErrorDocument a b
+ Network.JSONApi.Document: data ErrorDocument a
- Network.JSONApi.Document: data ResourceData a b
+ Network.JSONApi.Document: data ResourceData a
- Network.JSONApi.Error: Error :: Maybe Text -> Maybe Links -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe (Meta a) -> Error a
+ Network.JSONApi.Error: Error :: Maybe Text -> Maybe Links -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> Maybe Meta -> Error a
- Network.JSONApi.Error: [meta] :: Error a -> Maybe (Meta a)
+ Network.JSONApi.Error: [meta] :: Error a -> Maybe Meta
- Network.JSONApi.Meta: Meta :: (Map Text a) -> Meta a
+ Network.JSONApi.Meta: Meta :: Object -> Meta
- Network.JSONApi.Meta: data Meta a
+ Network.JSONApi.Meta: data Meta
- Network.JSONApi.Resource: Resource :: Identifier -> a -> Maybe Links -> Maybe (Meta b) -> Maybe (Map Text Relationship) -> Resource a b
+ Network.JSONApi.Resource: Resource :: Identifier -> a -> Maybe Links -> Maybe Meta -> Maybe (Map Text Relationship) -> Resource a
- Network.JSONApi.Resource: [getIdentifier] :: Resource a b -> Identifier
+ Network.JSONApi.Resource: [getIdentifier] :: Resource a -> Identifier
- Network.JSONApi.Resource: [getLinks] :: Resource a b -> Maybe Links
+ Network.JSONApi.Resource: [getLinks] :: Resource a -> Maybe Links
- Network.JSONApi.Resource: [getMetaData] :: Resource a b -> Maybe (Meta b)
+ Network.JSONApi.Resource: [getMetaData] :: Resource a -> Maybe Meta
- Network.JSONApi.Resource: [getRelationships] :: Resource a b -> Maybe (Map Text Relationship)
+ Network.JSONApi.Resource: [getRelationships] :: Resource a -> Maybe (Map Text Relationship)
- Network.JSONApi.Resource: [getResource] :: Resource a b -> a
+ Network.JSONApi.Resource: [getResource] :: Resource a -> a
- Network.JSONApi.Resource: data Resource a b
+ Network.JSONApi.Resource: data Resource a
Files
- example/example.cabal +1/−0
- json-api.cabal +3/−2
- src/Network/JSONApi.hs +28/−0
- src/Network/JSONApi/Document.hs +60/−40
- src/Network/JSONApi/Error.hs +1/−1
- src/Network/JSONApi/Meta.hs +44/−7
- src/Network/JSONApi/Resource.hs +24/−6
- test/Network/JSONApi/DocumentSpec.hs +25/−28
- test/Network/JSONApi/ErrorSpec.hs +2/−1
- test/Network/JSONApi/MetaSpec.hs +52/−15
- test/Network/JSONApi/ResourceSpec.hs +16/−18
- test/TestHelpers.hs +54/−27
example/example.cabal view
@@ -35,6 +35,7 @@ , servant-server >= 0.7.1 && < 0.8.0 , text >= 1.2.2.1 && < 1.2.3.0 , url >= 2.1.3 && < 2.2.0+ , unordered-containers >= 0.2.7.1 && < 0.2.8.0 , wai >= 3.2.1.1 && < 3.2.2.0 , warp
json-api.cabal view
@@ -1,5 +1,5 @@ name: json-api-version: 0.1.0.4+version: 0.1.1.0 homepage: https://github.com/toddmohney/json-api bug-reports: https://github.com/toddmohney/json-api/issues license: MIT@@ -52,6 +52,7 @@ RecordWildCards exposed-modules:+ Network.JSONApi Network.JSONApi.Error Network.JSONApi.Document Network.JSONApi.Meta@@ -80,6 +81,7 @@ , lens >= 4.13 && < 5.0 , lens-aeson >= 1.0.0.5 && < 1.0.1.0 , text >= 1.2.2.1 && < 1.2.3.0+ , unordered-containers >= 0.2.7.1 && < 0.2.8.0 , url >= 2.1.3 && < 2.2.0 default-language: Haskell2010@@ -94,7 +96,6 @@ -Wall -fwarn-unused-matches -fwarn-unused-binds- -fwarn-unused-imports hs-source-dirs: test
+ src/Network/JSONApi.hs view
@@ -0,0 +1,28 @@+{- |+Entry-point module for this package.++Contains representations of the top-level JSON-API document structure.+-}+module Network.JSONApi+ ( D.Document+ , D.ErrorDocument (..)+ , E.Error (..)+ , D.ResourceData (..)+ , R.Relationship+ , R.Resource (..)+ , R.ResourcefulEntity (..)+ , R.Identifier (..)+ , L.Links+ , M.Meta (..)+ , M.MetaObject (..)+ , L.toLinks+ , R.mkRelationship+ , D.mkDocument+ , M.mkMeta+ ) where++import qualified Network.JSONApi.Error as E+import qualified Network.JSONApi.Document as D+import qualified Network.JSONApi.Link as L+import qualified Network.JSONApi.Meta as M+import qualified Network.JSONApi.Resource as R
src/Network/JSONApi/Document.hs view
@@ -4,23 +4,17 @@ Contains representations of the top-level JSON-API document structure. -} module Network.JSONApi.Document-( Document (..)+( Document , ErrorDocument (..)-, E.Error (..) , ResourceData (..)-, R.Relationship-, R.Resource (..)-, R.Identifier (..)-, L.Links-, M.Meta (..)-, L.toLinks-, R.mkRelationship+, mkDocument ) where import Control.Monad (mzero) import Data.Aeson ( ToJSON , FromJSON+ , Value , (.=) , (.:) , (.:?)@@ -30,7 +24,7 @@ import qualified Network.JSONApi.Error as E import Network.JSONApi.Link as L import Network.JSONApi.Meta as M-import Network.JSONApi.Resource (Resource)+import Network.JSONApi.Resource (Resource, ResourcefulEntity) import qualified Network.JSONApi.Resource as R {- |@@ -41,13 +35,58 @@ For more information see: <http://jsonapi.org/format/#document-top-level> -}-data Document a b c = Document- { _data :: ResourceData a b+data Document a = Document+ { _data :: ResourceData a , _links :: Maybe Links- , _meta :: Maybe (Meta c)+ , _meta :: Maybe Meta+ , _included :: [Value] } deriving (Show, Eq, G.Generic) +instance (ToJSON a)+ => ToJSON (Document a) where+ toJSON (Document (List res) links meta included) =+ AE.object [ "data" .= res+ , "links" .= links+ , "meta" .= meta+ , "included" .= included+ ]+ toJSON (Document (Singleton res) links meta included) =+ AE.object [ "data" .= res+ , "links" .= links+ , "meta" .= meta+ , "included" .= included+ ]++instance (FromJSON a) => FromJSON (Document a) where+ parseJSON = AE.withObject "document" $ \v -> do+ d <- v .: "data"+ l <- v .:? "links"+ m <- v .:? "meta"+ i <- v .: "included"+ return (Document d l m i)+ {- |+Constructor function for the Document data type.+-}+mkDocument :: ResourcefulEntity a =>+ [a]+ -> Maybe Links+ -> Maybe Meta+ -> [Value]+ -> Document a+mkDocument res links meta included =+ Document+ { _data = toResourceData res+ , _links = links+ , _meta = meta+ , _included = included+ }++toResourceData :: ResourcefulEntity a => [a] -> ResourceData a+toResourceData (r:[]) = Singleton (R.toResource r)+toResourceData rs = List (map R.toResource rs)++{- | The @Resource@ type encapsulates the underlying 'Resource' Included in the top-level 'Document', the @Resource@ may be either@@ -55,38 +94,19 @@ For more information see: <http://jsonapi.org/format/#document-top-level> -}-data ResourceData a b = Singleton (Resource a b)- | List [Resource a b]+data ResourceData a = Singleton (Resource a)+ | List [ Resource a ] deriving (Show, Eq, G.Generic) -instance (ToJSON a, ToJSON b) => ToJSON (ResourceData a b) where+instance (ToJSON a) => ToJSON (ResourceData a) where toJSON (Singleton res) = AE.toJSON res toJSON (List res) = AE.toJSON res -instance (FromJSON a, FromJSON b) => FromJSON (ResourceData a b) where+instance (FromJSON a) => FromJSON (ResourceData a) where parseJSON (AE.Object v) = Singleton <$> (AE.parseJSON (AE.Object v)) parseJSON (AE.Array v) = List <$> (AE.parseJSON (AE.Array v)) parseJSON _ = mzero -instance (ToJSON a, ToJSON b, ToJSON c) => ToJSON (Document a b c) where- toJSON (Document (List res) links meta) =- AE.object [ "data" .= res- , "links" .= links- , "meta" .= meta- ]- toJSON (Document (Singleton res) links meta) =- AE.object [ "data" .= res- , "links" .= links- , "meta" .= meta- ]--instance (FromJSON a, FromJSON b, FromJSON c) => FromJSON (Document a b c) where- parseJSON = AE.withObject "document" $ \v -> do- d <- v .: "data"- l <- v .:? "links"- m <- v .:? "meta"- return (Document d l m)- {- | The @ErrorDocument@ type represents the alternative form of the top-level JSON-API requirement.@@ -96,20 +116,20 @@ For more information see: <http://jsonapi.org/format/#errors> -}-data ErrorDocument a b = ErrorDocument+data ErrorDocument a = ErrorDocument { _error :: E.Error a , _errorLinks :: Maybe Links- , _errorMeta :: Maybe (Meta b)+ , _errorMeta :: Maybe Meta } deriving (Show, Eq, G.Generic) -instance (ToJSON a, ToJSON b) => ToJSON (ErrorDocument a b) where+instance (ToJSON a) => ToJSON (ErrorDocument a) where toJSON (ErrorDocument err links meta) = AE.object [ "error" .= err , "links" .= links , "meta" .= meta ] -instance (FromJSON a, FromJSON b) => FromJSON (ErrorDocument a b) where+instance (FromJSON a) => FromJSON (ErrorDocument a) where parseJSON = AE.withObject "error" $ \v -> ErrorDocument <$> v .: "error"
src/Network/JSONApi/Error.hs view
@@ -31,7 +31,7 @@ , code :: Maybe Text , title :: Maybe Text , detail :: Maybe Text- , meta :: Maybe (Meta a)+ , meta :: Maybe Meta } deriving (Show, Eq, G.Generic)
src/Network/JSONApi/Meta.hs view
@@ -3,10 +3,14 @@ Specification: <http://jsonapi.org/format/#document-meta> -}-module Network.JSONApi.Meta where+module Network.JSONApi.Meta+( Meta (..)+, MetaObject (..)+, mkMeta+)where -import Data.Aeson (ToJSON, FromJSON)-import Data.Map (Map)+import Data.Aeson (ToJSON, FromJSON, Object, toJSON)+import Data.HashMap.Strict as HM import Data.Text (Text) import qualified GHC.Generics as G @@ -31,8 +35,41 @@ Specification: <http://jsonapi.org/format/#document-meta> -}-data Meta a = Meta (Map Text a)- deriving (Show, Eq, Ord, G.Generic)+data Meta = Meta Object+ deriving (Show, Eq, G.Generic) -instance ToJSON a => ToJSON (Meta a)-instance FromJSON a => FromJSON (Meta a)+instance ToJSON Meta+instance FromJSON Meta++instance Monoid Meta where+ mappend (Meta a) (Meta b) = Meta $ HM.union a b+ mempty = Meta $ HM.empty++{- |+Convienience class for constructing a Meta type++Example usage:+@+data Pagination = Pagination+ { currentPage :: Int+ , totalPages :: Int+ } deriving (Show, Generic)++instance ToJSON Pagination+instance MetaObject Pagination where+ typeName _ = "pagination"+@+-}+class (ToJSON a) => MetaObject a where+ typeName :: a -> Text++{- |+Convienience constructor function for the Meta type++Useful on its own or in combination with Meta's monoid instance++Example usage:+See MetaSpec.hs for an example+-}+mkMeta :: (MetaObject a) => a -> Meta+mkMeta obj = Meta $ HM.singleton (typeName obj) (toJSON obj)
src/Network/JSONApi/Resource.hs view
@@ -6,6 +6,7 @@ module Network.JSONApi.Resource ( Identifier (..) , Resource (..)+, ResourcefulEntity (..) , Relationship , mkRelationship ) where@@ -28,15 +29,15 @@ Specification: <http://jsonapi.org/format/#document-resource-objects> -}-data Resource a b = Resource+data Resource a = Resource { getIdentifier :: Identifier , getResource :: a , getLinks :: Maybe Links- , getMetaData :: Maybe (Meta b)+ , getMetaData :: Maybe Meta , getRelationships :: Maybe (Map Text Relationship) } deriving (Show, Eq, G.Generic) -instance (ToJSON a, ToJSON b) => ToJSON (Resource a b) where+instance (ToJSON a) => ToJSON (Resource a) where toJSON (Resource (Identifier resId resType) resObj linksObj metaObj rels) = AE.object [ "id" .= resId , "type" .= resType@@ -46,7 +47,7 @@ , "relationships" .= rels ] -instance (FromJSON a, FromJSON b) => FromJSON (Resource a b) where+instance (FromJSON a) => FromJSON (Resource a) where parseJSON = AE.withObject "resourceObject" $ \v -> do id <- v .: "id" typ <- v .: "type"@@ -56,8 +57,27 @@ rels <- v .:? "relationships" return $ Resource (Identifier id typ) attrs links meta rels +{- |+A typeclass for decorating an entity with JSON API properties+-}+class (ToJSON a, FromJSON a) => ResourcefulEntity a where+ resourceIdentifier :: a -> Text+ resourceType :: a -> Text+ resourceLinks :: a -> Maybe Links+ resourceMetaData :: a -> Maybe Meta+ resourceRelationships :: a -> Maybe (Map Text Relationship) + fromResource :: Resource a -> a+ fromResource = getResource + toResource :: a -> Resource a+ toResource a =+ Resource+ (Identifier (resourceIdentifier a) (resourceType a))+ a+ (resourceLinks a)+ (resourceMetaData a)+ (resourceRelationships a) {- | A type representing the Relationship between 2 entities@@ -88,8 +108,6 @@ 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
test/Network/JSONApi/DocumentSpec.hs view
@@ -1,16 +1,16 @@ module Network.JSONApi.DocumentSpec where -import Control.Lens ((^?))-import Data.Aeson (ToJSON)+import Control.Lens ((^?))+import Data.Aeson (ToJSON) import qualified Data.Aeson as AE import qualified Data.Aeson.Lens as Lens-import Data.ByteString.Lazy.Char8 (ByteString)+import Data.ByteString.Lazy.Char8 (ByteString) {- import qualified Data.ByteString.Lazy.Char8 as BS -}-import Data.Either (isRight)-import Data.Maybe-import Network.JSONApi.Document-import TestHelpers-import Test.Hspec+import Data.Either (isRight)+import Data.Maybe+import Network.JSONApi.Document+import TestHelpers+import Test.Hspec main :: IO () main = hspec spec@@ -19,8 +19,8 @@ spec = describe "JSON serialization" $ do it "JSON encodes/decodes a singleton resource" $ do- let resource = toResource testObject- let jsonApiObj = Document (Singleton resource) emptyLinks emptyMeta+ -- TODO: test the main resource actually is a singleton+ let jsonApiObj = mkDocument [testObject] Nothing Nothing [] let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson {- putStrLn (BS.unpack encodedJson) -}@@ -28,8 +28,8 @@ isRight decodedJson `shouldBe` True it "JSON encodes/decodes a list of resources" $ do- let resources = [toResource testObject, toResource testObject2]- let jsonApiObj = Document (List resources) emptyLinks emptyMeta+ -- TODO: test the main resource actually is a list+ let jsonApiObj = mkDocument [testObject, testObject2] Nothing Nothing [] let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson {- putStrLn (BS.unpack encodedJson) -}@@ -37,47 +37,44 @@ isRight decodedJson `shouldBe` True it "contains the allowable top-level keys" $ do- let resource = toResource testObject- let jsonApiObj = Document (Singleton resource) emptyLinks emptyMeta+ let jsonApiObj = mkDocument [testObject] Nothing Nothing [] let encodedJson = encodeDocumentObject jsonApiObj let dataObject = encodedJson ^? Lens.key "data" let linksObject = encodedJson ^? Lens.key "links" let metaObject = encodedJson ^? Lens.key "meta"+ let includedObject = encodedJson ^? Lens.key "included" isJust dataObject `shouldBe` True isJust linksObject `shouldBe` True isJust metaObject `shouldBe` True+ isJust includedObject `shouldBe` True it "allows an optional top-level links object" $ do- let resource = toResource testObject- let jsonApiObj = Document (Singleton resource) (Just linksObj) emptyMeta+ let jsonApiObj = mkDocument [testObject] (Just linksObj) Nothing [] let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson -- putStrLn (BS.unpack encodedJson)- -- putStrLn $ show . fromJust $ decodedJson+ -- putStrLn $ show decodedJson isRight decodedJson `shouldBe` True it "allows an optional top-level meta object" $ do- let resource = toResource testObject- let jsonApiObj = Document (Singleton resource) emptyLinks (Just testMetaObj)+ let jsonApiObj = mkDocument [testObject] Nothing (Just testMetaObj) [] let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson -- putStrLn (BS.unpack encodedJson)- -- putStrLn $ show . fromJust $ decodedJson+ -- putStrLn $ show decodedJson isRight decodedJson `shouldBe` True - it "allows an optional top-level meta object" $ do- let resource = toResource testObject- let jsonApiObj = Document (Singleton resource) (Just linksObj) (Just testMetaObj)+ it "allows a heterogeneous list of related resources" $ do+ let jsonApiObj = mkDocument [testObject] Nothing Nothing [AE.toJSON (toResource' testObject Nothing Nothing), AE.toJSON (toResource' otherTestObject Nothing Nothing)] let encodedJson = encodeDocumentObject jsonApiObj let decodedJson = decodeDocumentObject encodedJson- -- putStrLn (BS.unpack encodedJson)- -- putStrLn $ show . fromJust $ decodedJson+ {- putStrLn (BS.unpack encodedJson) -}+ {- putStrLn $ show decodedJson -} isRight decodedJson `shouldBe` True decodeDocumentObject :: ByteString- -> Either String (Document TestResource (Maybe Bool) (Maybe TestMetaObject))+ -> Either String (Document TestResource) decodeDocumentObject = AE.eitherDecode -encodeDocumentObject :: (ToJSON a, ToJSON b, ToJSON c) => Document a b c -> ByteString+encodeDocumentObject :: (ToJSON a) => Document a -> ByteString encodeDocumentObject = prettyEncode-
test/Network/JSONApi/ErrorSpec.hs view
@@ -13,7 +13,7 @@ main = hspec spec spec :: Spec-spec =+spec = do describe "Defaults" $ do it "provides defaults" $ let expectedDefault = Error@@ -27,6 +27,7 @@ } in (def::Error Int) `shouldBe` expectedDefault + describe "JSON serialization" $ it "provides ToJSON/FromJSON instances" $ do let testError = (def::Error Int) let encJson = BS.unpack . prettyEncode $ testError
test/Network/JSONApi/MetaSpec.hs view
@@ -1,9 +1,13 @@ module Network.JSONApi.MetaSpec where +import Data.Aeson (ToJSON) import qualified Data.Aeson as AE import qualified Data.ByteString.Lazy.Char8 as BS-import qualified Data.Map as Map+import qualified Data.HashMap.Strict as HM import Data.Maybe (isJust)+import Data.Monoid ((<>))+import Data.Text (Text)+import GHC.Generics (Generic) import Network.JSONApi.Meta import TestHelpers (prettyEncode) import Test.Hspec@@ -12,22 +16,55 @@ main = hspec spec spec :: Spec-spec =- describe "serialization" $- -- is there a compelling reason to test this?- --- it "serializes/deserializes primitive values" $ do- let intTestData = Meta . Map.fromList $ [ ("numData", 5 :: Int) ]- let encIntJson = BS.unpack . prettyEncode $ intTestData- let decIntJson = AE.decode (BS.pack encIntJson) :: Maybe (Meta Int)+spec = do+ describe "JSON serialization" $ do+ it "serializes/deserializes maps simple heterogeneous values" $ do+ let testMeta = Meta . HM.fromList $ [ ("numData", AE.toJSON (5 :: Int))+ , ("strData", AE.toJSON ("hello" :: String))+ ]+ let encIntJson = BS.unpack . prettyEncode $ testMeta+ let decIntJson = AE.decode (BS.pack encIntJson) :: Maybe Meta isJust decIntJson `shouldBe` True- -- putStrLn (keys encIntJson)- -- putStrLn $ show . fromJust $ decIntJson - let boolTestData = Meta . Map.fromList $ [ ("boolData", True) ]+ it "serializes/deserializes heterogeneous maps of ToJSON types" $ do+ let boolTestData = Meta . HM.fromList $ [ ("objData", AE.toJSON testObject)+ , ("otherObjData", AE.toJSON otherTestObject)+ ] let encBoolJson = BS.unpack . prettyEncode $ boolTestData- let decBoolJson = AE.decode (BS.pack encBoolJson) :: Maybe (Meta Bool)+ let decBoolJson = AE.decode (BS.pack encBoolJson) :: Maybe Meta isJust decBoolJson `shouldBe` True- -- putStrLn (keys encBoolJson)- -- putStrLn $ show . fromJust $ decBoolJson + describe "monoid instance" $+ it "combines" $ do+ let monoidialConstruction = mkMeta testObject <> mkMeta otherTestObject+ let manualConstruction = Meta . HM.fromList $ [ ("testObject", AE.toJSON testObject)+ , ("otherTestObject", AE.toJSON otherTestObject)+ ]+ monoidialConstruction `shouldBe` manualConstruction++testObject :: TestObject+testObject = TestObject 99 102 "Zapp Brannigan"++otherTestObject :: OtherTestObject+otherTestObject = OtherTestObject "Olive Garden" "Woofers" 29 "TGIFriday's"++data TestObject = TestObject+ { myID :: Int+ , myAge :: Int+ , myName :: Text+ } deriving (Show, Generic)++instance ToJSON TestObject+instance MetaObject TestObject where+ typeName _ = "testObject"++data OtherTestObject = OtherTestObject+ { myFavoriteRestaurant :: Text+ , myDogsName :: Text+ , myDogsAge :: Int+ , myDogsFavoriteRestarant :: Text+ } deriving (Show, Generic)++instance ToJSON OtherTestObject+instance MetaObject OtherTestObject where+ typeName _ = "otherTestObject"
test/Network/JSONApi/ResourceSpec.hs view
@@ -4,6 +4,7 @@ import qualified Data.ByteString.Lazy.Char8 as BS import Data.Map (Map) import qualified Data.Map as Map+import qualified Data.HashMap.Strict as HM import Data.Maybe (isJust, fromJust) import Data.Text (Text, pack) import qualified GHC.Generics as G@@ -19,10 +20,10 @@ spec :: Spec spec =- describe "ToResource" $+ describe "JSON serialization" $ 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))+ let decodedJson = AE.decode (BS.pack encodedJson) :: Maybe (Resource TestObject) isJust decodedJson `shouldBe` True {- putStrLn encodedJson -} {- putStrLn $ show . fromJust $ decodedJson -}@@ -36,33 +37,30 @@ 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)+instance ResourcefulEntity TestObject where+ resourceIdentifier = pack . show . myId+ resourceType _ = "TestObject"+ resourceLinks _ = Just myResourceLinks+ resourceMetaData _ = Just myResourceMetaData+ resourceRelationships _ = Just myResourceRelationships -resourceRelationships :: Map Text Relationship-resourceRelationships = Map.fromList $ [ ("friends", relationship) ]+myResourceRelationships :: Map Text Relationship+myResourceRelationships = Map.fromList $ [ ("friends", relationship) ] relationship :: Relationship relationship = fromJust $ mkRelationship (Just $ Identifier "42" "FriendOfTestObject")- (Just resourceLinks)+ (Just myResourceLinks) -resourceLinks :: Links-resourceLinks =+myResourceLinks :: Links+myResourceLinks = toLinks [ ("self", toURL "/me") , ("related", toURL "/tacos/4") ] -resourceMetaData :: Meta Int-resourceMetaData = Meta . Map.fromList $ [ ("extraData", 20) ]+myResourceMetaData :: Meta+myResourceMetaData = Meta . HM.fromList $ [ ("extraData", AE.toJSON (20 :: Int)) ] toURL :: String -> URL toURL = fromJust . importURL
test/TestHelpers.hs view
@@ -4,11 +4,10 @@ import qualified Data.Aeson.Encode.Pretty as AE import qualified Data.ByteString.Lazy.Char8 as BS import Data.Maybe (fromJust)-import qualified Data.Map as Map-import Data.Monoid ((<>))+import qualified Data.HashMap.Strict as HM import Data.Text (Text, pack)-import qualified GHC.Generics as G-import Network.JSONApi.Document+import GHC.Generics (Generic)+import Network.JSONApi import Network.URL (URL, importURL) prettyEncode :: AE.ToJSON a => a -> BS.ByteString@@ -17,43 +16,68 @@ prettyConfig :: AE.Config prettyConfig = AE.Config { AE.confIndent = 2, AE.confCompare = mempty } +class HasIdentifiers a where+ uniqueId :: a -> Int+ typeDescriptor :: a -> Text+ data TestResource = TestResource { myId :: Int , myName :: Text , myAge :: Int , myFavoriteFood :: Text- } deriving (Show, G.Generic)+ } deriving (Show, Generic) +instance AE.ToJSON TestResource+instance AE.FromJSON TestResource+instance ResourcefulEntity TestResource where+ resourceIdentifier = pack . show . myId+ resourceType _ = "testResource"+ resourceLinks _ = Nothing+ resourceMetaData _ = Nothing+ resourceRelationships _ = Nothing+instance HasIdentifiers TestResource where+ uniqueId = myId+ typeDescriptor _ = "TestResource"++data OtherTestResource = OtherTestResource+ { myFavoriteNumber :: Int+ , myJob :: Text+ , myPay :: Int+ , myEmployer :: Text+ } deriving (Show, Generic)++instance AE.ToJSON OtherTestResource+instance AE.FromJSON OtherTestResource+instance ResourcefulEntity OtherTestResource where+ resourceIdentifier = pack . show . myFavoriteNumber+ resourceType _ = "otherTestResource"+ resourceLinks _ = Nothing+ resourceMetaData _ = Nothing+ resourceRelationships _ = Nothing+instance HasIdentifiers OtherTestResource where+ uniqueId = myFavoriteNumber+ typeDescriptor _ = "OtherTestResource"+ data TestMetaObject = TestMetaObject { totalPages :: Int , isSuperFun :: Bool- } deriving (Show, G.Generic)--instance AE.ToJSON TestResource-instance AE.FromJSON TestResource+ } deriving (Show, Generic) instance AE.ToJSON TestMetaObject instance AE.FromJSON TestMetaObject -toResource :: TestResource -> Resource TestResource Bool-toResource obj =+toResource' :: (HasIdentifiers a) => a+ -> Maybe Links+ -> Maybe Meta+ -> Resource a+toResource' obj links meta = Resource- (Identifier (pack . show . myId $ obj) "TestResource")+ (Identifier (pack . show . uniqueId $ obj) (typeDescriptor obj)) obj- (Just $ objectLinks obj)- (Just $ objectMetaData obj)+ links+ meta Nothing -objectLinks :: TestResource -> Links-objectLinks obj =- toLinks [ ("self", toURL ("/me/" <> (show $ myId obj)))- , ("related", toURL ("/friends/" <> (show $ myId obj)))- ]--objectMetaData :: TestResource -> Meta Bool-objectMetaData obj =- Meta . Map.fromList $ [ ("isOld", myAge obj > 50) ]- linksObj :: Links linksObj = toLinks [ ("self", toURL "/things/1") , ("related", toURL "http://some.domain.com/other/things/1")@@ -65,11 +89,14 @@ testObject2 :: TestResource testObject2 = TestResource 2 "Carrie Brownstein" 35 "Lunch" -testMetaObj :: Meta TestMetaObject+otherTestObject :: OtherTestResource+otherTestObject = OtherTestResource 999 "Atom Smasher" 100 "Atom Smashers, Inc"++testMetaObj :: Meta testMetaObj =- Meta . Map.fromList $ [ ("importantData", TestMetaObject 3 True) ]+ Meta . HM.fromList $ [ ("importantData", (AE.toJSON $ TestMetaObject 3 True)) ] -emptyMeta :: Maybe (Meta TestMetaObject)+emptyMeta :: Maybe Meta emptyMeta = Nothing toURL :: String -> URL