cayley-client 0.1.5.1 → 0.2.0.0
raw patch · 7 files changed
+168/−36 lines, 7 filesdep +cayley-clientdep +hspecdep ~aesondep ~basedep ~unordered-containersPVP ok
version bump matches the API change (PVP)
Dependencies added: cayley-client, hspec
Dependency ranges changed: aeson, base, unordered-containers
API changes (from Hackage documentation)
- Database.Cayley.Internal: apiRequest :: Manager -> String -> Int -> RequestBody -> ReaderT CayleyConfig IO (Maybe Value)
- Database.Cayley.Internal: getConfig :: CayleyConnection -> CayleyConfig
- Database.Cayley.Internal: getManager :: CayleyConnection -> Manager
- Database.Cayley.Internal: toRequestBody :: [Quad] -> RequestBody
- Database.Cayley.Types: instance Data.Aeson.Types.Class.FromJSON Database.Cayley.Types.Quad
- Database.Cayley.Types: instance Data.Aeson.Types.Class.ToJSON Database.Cayley.Types.Quad
+ Database.Cayley.Client: data Shape
+ Database.Cayley.Client: queryShape :: CayleyConnection -> Query -> IO (Result Shape)
+ Database.Cayley.Types: Link :: Integer -> Integer -> Integer -> Link
+ Database.Cayley.Types: Node :: Integer -> Maybe [Tag] -> Maybe [Value] -> Bool -> Bool -> Node
+ Database.Cayley.Types: Shape :: [Node] -> [Link] -> Shape
+ Database.Cayley.Types: [id] :: Node -> Integer
+ Database.Cayley.Types: [isFixed] :: Node -> Bool
+ Database.Cayley.Types: [isLinkNode] :: Node -> Bool
+ Database.Cayley.Types: [linkNode] :: Link -> Integer
+ Database.Cayley.Types: [links] :: Shape -> [Link]
+ Database.Cayley.Types: [nodes] :: Shape -> [Node]
+ Database.Cayley.Types: [source] :: Link -> Integer
+ Database.Cayley.Types: [tags] :: Node -> Maybe [Tag]
+ Database.Cayley.Types: [target] :: Link -> Integer
+ Database.Cayley.Types: [values] :: Node -> Maybe [Value]
+ Database.Cayley.Types: data Link
+ Database.Cayley.Types: data Node
+ Database.Cayley.Types: data Shape
+ Database.Cayley.Types: instance Data.Aeson.Types.FromJSON.FromJSON Database.Cayley.Types.Quad
+ Database.Cayley.Types: instance Data.Aeson.Types.FromJSON.FromJSON Database.Cayley.Types.Shape
+ Database.Cayley.Types: instance Data.Aeson.Types.ToJSON.ToJSON Database.Cayley.Types.Quad
+ Database.Cayley.Types: instance GHC.Classes.Eq Database.Cayley.Types.Link
+ Database.Cayley.Types: instance GHC.Classes.Eq Database.Cayley.Types.Node
+ Database.Cayley.Types: instance GHC.Classes.Eq Database.Cayley.Types.Shape
+ Database.Cayley.Types: instance GHC.Show.Show Database.Cayley.Types.Link
+ Database.Cayley.Types: instance GHC.Show.Show Database.Cayley.Types.Node
+ Database.Cayley.Types: instance GHC.Show.Show Database.Cayley.Types.Shape
+ Database.Cayley.Types: parseLink :: Value -> Parser Link
+ Database.Cayley.Types: parseNode :: Value -> Parser Node
+ Database.Cayley.Types: type Tag = Text
+ Database.Cayley.Types: type Value = Text
Files
- Database/Cayley/Client.hs +40/−25
- Database/Cayley/Internal.hs +4/−1
- Database/Cayley/Types.hs +57/−7
- README.md +3/−0
- cayley-client.cabal +17/−3
- tests/hspec.hs +32/−0
- tests/testdata.nq +15/−0
Database/Cayley/Client.hs view
@@ -3,11 +3,16 @@ {-# LANGUAGE RecordWildCards #-} module Database.Cayley.Client (+ Quad (..)+ -- * Connect & query , defaultCayleyConfig , connectCayley , query+ , Shape+ , queryShape+ -- * REST API operations , write , writeQuad@@ -16,10 +21,12 @@ , delete , deleteQuad , deleteQuads+ -- * Utils , createQuad , isValid , successfulResults+ ) where import Control.Applicative ((<|>))@@ -57,12 +64,8 @@ doQuery m _q = do CayleyConfig {..} <- ask r <- apiRequest- m ("http://"- ++ serverName- ++ "/api/v"- ++ show apiVersion- ++ "/query/"- ++ show queryLang)+ m (urlBase serverName apiVersion+ ++ "/query/" ++ show queryLang) serverPort (RequestBodyBS _q) return $ case r of Just a ->@@ -73,11 +76,25 @@ Just e -> case A.fromJSON e of A.Success s -> Left s- A.Error _e -> Left _e+ A.Error _e -> Left _e Nothing -> Left "No JSON response from Cayley server" Nothing -> Left "Can't get any response from Cayley server" +-- | Return the description of the given query.+queryShape :: CayleyConnection -> Query -> IO (A.Result Shape)+queryShape c q =+ runReaderT (doShape (getManager c) (encodeUtf8 q)) (getConfig c)+ where+ doShape m _q = do+ CayleyConfig {..} <- ask+ r <- apiRequest+ m (urlBase serverName apiVersion ++ "/shape/" ++ show queryLang)+ serverPort (RequestBodyBS _q)+ case r of+ Just o -> return $ A.fromJSON o+ Nothing -> return $ A.Error "API request error"+ -- | Write a 'Quad' with the given subject, predicate, object and optional -- label. Throw result or extract amount of query 'successfulResults' -- from it.@@ -85,7 +102,12 @@ -- >λ> writeQuad conn "Humphrey" "loves" "Lauren" (Just "In love") -- >Just (Object (fromList [("result",String "Successfully wrote 1 quads.")])) ---writeQuad :: CayleyConnection -> Subject -> Predicate -> Object -> Maybe Label -> IO (Maybe A.Value)+writeQuad :: CayleyConnection+ -> Subject+ -> Predicate+ -> Object+ -> Maybe Label+ -> IO (Maybe A.Value) writeQuad c s p o l = writeQuads c [Quad { subject = s, predicate = p, object = o, label = l }] @@ -95,7 +117,12 @@ -- | Delete the 'Quad' defined by the given subject, predicate, object -- and optional label.-deleteQuad :: CayleyConnection -> Subject -> Predicate -> Object -> Maybe Label -> IO (Maybe A.Value)+deleteQuad :: CayleyConnection+ -> Subject+ -> Predicate+ -> Object+ -> Maybe Label+ -> IO (Maybe A.Value) deleteQuad c s p o l = deleteQuads c [Quad { subject = s, predicate = p, object = o, label = l }] @@ -111,11 +138,7 @@ _write m _qs = do CayleyConfig {..} <- ask apiRequest- m ("http://"- ++ serverName- ++ "/api/v"- ++ show apiVersion- ++ "/write")+ m (urlBase serverName apiVersion ++ "/write") serverPort (toRequestBody _qs) -- | Delete the given list of 'Quad'(s).@@ -126,11 +149,7 @@ _delete m _qs = do CayleyConfig {..} <- ask apiRequest- m ("http://"- ++ serverName- ++ "/api/v"- ++ show apiVersion- ++ "/delete")+ m (urlBase serverName apiVersion ++ "/delete") serverPort (toRequestBody _qs) @@ -148,11 +167,7 @@ where writenq m _p = do CayleyConfig {..} <- ask- r <- parseUrl ("http://"- ++ serverName- ++ "/api/v"- ++ show apiVersion- ++ "/write/file/nquad")+ r <- parseUrl (urlBase serverName apiVersion ++ "/write/file/nquad") >>= \r -> return r { port = serverPort } t <- liftIO $ try $@@ -160,7 +175,7 @@ =<< formDataBody [partFileSource "NQuadFile" _p] r return $ case t of Right _r -> A.decode $ responseBody _r- Left e -> Just $+ Left e -> Just $ A.object ["error" A..= T.pack (show (e :: SomeException))] -- | A valid 'Quad' has its subject, predicate and object not empty.
Database/Cayley/Internal.hs view
@@ -29,11 +29,14 @@ A.object ["error" A..= T.pack (show (e :: SomeException))] toRequestBody :: [Quad] -> RequestBody-toRequestBody qs = RequestBodyLBS $ A.encode $ fromList $ map A.toJSON qs+toRequestBody = RequestBodyLBS . A.encode . fromList . map A.toJSON getManager :: CayleyConnection -> Manager getManager (CayleyConnection (_,m)) = m getConfig :: CayleyConnection -> CayleyConfig getConfig (CayleyConnection (c,_)) = c++urlBase :: String -> APIVersion -> String+urlBase s a = "http://" ++ s ++ "/api/v" ++ show a
Database/Cayley/Types.hs view
@@ -2,9 +2,11 @@ module Database.Cayley.Types where -import Control.Monad+import Control.Monad (mzero) import qualified Data.Aeson as A+import qualified Data.Aeson.Types as AT import qualified Data.Text as T+import qualified Data.Vector as V import Network.HTTP.Client (Manager) data APIVersion = V1@@ -34,7 +36,7 @@ , queryLang = Gremlin } -data CayleyConnection = CayleyConnection (CayleyConfig,Manager)+data CayleyConnection = CayleyConnection (CayleyConfig, Manager) data Quad = Quad { subject :: T.Text -- ^ Subject node@@ -63,11 +65,11 @@ Quad s p o l == Quad s' p' o' l' = s == s' && p == p' && o == o' && l == l' instance A.ToJSON Quad where- toJSON (Quad _subject _predicate _object _label) =- A.object [ "subject" A..= _subject- , "predicate" A..= _predicate- , "object" A..= _object- , "label" A..= _label+ toJSON (Quad s p o l) =+ A.object [ "subject" A..= s+ , "predicate" A..= p+ , "object" A..= o+ , "label" A..= l ] instance A.FromJSON Quad where@@ -78,6 +80,50 @@ v A..: "label" parseJSON _ = mzero +data Shape = Shape+ { nodes :: [Node]+ , links :: [Link]+ } deriving (Eq, Show)++instance A.FromJSON Shape where+ parseJSON (A.Object v) = do+ vnds <- v A..: "nodes"+ nds <- mapM parseNode $ V.toList vnds+ vlks <- v A..: "links"+ lks <- mapM parseLink $ V.toList vlks+ return Shape { nodes = nds, links = lks}+ parseJSON _ = mzero++parseNode :: A.Value -> AT.Parser Node+parseNode (A.Object v) = Node <$>+ v A..: "id"<*>+ v A..:? "tags" <*>+ v A..:? "values" <*>+ v A..: "is_link_node" <*>+ v A..: "is_fixed"+parseNode _ = fail "Node expected"++parseLink :: AT.Value -> AT.Parser Link+parseLink (A.Object v) = Link <$>+ v A..: "source" <*>+ v A..: "target" <*>+ v A..: "link_node"+parseLink _ = fail "Link expected"++data Node = Node+ { id :: Integer+ , tags :: Maybe [Tag] -- ^ list of tags from the query+ , values :: Maybe [Value] -- ^ Known values from the query+ , isLinkNode :: Bool -- ^ Does the node represent the link or the node (the oval shapes)+ , isFixed :: Bool -- ^ Is the node a fixed starting point of the query+ } deriving (Eq, Show)++data Link = Link+ { source :: Integer -- ^ Node ID+ , target :: Integer -- ^ Node ID+ , linkNode :: Integer -- ^ Node ID+ } deriving (Eq, Show)+ type Query = T.Text type Subject = T.Text@@ -87,4 +133,8 @@ type Object = T.Text type Label = T.Text++type Tag = T.Text++type Value = T.Text
+ README.md view
@@ -0,0 +1,3 @@+#A Cayley client for Haskell [](https://travis-ci.org/MichelBoucey/cayley-client)++Cayley-client is a Haskell library that implements the [RESTful API](https://github.com/google/cayley/blob/master/docs/HTTP.md) of [Cayley database graph](https://github.com/google/cayley).
cayley-client.cabal view
@@ -1,5 +1,5 @@ name: cayley-client-version: 0.1.5.1+version: 0.2.0.0 synopsis: A Haskell client for the Cayley graph database description: cayley-client implements the RESTful API of the Cayley graph database. homepage: https://github.com/MichelBoucey/cayley-client@@ -11,6 +11,8 @@ category: Database build-type: Simple cabal-version: >=1.10+extra-source-files: README.md+ , tests/testdata.nq Source-Repository head Type: git@@ -19,7 +21,7 @@ library exposed-modules: Database.Cayley.Client , Database.Cayley.Types- , Database.Cayley.Internal+ other-modules: Database.Cayley.Internal other-extensions: OverloadedStrings build-depends: base >=4.7 && <5 , mtl >=2.1@@ -36,4 +38,16 @@ , unordered-containers >=0.2 , exceptions >= 0.6 default-language: Haskell2010- GHC-Options: -Wall+ GHC-Options: -Wall++test-suite tests+ type: exitcode-stdio-1.0+ hs-source-dirs: tests+ main-is: hspec.hs+ build-depends: hspec >= 2.2.2+ , base >= 4.7 && < 5+ , cayley-client+ , aeson >=0.8+ , unordered-containers >= 0.2.5+ default-language: Haskell2010+
+ tests/hspec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE OverloadedStrings #-}++import Data.Aeson+import Data.HashMap.Strict+import Database.Cayley.Client as C+import Database.Cayley.Types as CT+import Test.Hspec++main :: IO ()+main = hspec $ do++ describe "write" $+ it "writes a quad to Cayley server" $ do+ c <- connectCayley defaultCayleyConfig+ write c Quad { subject = "danny", predicate = "follows", C.object = "sandy", label = Nothing } `shouldReturn` Just (Object (fromList [("result",String "Successfully wrote 1 quads.")]))++ describe "query" $+ it "query Cayley server" $ do+ c <- connectCayley defaultCayleyConfig+ Right a <- query c "graph.Vertex('sandy').In('follows').All()"+ encode a `shouldBe` "[{\"id\":\"danny\"}]"++ describe "shape" $+ it "returns the description of the last query executed" $ do+ c <- connectCayley defaultCayleyConfig+ queryShape c "graph.Vertex('sandy').In('follows').All()" `shouldReturn` Success (Shape {nodes = [Node {CT.id = 4, tags = Nothing, values = Just ["sandy"], isLinkNode = False, isFixed = True},Node {CT.id = 8, tags = Nothing, values = Just ["follows"], isLinkNode = False, isFixed = True},Node {CT.id = 2, tags = Nothing, values = Nothing, isLinkNode = True, isFixed = False},Node {CT.id = 1, tags = Just ["id"], values = Nothing, isLinkNode = False, isFixed = False}], links = [Link {source = 1, target = 4, linkNode = 2}]})++ describe "writeNQuadFile" $+ it "writes quads from a file to Cayley server" $ do+ c <- connectCayley defaultCayleyConfig+ (writeNQuadFile c "./tests/testdata.nq" >>= successfulResults) `shouldReturn` Right 15+
+ tests/testdata.nq view
@@ -0,0 +1,15 @@+<alice> <follows> <bob> .+<bob> <follows> <fred> .+<bob> <status> "cool_person" .+<charlie> <follows> <bob> .+<charlie> <follows> <dani> .+<dani> <follows> <bob> .+<dani> <follows> <greg> .+<dani> <status> "cool_person" .+<emily> <follows> <fred> .+<fred> <follows> <greg> .+<greg> <status> "cool_person" .+<predicates> <are> <follows> .+<predicates> <are> <status> .+<emily> <status> "smart_person" "smart_graph" .+<greg> <status> "smart_person" "smart_graph" .