-- | Dereferencing
-- | A restricted monad for writing dereferencing "scripts".
newtype Fetch o
= Fetch { eff :: Eff '[Reader State, NonDet, Sign, HTTP, Fail] o }
deriving newtype (Functor, Applicative, Alternative, Monad, MonadFail)
data State = State { value :: JSON.Value, depth :: Int }
runFetch
:: (HTTP :> es, Sign :> es, Fail :> es)
=> JSON.Value
-> Fetch o
-> Eff es o
runFetch json (Fetch eff) = do
-- `inject` narrows the list of effects expected by `runNonDet`
-- to the restricted subset of `eff` (see the `toPoly` example
-- in the haddocs for `inject`)
result <- inject eff
& runBacktrack
-- Keeping the state is more efficient according to the docs
& runNonDet OnEmptyKeep
& runReader (State json 0)
case result of
Left _ -> fail "Failed to fetch"
Right ok -> do
-- TODO: Add caching here
pure ok
where
-- | Backtrack on failures by triggering NonDet.
runBacktrack = interpret $ \_ (Fail _) -> empty
10
-- | Fetch a URL from the interwebs.
Fetch $ do
response <- get url
case JSON.eitherDecode (fromStrict response.body) of
Left err -> fail ("Error with decoding response: " <> err <> "; data: " <> show response.body)
Right value -> pure value
Fetch $ do
-- Increment the depth so we don't end up in a dereferencing loop
local (\st -> st { depth = st.depth + 1 }) $ do
-- Check depth limiter
current <- asks depth
when (current >= limit) $ do
fail "Depth limit exceeded"
-- Extract the url to request the data from
val <- (parse key).eff
result <- case val of
JSON.String url -> (fetch url).eff
JSON.Object _ -> pure val
_ -> fail ("No url at key " <> show key)
-- Execute the dereferencer
runFetch result dereferencer
Fetch $ do
val <- asks value
case extract val >>= decoder of
Just x -> pure x
Nothing -> fail ("Failed to parse field " <> show key)
where
decoder = JSON.parseMaybe JSON.parseJSON
extract = \case { JSON.Object obj -> obj !? key; _ -> Nothing }
Fetch $ do
val <- asks value
obj <- case val of
JSON.Object obj -> case obj !? key of
Just value -> pure value
Nothing -> fail ("Expected " <> show key <> " to be present")
-- TODO: catch this case earlier
_ -> fail "Expected an object"
local
(\st -> st { value = obj })
action.eff
dereferencer = Activity
<$> parse "id"
<*> parse "actor"
<*> deref "object"
<*> (parse "to" <|> pure [])
<*> (parse "cc" <|> pure [])
<*> optional (parse "published")
<*> parse "type"
dereferencer = do
publicKey <- focus "publicKey" $ do
PublicKey <$> parse "id"
<*> parse "owner"
<*> (fromJust . decodePublicKey . T.encodeUtf8 <$> parse "publicKeyPem")
Actor
<$> parse "id"
<*> optional (parse "inbox")
<*> optional (parse "outbox")
<*> optional (parse "followers")
<*> optional (parse "following")
<*> parse "preferredUsername"
<*> optional (parse "name")
<*> optional (parse "summary")
<*> return publicKey
<*> (parse "manuallyApprovesFollowers" <|> pure True)
<*> parse "type"
dereferencer = Document
<$> parse "id"
<*> optional (parse "content")
<*> optional (parse "summary")
<*> (parse "type" <|> pure Note)
dereferencer =
(toObject <$> (dereferencer :: Fetch Actor)) <|>
(toObject <$> (dereferencer :: Fetch (Activity Object))) <|>
(toObject <$> (dereferencer :: Fetch Document))