Dynamic.hs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. {-# LANGUAGE OverloadedStrings #-}
  2. {-# LANGUAGE RankNTypes #-}
  3. {-|
  4. Module : Codec.ActivityStream.Dynamic
  5. Description : A (more dynamic) interface to Activity Streams
  6. Copyright : (c) Getty Ritter, 2014
  7. Maintainer : gdritter@galois.com
  8. This is an interface to ActivityStreams that simply wraps an underlying
  9. @aeson@ Object, and exposes a set of (convenient) lenses to access the
  10. values inside. If an @aeson@ object is wrapped in the respective wrapper,
  11. it will contain the obligatory values for that type (e.g. an @Activity@
  12. is guaranteed to have a @published@ date.)
  13. -}
  14. module Codec.ActivityStream.Dynamic
  15. ( Lens'
  16. -- * MediaLink
  17. , MediaLink
  18. , mlDuration
  19. , mlHeight
  20. , mlWidth
  21. , mlURL
  22. , mlRest
  23. , makeMediaLink
  24. -- * Object
  25. , Object
  26. , oAttachments
  27. , oAuthor
  28. , oContent
  29. , oDisplayName
  30. , oDownstreamDuplicates
  31. , oId
  32. , oImage
  33. , oObjectType
  34. , oPublished
  35. , oSummary
  36. , oUpdated
  37. , oUpstreamDuplicates
  38. , oURL
  39. , oRest
  40. , emptyObject
  41. -- * Activity
  42. , Activity
  43. , acActor
  44. , acContent
  45. , acGenerator
  46. , acIcon
  47. , acId
  48. , acPublished
  49. , acProvider
  50. , acTarget
  51. , acTitle
  52. , acUpdated
  53. , acURL
  54. , acVerb
  55. , acRest
  56. , makeActivity
  57. -- * Collection
  58. , Collection
  59. , cTotalItems
  60. , cItems
  61. , cURL
  62. , cRest
  63. , makeCollection
  64. ) where
  65. import Data.Aeson ( FromJSON(..)
  66. , ToJSON(..)
  67. , Result(..)
  68. , fromJSON
  69. )
  70. import qualified Data.Aeson as A
  71. import Data.DateTime (DateTime)
  72. import qualified Data.HashMap.Strict as HM
  73. import Data.Maybe (fromJust)
  74. import Data.Text (Text)
  75. -- This way, we don't have to import lens... but we can still export lenses!
  76. newtype Const a b = Const { fromConst :: a }
  77. instance Functor (Const a) where fmap f (Const x) = Const x
  78. -- We need these to write get and set
  79. newtype Id a = Id { fromId :: a }
  80. instance Functor Id where fmap f (Id x) = Id (f x)
  81. -- | This is the same type alias as in @Control.Lens@, and so can be used
  82. -- anywhere lenses are needed.
  83. type Lens' a b = forall f. Functor f => (b -> f b) -> (a -> f a)
  84. get :: Lens' a b -> a -> b
  85. get lens a = fromConst (lens Const a)
  86. set :: Lens' a b -> b -> a -> a
  87. set lens x a = fromId (lens (const Id x) a)
  88. makeLens :: (a -> b) -> (b -> a -> a) -> Lens' a b
  89. makeLens get set f a = (`set` a) `fmap` f (get a)
  90. fromJSON' :: FromJSON a => A.Value -> Maybe a
  91. fromJSON' v = case fromJSON v of
  92. Success a -> Just a
  93. Error _ -> Nothing
  94. -- Create a lens into an Aeson object wrapper that takes and
  95. -- returns a Maybe value
  96. makeAesonLensMb :: (FromJSON v, ToJSON v)
  97. => Text -> Lens' c A.Object -> Lens' c (Maybe v)
  98. makeAesonLensMb key fromObj = fromObj . lens
  99. where lens = makeLens
  100. (\ o -> HM.lookup key o >>= fromJSON')
  101. (\ v o -> HM.insert key (toJSON (Just v)) o)
  102. -- Create a lens into an Aeson object wrapper
  103. makeAesonLens :: (FromJSON v, ToJSON v) => Text -> Lens' c A.Object -> Lens' c v
  104. makeAesonLens key fromObj = fromObj . lens
  105. where lens = makeLens
  106. (\ o -> fromJust (HM.lookup key o >>= fromJSON'))
  107. (\ v o -> HM.insert key (toJSON v) o)
  108. data MediaLink = MediaLink { fromMediaLink :: A.Object } deriving (Eq, Show)
  109. instance FromJSON MediaLink where
  110. parseJSON (A.Object o) | HM.member "url" o = return (MediaLink o)
  111. | otherwise = fail "..."
  112. parseJSON _ = fail "..."
  113. instance ToJSON MediaLink where
  114. toJSON (MediaLink o) = A.Object o
  115. mlRest :: Lens' MediaLink A.Object
  116. mlRest = makeLens fromMediaLink (\ o' m -> m { fromMediaLink = o' })
  117. mlDuration :: Lens' MediaLink (Maybe Int)
  118. mlDuration = makeAesonLensMb "duration" mlRest
  119. mlHeight :: Lens' MediaLink (Maybe Int)
  120. mlHeight = makeAesonLensMb "height" mlRest
  121. mlWidth :: Lens' MediaLink (Maybe Int)
  122. mlWidth = makeAesonLensMb "width" mlRest
  123. mlURL :: Lens' MediaLink Text
  124. mlURL = makeAesonLens "url" mlRest
  125. -- | Create a @MediaLink@ with just a @url@ property.
  126. makeMediaLink :: Text -> MediaLink
  127. makeMediaLink url = MediaLink (HM.insert "url" (toJSON url) HM.empty)
  128. -- | Object
  129. data Object = Object { fromObject :: A.Object } deriving (Eq, Show)
  130. instance FromJSON Object where
  131. parseJSON (A.Object o) = return (Object o)
  132. parseJSON _ = fail "..."
  133. instance ToJSON Object where
  134. toJSON (Object o) = A.Object o
  135. oRest :: Lens' Object A.Object
  136. oRest = makeLens fromObject (\ o' m -> m { fromObject = o' })
  137. oAttachments :: Lens' Object (Maybe [Object])
  138. oAttachments = makeAesonLensMb "attachments" oRest
  139. oAuthor :: Lens' Object (Maybe Object)
  140. oAuthor = makeAesonLensMb "author" oRest
  141. oContent :: Lens' Object (Maybe Text)
  142. oContent = makeAesonLensMb "content" oRest
  143. oDisplayName :: Lens' Object (Maybe Text)
  144. oDisplayName = makeAesonLensMb "displayName" oRest
  145. oDownstreamDuplicates :: Lens' Object (Maybe [Text])
  146. oDownstreamDuplicates = makeAesonLensMb "downstreamDuplicates" oRest
  147. oId :: Lens' Object (Maybe Text)
  148. oId = makeAesonLensMb "id" oRest
  149. oImage :: Lens' Object (Maybe MediaLink)
  150. oImage = makeAesonLensMb "image" oRest
  151. oObjectType :: (FromJSON o, ToJSON o) => Lens' Object (Maybe o)
  152. oObjectType = makeAesonLensMb "objectType" oRest
  153. oPublished :: Lens' Object (Maybe DateTime)
  154. oPublished = makeAesonLensMb "published" oRest
  155. oSummary :: Lens' Object (Maybe Text)
  156. oSummary = makeAesonLensMb "summary" oRest
  157. oUpdated :: Lens' Object (Maybe DateTime)
  158. oUpdated = makeAesonLensMb "updated" oRest
  159. oUpstreamDuplicates :: Lens' Object (Maybe [Text])
  160. oUpstreamDuplicates = makeAesonLensMb "upstreamDuplicates" oRest
  161. oURL :: Lens' Object (Maybe Text)
  162. oURL = makeAesonLensMb "url" oRest
  163. -- | Create an @Object@ with no fields.
  164. emptyObject :: Object
  165. emptyObject = Object HM.empty
  166. -- | Activity
  167. data Activity = Activity { fromActivity :: A.Object } deriving (Eq, Show)
  168. instance FromJSON Activity where
  169. parseJSON (A.Object o)
  170. | HM.member "published" o && HM.member "provider" o = return (Activity o)
  171. | otherwise = fail "..."
  172. parseJSON _ = fail "..."
  173. instance ToJSON Activity where
  174. toJSON (Activity o) = A.Object o
  175. acRest :: Lens' Activity A.Object
  176. acRest = makeLens fromActivity (\ o' m -> m { fromActivity = o' })
  177. acActor :: Lens' Activity Object
  178. acActor = makeAesonLens "actor" acRest
  179. acContent :: Lens' Activity (Maybe Text)
  180. acContent = makeAesonLensMb "content" acRest
  181. acGenerator :: Lens' Activity (Maybe Object)
  182. acGenerator = makeAesonLens "generator" acRest
  183. acIcon :: Lens' Activity (Maybe MediaLink)
  184. acIcon = makeAesonLensMb "icon" acRest
  185. acId :: Lens' Activity (Maybe Text)
  186. acId = makeAesonLensMb "id" acRest
  187. acPublished :: Lens' Activity DateTime
  188. acPublished = makeAesonLens "published" acRest
  189. acProvider :: Lens' Activity (Maybe Object)
  190. acProvider = makeAesonLensMb "provider" acRest
  191. acTarget :: Lens' Activity (Maybe Object)
  192. acTarget = makeAesonLensMb "target" acRest
  193. acTitle :: Lens' Activity (Maybe Text)
  194. acTitle = makeAesonLensMb "title" acRest
  195. acUpdated :: Lens' Activity (Maybe DateTime)
  196. acUpdated = makeAesonLensMb "updated" acRest
  197. acURL :: Lens' Activity (Maybe Text)
  198. acURL = makeAesonLensMb "url" acRest
  199. acVerb :: (FromJSON v, ToJSON v) => Lens' Activity (Maybe v)
  200. acVerb = makeAesonLensMb "verb" acRest
  201. -- | Create an @Activity@ with an @actor@, @published@, and
  202. -- @provider@ property.
  203. makeActivity :: Object -> DateTime -> Object -> Activity
  204. makeActivity actor published provider = Activity
  205. $ HM.insert "actor" (toJSON actor)
  206. $ HM.insert "published" (toJSON published)
  207. $ HM.insert "provider" (toJSON provider)
  208. $ HM.empty
  209. -- | Collection
  210. data Collection = Collection { fromCollection :: A.Object } deriving (Eq, Show)
  211. instance FromJSON Collection where
  212. parseJSON (A.Object o) = return (Collection o)
  213. parseJSON _ = fail "..."
  214. instance ToJSON Collection where
  215. toJSON (Collection o) = A.Object o
  216. cRest :: Lens' Collection A.Object
  217. cRest = makeLens fromCollection (\ o' m -> m { fromCollection = o' })
  218. cTotalItems :: Lens' Collection (Maybe Int)
  219. cTotalItems = makeAesonLensMb "totalItems" cRest
  220. cItems :: Lens' Collection (Maybe [Object])
  221. cItems = makeAesonLensMb "items" cRest
  222. cURL :: Lens' Collection (Maybe Text)
  223. cURL = makeAesonLensMb "url" cRest
  224. -- | Create a @Collection@ with an @items@ and a @url@ property
  225. -- and fill in the corresponding @totalItems@ field with the
  226. -- length of the @items@ array.
  227. makeCollection :: [Object] -> Text -> Collection
  228. makeCollection objs url = Collection
  229. $ HM.insert "totalItems" (toJSON (length objs))
  230. $ HM.insert "items" (toJSON objs)
  231. $ HM.insert "url" (toJSON url)
  232. $ HM.empty