{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {- | Module : Database.Couch.Response Description : Utilities for extracting specific types from Database.Couch JSON values Copyright : Copyright (c) 2015, Michael Alan Dorman License : MIT Maintainer : mdorman@jaunder.io Stability : experimental Portability : POSIX Calls to CouchDB can return values that have well-defined structure beyond their simple JSON content, but that don't necessarily warrant full-blown data types with 'FromJSON' instances and the like. We want to provide convenient conversions when that is the case. > result <- Database.compact cxt `ap` asBool > if result > then ... > else ... -} module Database.Couch.Response where import Control.Monad ((>>=)) import Data.Aeson (FromJSON, Value (Object), fromJSON) import qualified Data.Aeson as Aeson (Result (Error, Success)) import Data.Bool (Bool) import Data.Either (Either (Left, Right)) import Data.Function (($), (.)) import Data.Functor (fmap) import Data.HashMap.Strict (lookup) import Data.Maybe (Maybe (Just, Nothing), catMaybes, maybe) import Data.String (fromString) import Data.Text (Text, intercalate, splitAt) import Data.Text.Encoding (encodeUtf8) import Data.UUID (UUID, fromASCIIBytes) import Database.Couch.Types (Error (NotFound, ParseFail), Result) {- | Attempt to decode the value into anything with a FromJSON constraint. This is really about translating 'Data.Aeson.Result' values into our 'Database.Couch.Types.Result' values. -} asAnything :: FromJSON a => Result Value -> Result a asAnything v = case v of Left x -> Left x Right (a, b) -> case fromJSON a of Aeson.Error e -> (Left . ParseFail . fromString) e Aeson.Success s -> Right (s, b) {- | Attempt to construct a 'Data.Bool.Bool' value. This assumes the routine conforms to CouchDB's @{"ok": true}@ return convention. -} asBool :: Result Value -> Result Bool asBool = getKey "ok" {- | Attempt to construct a list of 'Data.UUID.UUID' values. CouchDB returns uuids as string values in a form that "Data.UUID" cannot consume directly, so we provide this standard conversion. -} asUUID :: Result Value -> Result [UUID] asUUID v = case v of Left x -> Left x Right (Object o, b) -> maybe (Left (ParseFail "Couldn't convert to UUID type")) (Right . (,b) . catMaybes . reformat) $ lookup "uuids" o _ -> Left NotFound where reformat i = case fromJSON i of Aeson.Error _ -> [] Aeson.Success a -> fmap (fromASCIIBytes . encodeUtf8 . reformatUuid) a reformatUuid s = let (first, second') = splitAt 8 s (second, third') = splitAt 4 second' (third, fourth') = splitAt 4 third' (fourth, fifth) = splitAt 4 fourth' in intercalate "-" [first, second, third, fourth, fifth] {- | Attempt to extract the value of a particular key. -} getKey :: FromJSON a => Text -> Result Value -> Result a getKey k v = case v of Left x -> Left x Right (Object o, b) -> maybe (Left NotFound) (Right . (, b)) $ lookup k o >>= reformat _ -> Left NotFound where reformat i = case fromJSON i of Aeson.Error _ -> Nothing Aeson.Success a -> Just a