packages feed

grakn 0.2.0 → 0.3.0

raw patch · 13 files changed

+503/−173 lines, 13 filesdep ~aesondep ~servantPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: aeson, servant

API changes (from Hackage documentation)

- Grakn: Graph :: BaseUrl -> String -> Graph
- Grakn: InsertResult :: [Text] -> Result
- Grakn: MatchResult :: [Map Var Concept] -> Result
- Grakn: data Graph
- Grakn: data MatchQuery
- Grakn: data Name
- Grakn: distinct :: MatchQuery -> MatchQuery
- Grakn: name :: Text -> Name
- Grakn: select :: [Var] -> MatchQuery -> MatchQuery
+ Grakn: AnswerResult :: (Map Var Concept) -> Result
+ Grakn: AnswersResult :: [Map Var Concept] -> Result
+ Grakn: Client :: BaseUrl -> String -> Client
+ Grakn: Options :: Bool -> Options
+ Grakn: [infer] :: Options -> Bool
+ Grakn: data Client
+ Grakn: data GetQuery
+ Grakn: data Label
+ Grakn: data Match
+ Grakn: data Options
+ Grakn: execute_ :: IsQuery q => Options -> Client -> q -> ExecuteResponse
+ Grakn: get :: [Var] -> Match -> GetQuery
+ Grakn: label :: Text -> Label
- Grakn: (-:) :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern
+ Grakn: (-:) :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern
- Grakn: (.:) :: Convert a VarOrName => a -> Var -> RolePlayer
+ Grakn: (.:) :: Convert a VarOrLabel => a -> Var -> RolePlayer
- Grakn: [keyspace] :: Graph -> String
+ Grakn: [keyspace] :: Client -> String
- Grakn: [url] :: Graph -> BaseUrl
+ Grakn: [url] :: Client -> BaseUrl
- Grakn: execute :: IsQuery q => Graph -> q -> IO (Either ServantError Result)
+ Grakn: execute :: IsQuery q => Client -> q -> ExecuteResponse
- Grakn: has :: (Convert p Pattern, Convert a (Either Value Var)) => p -> Name -> a -> Pattern
+ Grakn: has :: (Convert p Pattern, Convert a (Either Value Var)) => p -> Label -> a -> Pattern
- Grakn: hasText :: (Convert p Pattern) => p -> Name -> Text -> Pattern
+ Grakn: hasText :: (Convert p Pattern) => p -> Label -> Text -> Pattern
- Grakn: isa :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern
+ Grakn: isa :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern
- Grakn: limit :: Integer -> MatchQuery -> MatchQuery
+ Grakn: limit :: Integer -> Match -> Match
- Grakn: match :: Convert a Pattern => [a] -> MatchQuery
+ Grakn: match :: Convert a Pattern => [a] -> Match

Files

+ features/ClientSpec.hs view
@@ -0,0 +1,21 @@+module ClientSpec+  ( spec+  ) where++import           Data.Either    (isLeft)+import           Grakn+import           Servant.Client (BaseUrl (BaseUrl), Scheme (Http))+import           Test.Hspec++spec :: Spec+spec =+  describe+    "As a Grakn Developer I should be able to connect to a running Grakn Instance and use that instance to issue queries." $+  it "Issuing a query with a broken connection" $ do+    let client = aBrokenConnectionToTheDatabase+    result <- execute client "match $x sub entitty"+    result `shouldSatisfy` isLeft++aBrokenConnectionToTheDatabase :: Client+aBrokenConnectionToTheDatabase =+  Client (BaseUrl Http "1.2.3.4" 5678 "") "akeyspace"
+ features/Env.hs view
@@ -0,0 +1,42 @@+module Env where++import           Data.String.Utils (rstrip)+import           System.Exit       (ExitCode (ExitSuccess))+import           System.Process    (CreateProcess, createProcess, proc,+                                    readCreateProcess, waitForProcess)++env :: [String] -> CreateProcess+env = proc "features/grakn-spec/env.sh"++envStart :: IO Bool+envStart = envSuccess ["start", "1.0.0"]++envStop :: IO ()+envStop = do+  _ <- envSuccess ["stop"]+  return ()++envKeyspace :: IO String+envKeyspace = rstrip <$> readCreateProcess (env ["keyspace"]) ""++envInsert :: String -> IO ()+envInsert patterns = do+  _ <- envSuccess ["insert", patterns]+  return ()++envDefine :: String -> IO ()+envDefine patterns = do+  _ <- envSuccess ["define", patterns]+  return ()++envCheckType :: String -> IO Bool+envCheckType label = envSuccess ["check", "type", label]++envCheckInstance :: String -> String -> IO Bool+envCheckInstance res value = envSuccess ["check", "instance", res, value]++envSuccess :: [String] -> IO Bool+envSuccess args = do+    (_, _, _, p) <- createProcess $ env args+    code <- waitForProcess p+    return $ code == ExitSuccess
+ features/GraqlQuerySpec.hs view
@@ -0,0 +1,117 @@+module GraqlQuerySpec+  ( spec+  ) where++import           Control.Exception (Exception, displayException)+import           Data.Either       (isLeft)+import           Data.Map          (member)+import           Data.Text         (pack)+import           Env+import           Grakn+import           Test.Hspec++aKnowledgeBaseContainingTypesAndInstances :: IO Client+aKnowledgeBaseContainingTypesAndInstances = do+  client <- givenAKnowledgeBase+  envDefine "person sub entity, has name; name sub attribute, datatype string;"+  envInsert "$alice isa person, has name \"Alice\";"+  return client++givenAKnowledgeBase :: IO Client+givenAKnowledgeBase = Client defaultUrl <$> envKeyspace++x :: Var+x = var $ pack "x"++spec :: Spec+spec =+  before aKnowledgeBaseContainingTypesAndInstances $+  describe+    "As a Grakn Developer, I should be able to interact with a Grakn knowledge base using Graql queries" $ do+    it "Valid Define Query" $ \client -> do+      response <- execute client "define $x label dog sub entity;"+      typeIsInTheKB "dog"+      response `hasVar` x+    it "Redundant Define Query" $ \client -> do+      response <- execute client "define $x label person sub entity;"+      response `hasVar` x+    it "Valid Insert Query" $ \client -> do+      response <- execute client "insert $bob isa person, has name \"Bob\";"+      instanceIsInTheKB "name" "Bob"+      response `hasAnswers` 1+    it "Invalid Insert Query" $ \client -> do+      response <-+        execute client "insert $dunstan isa dog, has name \"Dunstan\";"+      isAnError response+    it "Get Query With Empty Response" $ \client -> do+      response <- execute client "match $x isa person, has name \"Precy\"; get;"+      response `hasAnswers` 0+    it "Get Query With Non-Empty Response" $ \client -> do+      response <- execute client "match $x isa person, has name \"Alice\"; get;"+      response `hasAnswers` 1+    it "Aggregate Ask Query With False Response" $ \client -> do+      response <- execute client "match $x has name \"Precy\"; aggregate ask;"+      response `is` AskResult False+    it "Aggregate Ask Query With True Response" $ \client -> do+      response <- execute client "match $x has name \"Alice\"; aggregate ask;"+      response `is` AskResult True+    it "Aggregate Query" $ \client -> do+      response <- execute client "match $x isa person; aggregate count;"+      response `is` CountResult 1+    it "Compute Query" $ \client -> do+      response <- execute client "compute count in person;"+      response `is` CountResult 1+    it "Successful Undefine Query" $ \client -> do+      envDefine "dog sub entity;"+      response <- execute client "undefine dog sub entity;"+      response `is` DeleteResult+    it "Unsuccessful Undefine Query" $ \client -> do+      response <- execute client "undefine person sub entity;"+      isAnError response+    it "Delete Query for non Existent Concept" $ \client -> do+      response <- execute client "match $x has name \"Precy\"; delete $x;"+      response `is` DeleteResult+    it "Inference on by default" $ \client -> do+      envDefine+        "weird-rule sub rule when { $person has name \"Alice\"; } then { $person has name \"A\"; };"+      response <- execute client "match $x has name \"A\"; get;"+      response `hasAnswers` 1+    it "Inference can be disabled" $ \client -> do+      envDefine+        "weird-rule sub rule when { $person has name \"Alice\"; } then { $person has name \"A\"; };"+      response <-+        execute_ Options {infer = False} client "match $x has name \"A\"; get;"+      response `hasAnswers` 0++is :: (Exception e, Show a, Eq a) => Either e a -> a -> Expectation+response `is` expected =+  case response of+    Right answer -> answer `shouldBe` expected+    Left err     -> expectationFailure (displayException err)++isAnError :: Either t a -> Expectation+isAnError response = isLeft response `shouldBe` True++typeIsInTheKB :: String -> Expectation+typeIsInTheKB label = do+  isIn <- envCheckType label+  isIn `shouldBe` True++instanceIsInTheKB :: String -> String -> Expectation+instanceIsInTheKB res val = do+  isIn <- envCheckInstance res val+  isIn `shouldBe` True++hasAnswers :: Exception e => Either e Result -> Int -> Expectation+response `hasAnswers` n =+  case response of+    Right (AnswersResult answers) -> length answers `shouldBe` n+    Left err -> expectationFailure (displayException err)+    x -> expectationFailure (show x)++hasVar :: Exception e => Either e Result -> Var -> Expectation+response `hasVar` var =+  case response of+    Right (AnswerResult answer) -> member var answer `shouldBe` True+    Left err                    -> expectationFailure (displayException err)+    x                           -> expectationFailure (show x)
+ features/Spec.hs view
@@ -0,0 +1,2 @@+{-# OPTIONS_GHC+  -F -pgmF hspec-discover -optF --module-name=Spec #-}
grakn.cabal view
@@ -1,7 +1,7 @@ name:                 grakn-version:              0.2.0-synopsis:             A Haskell client for <http://grakn.ai Grakn>-description:          A Haskell client for <http://grakn.ai Grakn>+version:              0.3.0+synopsis:             A Haskell client for Grakn+description:          See README.md license:              Apache-2.0 license-file:         LICENSE homepage:             https://github.com/graknlabs/grakn-haskell@@ -24,7 +24,7 @@                       , scientific  == 0.3.*                       , text        == 1.2.*                       , regex-posix == 0.95.*-                      , servant     == 0.9.*+                      , servant     >= 0.9                       , servant-client                       , http-client                       , mtl@@ -52,11 +52,11 @@                       , grakn                       , containers  == 0.5.*                       , process     == 1.4.*-                      , aeson       == 1.0.*+                      , aeson       == 1.*                       , scientific  == 0.3.*                       , text        == 1.2.*                       , regex-posix == 0.95.*-                      , servant     == 0.9.*+                      , servant     >= 0.9                       , servant-client                       , http-client                       , mtl@@ -66,6 +66,14 @@                       , markdown-unlit                       , MissingH   other-modules:        Example+                      , Grakn+                      , GraknSpec+                      , Grakn.Client+                      , Grakn.ClientSpec+                      , Grakn.Pattern+                      , Grakn.Property+                      , Grakn.Query+                      , Grakn.Util   default-language:     Haskell2010   ghc-options:          -pgmL markdown-unlit   default-extensions:   MultiParamTypeClasses@@ -82,11 +90,11 @@                       , grakn                       , containers  == 0.5.*                       , process     == 1.4.*-                      , aeson       == 1.0.*+                      , aeson       == 1.*                       , scientific  == 0.3.*                       , text        == 1.2.*                       , regex-posix == 0.95.*-                      , servant     == 0.9.*+                      , servant     >= 0.9                       , servant-client                       , http-client                       , mtl@@ -94,6 +102,16 @@                       , hspec                       , markdown-unlit                       , MissingH+  other-modules:        ClientSpec+                      , Env+                      , Spec+                      , GraqlQuerySpec+                      , Grakn+                      , Grakn.Client+                      , Grakn.Pattern+                      , Grakn.Property+                      , Grakn.Query+                      , Grakn.Util   default-language:     Haskell2010   default-extensions:   MultiParamTypeClasses                       , FlexibleInstances
src/Grakn.hs view
@@ -1,21 +1,23 @@ module Grakn-  ( MatchQuery-  , Graph(Graph, keyspace, url)+  ( Match+  , GetQuery+  , Client(Client, keyspace, url)   , GraknError-  , Result(MatchResult, InsertResult, AskResult, CountResult,+  , Result(AnswersResult, AnswerResult, AskResult, CountResult,        DeleteResult)+  , Options(Options, infer)   , Var-  , Name-  , Value(..)+  , Label+  , Value(ValueString, ValueNumber, ValueBool)   , defaultUrl   , defaultKeyspace   , execute+  , execute_   , match-  , select-  , distinct+  , get   , limit   , var-  , name+  , label   , isa   , (-:)   , (.:)@@ -28,15 +30,19 @@   ) where  import           Data.Text      (Text)-import           Grakn.Client   (GraknError, Graph (Graph, keyspace, url), Result (AskResult, CountResult, DeleteResult, InsertResult, MatchResult),-                                 defaultKeyspace, defaultUrl, execute)-import           Grakn.Pattern-import           Grakn.Property-import           Grakn.Query+import           Grakn.Client   (Client (Client, keyspace, url), GraknError,+                                 Options (Options, infer),+                                 Result (AnswerResult, AnswersResult, AskResult, CountResult, DeleteResult),+                                 defaultKeyspace, defaultUrl, execute, execute_)+import           Grakn.Pattern  (Pattern, has, isa, var_, (<:))+import           Grakn.Property (Label, RolePlayer,+                                 Value (ValueBool, ValueNumber, ValueString),+                                 Var, VarOrLabel, label, rp, var, (.:))+import           Grakn.Query    (GetQuery, Match, get, limit, match) import           Grakn.Util     (Convert)  -- |Specify a property has a particular type-(-:) :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern+(-:) :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern (-:) = isa  -- |Shorthand to define a relation@@ -44,5 +50,5 @@ rel = (var_ <:)  -- |Specify a property has a resource-hasText :: (Convert p Pattern) => p -> Name -> Text -> Pattern+hasText :: (Convert p Pattern) => p -> Label -> Text -> Pattern hasText = has
src/Grakn/Client.hs view
@@ -1,33 +1,36 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards   #-}  module Grakn.Client-  ( Graph(Graph, keyspace, url)-  , Concept(Concept, cid, cname, ctype, value)+  ( Client(Client, keyspace, url)+  , Concept(Concept, cid, clabel, ctype, value)   , GraknError-  , Result(MatchResult, InsertResult, AskResult, CountResult,+  , Result(AnswersResult, AnswerResult, AskResult, CountResult,        DeleteResult)+  , Options(Options, infer)   , defaultUrl   , defaultKeyspace   , execute+  , execute_   ) where -import           Control.Applicative      (empty)-import           Data.Aeson               (FromJSON, ToJSON, encode, parseJSON,-                                           (.:), (.:?))-import qualified Data.Aeson               as Aeson-import           Data.Foldable            (asum)-import           Data.Map                 (Map)-import           Data.Proxy               (Proxy (Proxy))-import           Data.Text                (Text)-import           Grakn.Property           (Name, Value, Var)-import           Grakn.Query              (IsQuery (queryString))-import           Network.HTTP.Client      (defaultManagerSettings, newManager)-import           Network.HTTP.Media       ((//))-import           Servant.API-import           Servant.API.ContentTypes (eitherDecodeLenient)-import           Servant.Client+import           Data.Aeson          (FromJSON, Object, parseJSON, withObject,+                                      (.:), (.:?))+import           Data.Aeson.Types    (Parser)+import           Data.Foldable       (asum)+import           Data.Map            (Map)+import           Data.Proxy          (Proxy (Proxy))+import           Data.Text           (Text)+import           Grakn.Property      (Label, Value, Var)+import           Grakn.Query         (IsQuery (queryString))+import           Network.HTTP.Client (defaultManagerSettings, newManager)+import           Servant.API         ((:>), Capture, JSON, PlainText, Post,+                                      QueryParam, ReqBody)+import           Servant.Client      (BaseUrl (BaseUrl), ClientEnv (ClientEnv),+                                      ClientM, Scheme (Http), ServantError,+                                      client, runClientM) -data Graph = Graph+data Client = Client   { url      :: BaseUrl   , keyspace :: String   }@@ -36,21 +39,21 @@   GraknError String   deriving (Eq, Show) --- |A result of a match query, binding variables to concepts+-- |A result of a query data Result-  = MatchResult [Map Var Concept]-  | InsertResult [Text]+  = AnswersResult [Map Var Concept]+  | AnswerResult (Map Var Concept)+  | DeleteResult   | AskResult Bool   | CountResult Integer-  | DeleteResult   deriving (Show, Eq) --- |A concept in the graph+-- |A concept in the knowledge base data Concept = Concept-  { cid   :: Text-  , cname :: Maybe Name-  , ctype :: Maybe Name-  , value :: Maybe Value+  { cid    :: Text+  , clabel :: Maybe Label+  , ctype  :: Maybe Label+  , value  :: Maybe Value   } deriving (Show, Eq)  -- |The default Grakn URL, accessing localhost@@ -61,55 +64,59 @@ defaultKeyspace :: String defaultKeyspace = "grakn" -execute :: IsQuery q => Graph -> q -> IO (Either ServantError Result)-execute (Graph u ks) query = do+data Options = Options+  { infer :: Bool+  }++type ExecuteResponse = IO (Either ServantError Result)++execute :: IsQuery q => Client -> q -> ExecuteResponse+execute = executeOpts Nothing++execute_ :: IsQuery q => Options -> Client -> q -> ExecuteResponse+execute_ opts = executeOpts (Just opts)++executeOpts :: IsQuery q => Maybe Options -> Client -> q -> ExecuteResponse+executeOpts opts (Client u ks) query = do   manager <- newManager defaultManagerSettings   let env = ClientEnv manager u-  runClientM (graqlGet (queryString query) ks) env--type Keyspace = QueryParam "keyspace" String+  runClientM (graqlGet opts (queryString query) ks) env  type Infer = QueryParam "infer" Bool -type Materialise = QueryParam "materialise" Bool--type Body = ReqBody '[ PlainText] String+type Query = ReqBody '[ PlainText] String -type GraknParams-   = Keyspace :> Infer :> Materialise :> Body :> Post '[ GraqlJSON] Result+type Keyspace = Capture "keyspace" String  -- |A type describing the REST API we use to execute queries-type GraknAPI = "graph" :> "graql" :> "execute" :> GraknParams+type GraknAPI+   = "kb" :> Keyspace :> "graql" :> Infer :> Query :> Post '[ JSON] Result  graknAPI :: Proxy GraknAPI graknAPI = Proxy -graqlGet :: String -> String -> ClientM Result-graqlGet query ks = client graknAPI (Just ks) (Just False) (Just False) query+graqlGet :: Maybe Options -> String -> String -> ClientM Result+graqlGet opts query ks = client graknAPI ks (infer <$> opts) query +-- This is a helper method for chaining accessing optional JSON fields+(.:>) :: FromJSON a => Parser (Maybe Object) -> Text -> Parser (Maybe a)+maybeParser .:> key = traverse (.: key) =<< maybeParser+ instance FromJSON Concept where-  parseJSON (Aeson.Object obj) =-    Concept <$> (obj .: "id") <*> (obj .:? "name") <*> (obj .:? "isa") <*>-    (obj .:? "value")-  parseJSON _ = empty+  parseJSON =+    withObject "Concept" $ \obj -> do+      cid <- obj .: "id"+      clabel <- obj .:? "label"+      ctype <- obj .:? "type" .:> "label"+      value <- obj .:? "value"+      return Concept {..}  instance FromJSON Result where   parseJSON val =     asum-      [ MatchResult <$> parseJSON val-      , InsertResult <$> parseJSON val+      [ AnswersResult <$> parseJSON val+      , AnswerResult <$> parseJSON val       , AskResult <$> parseJSON val       , CountResult <$> parseJSON val       , pure DeleteResult       ]--data GraqlJSON--instance Accept GraqlJSON where-  contentType _ = "application" // "graql+json"--instance ToJSON a => MimeRender GraqlJSON a where-  mimeRender _ = encode--instance FromJSON a => MimeUnrender GraqlJSON a where-  mimeUnrender _ = eitherDecodeLenient
src/Grakn/Pattern.hs view
@@ -6,12 +6,13 @@   , has   ) where -import           Grakn.Property+import           Grakn.Property (Label, Property (Has, Isa, Rel), RolePlayer,+                                 Value, Var, VarOrLabel) import           Grakn.Util     (Convert (convert), spaces, with) --- |A pattern to find in the graph+-- |A pattern to find in the knowledge base data Pattern =-  Pattern (Maybe VarOrName)+  Pattern (Maybe VarOrLabel)           [Property]  -- |Create an anonymous variable@@ -19,7 +20,7 @@ var_ = Pattern Nothing []  -- |Specify a property has a particular type-isa :: (Convert p Pattern, Convert a VarOrName) => p -> a -> Pattern+isa :: (Convert p Pattern, Convert a VarOrLabel) => p -> a -> Pattern patt `isa` x = addProperty patt (Isa $ convert x)  -- |Specify a property is a relation between other variables@@ -30,7 +31,7 @@ has ::      (Convert p Pattern, Convert a (Either Value Var))   => p-  -> Name+  -> Label   -> a   -> Pattern has patt rt v = addProperty patt (Has rt $ convert v)@@ -49,10 +50,10 @@   show (Pattern v props) = v `with` " " ++ showProps props ++ ";"  instance Convert Var Pattern where-  convert = convert . (convert :: Var -> VarOrName)+  convert = convert . (convert :: Var -> VarOrLabel) -instance Convert Name Pattern where-  convert = convert . (convert :: Name -> VarOrName)+instance Convert Label Pattern where+  convert = convert . (convert :: Label -> VarOrLabel) -instance Convert VarOrName Pattern where+instance Convert VarOrLabel Pattern where   convert v = Pattern (Just v) []
src/Grakn/Property.hs view
@@ -1,12 +1,12 @@ module Grakn.Property-  ( Property(..)+  ( Property(Isa, Has, Rel)   , RolePlayer   , Var-  , Name-  , VarOrName-  , Value(..)+  , Label+  , VarOrLabel+  , Value(ValueString, ValueNumber, ValueBool)   , var-  , name+  , label   , (.:)   , rp   ) where@@ -23,26 +23,26 @@  -- |A property of a concept data Property-  = Isa VarOrName-  | NameProperty Name+  = Isa VarOrLabel+  | LabelProperty Label   | Rel [RolePlayer]-  | Has Name+  | Has Label         (Either Value Var) --- |A variable name wildcard that will represent a concept in the results+-- |A variable that will represent a concept in the results newtype Var =   Var Text   deriving (Eq, Ord) --- |A name of something in the graph-newtype Name =-  Name Text+-- |A label of something in the knowledge base+newtype Label =+  Label Text   deriving (Eq) --- |Something that may be a variable name or a type name-data VarOrName+-- |Something that may be a variable or a type label+data VarOrLabel   = VarName Var-  | TypeName Name+  | TypeLabel Label  -- |A value of a resource data Value@@ -53,33 +53,33 @@  -- |A casting, relating a role type and role player data RolePlayer =-  RolePlayer (Maybe VarOrName)+  RolePlayer (Maybe VarOrLabel)              Var  -- |Create a variable var :: Text -> Var var = Var --- |Create a name of something in the graph-name :: Text -> Name-name = Name+-- |Create a label of something in the knowledge base+label :: Text -> Label+label = Label  -- |A casting in a relation between a role type and a role player-(.:) :: Convert a VarOrName => a -> Var -> RolePlayer+(.:) :: Convert a VarOrLabel => a -> Var -> RolePlayer rt .: player = RolePlayer (Just $ convert rt) player  -- |A casting in a relation without a role type rp :: Var -> RolePlayer rp = RolePlayer Nothing -nameRegex :: String-nameRegex = "^[a-zA-Z_][a-zA-Z0-9_-]*$"+labelRegex :: String+labelRegex = "^[a-zA-Z_][a-zA-Z0-9_-]*$"  instance Show Property where-  show (Isa varOrName)  = "isa " ++ show varOrName-  show (NameProperty n) = "type-name " ++ show n-  show (Rel castings)   = "(" ++ commas castings ++ ")"-  show (Has rt value)   = "has " ++ show rt ++ " " ++ showEither value+  show (Isa varOrLabel)  = "isa " ++ show varOrLabel+  show (LabelProperty n) = "label " ++ show n+  show (Rel castings)    = "(" ++ commas castings ++ ")"+  show (Has rt value)    = "has " ++ show rt ++ " " ++ showEither value  instance Show RolePlayer where   show (RolePlayer roletype player) = roletype `with` ": " ++ show player@@ -89,9 +89,9 @@   show (ValueNumber num)  = show num   show (ValueBool bool)   = show bool -instance Show Name where-  show (Name text)-    | str =~ nameRegex = str+instance Show Label where+  show (Label text)+    | str =~ labelRegex = str     | otherwise = show text     where       str = unpack text@@ -99,15 +99,15 @@ instance Show Var where   show (Var v) = '$' : unpack v -instance Show VarOrName where-  show (VarName v)  = show v-  show (TypeName t) = show t+instance Show VarOrLabel where+  show (VarName v)   = show v+  show (TypeLabel t) = show t -instance Convert Var VarOrName where+instance Convert Var VarOrLabel where   convert = VarName -instance Convert Name VarOrName where-  convert = TypeName+instance Convert Label VarOrLabel where+  convert = TypeLabel  instance Convert Var RolePlayer where   convert = rp@@ -130,8 +130,8 @@   parseJSON (Aeson.Bool b)   = return $ ValueBool b   parseJSON _                = empty -instance FromJSON Name where-  parseJSON (Aeson.String s) = return $ name s+instance FromJSON Label where+  parseJSON (Aeson.String s) = return $ label s   parseJSON _                = empty  instance FromJSON Var where
src/Grakn/Query.hs view
@@ -1,51 +1,50 @@ module Grakn.Query   ( IsQuery(queryString)-  , MatchQuery+  , Match+  , GetQuery   , match-  , select+  , get   , limit-  , distinct   ) where -import           Grakn.Pattern-import           Grakn.Property-import           Grakn.Util+import           Grakn.Pattern  (Pattern)+import           Grakn.Property (Var)+import           Grakn.Util     (Convert, commas, convert, spaces)  class IsQuery q where   queryString :: q -> String --- |A Graql 'match' query that finds a pattern in the graph-data MatchQuery+-- |A Graql 'match' part that finds a pattern in the knowledge base+data Match   = Match [Pattern]-  | Select MatchQuery-           [Var]-  | Limit MatchQuery+  | Limit Match           Integer-  | Distinct MatchQuery --- |Create a match query by providing a list of patterns-match :: Convert a Pattern => [a] -> MatchQuery+data GetQuery =+  GetQuery Match+           [Var]++-- |Create a match by providing a list of patterns+match :: Convert a Pattern => [a] -> Match match = Match . map convert --- |Select variables from a match query, intended to be used infix-select :: [Var] -> MatchQuery -> MatchQuery-select = flip Select+-- |Get variables from a match, intended to be used infix+get :: [Var] -> Match -> GetQuery+get = flip GetQuery --- |Limit a match query, intended to be used infix-limit :: Integer -> MatchQuery -> MatchQuery+-- |Limit a match, intended to be used infix+limit :: Integer -> Match -> Match limit = flip Limit --- |Retrieve only distinct results from a match query-distinct :: MatchQuery -> MatchQuery-distinct = Distinct+instance Show Match where+  show (Match patts)  = "match " ++ spaces patts+  show (Limit mq lim) = show mq ++ " limit " ++ show lim ++ ";" -instance Show MatchQuery where-  show (Match patts)    = "match " ++ spaces patts-  show (Select mq vars) = show mq ++ " select " ++ commas vars ++ ";"-  show (Limit mq lim)   = show mq ++ " limit " ++ show lim ++ ";"-  show (Distinct mq)    = show mq ++ " distinct;"+instance Show GetQuery where+  show (GetQuery match_ [])   = show match_ ++ " get;"+  show (GetQuery match_ vars) = show match_ ++ " get " ++ commas vars ++ ";" -instance IsQuery MatchQuery where+instance IsQuery GetQuery where   queryString = show  instance IsQuery String where
test/Example.lhs view
@@ -2,7 +2,7 @@  A Haskell client for [Grakn](http://grakn.ai). -Requires Grakn 0.16.+Requires Grakn 1.0.0.  # Installation @@ -30,20 +30,20 @@ import Data.Function ((&)) ``` -Define the type names:+Define the type labels:  ```haskell-person :: Name-person = name "person"+person :: Label+person = label "person" -husband :: Name-husband = name "husband"+husband :: Label+husband = label "husband" -wife :: Name-wife = name "wife"+wife :: Label+wife = label "wife" -marriage :: Name-marriage = name "marriage"+marriage :: Label+marriage = label "marriage" ```  Define the variables:@@ -59,35 +59,35 @@ We can translate the following query into Haskell:  ```graql-match $x isa person, (husband: $x, wife: $y) isa marriage; select $y;+match $x isa person, (husband: $x, wife: $y) isa marriage; get $y; ```  ```haskell-query :: MatchQuery+query :: GetQuery query = match     [ x `isa` person     , rel [husband .: x, wife .: y] `isa` marriage-    ] & select [y]+    ] & get [y] ```  We can also use infix functions like `(-:)` instead of `isa`:  ```haskell-otherQuery :: MatchQuery+otherQuery :: GetQuery otherQuery = match     [ x -: person     , rel [husband .: x, wife .: y] -: marriage-    ] & select [y]+    ] & get [y] ```  To execute and print the results of our query:  ```haskell-graph :: Graph-graph = Graph defaultUrl "my-keyspace"+client :: Client+client = Client defaultUrl "my-keyspace"  main :: IO () main = do-    result <- execute graph query+    result <- execute client query     print result ```
+ test/Grakn/ClientSpec.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE OverloadedStrings #-}++module Grakn.ClientSpec+  ( spec+  ) where++import           Data.Aeson      (decode)+import           Grakn           (Value (ValueNumber), label)+import           Grakn.Client+import qualified Servant.Client  as S+import           Test.Hspec+import           Test.QuickCheck (Arbitrary, arbitrary, elements, property)++spec :: Spec+spec = do+  it "The client's keyspace is set correctly" $+    property $ \(BaseUrl u) k ->+      let client = Client {url = u, keyspace = k}+      in keyspace client `shouldBe` k+  it "The client's URI is set correctly" $+    property $ \(BaseUrl u) k ->+      let client = Client {url = u, keyspace = k}+      in url client `shouldBe` u+  it "type can be parsed from JSON" $ do+    let person = Just (label "person")+    let json = "{\"id\": \"V123\", \"label\": \"person\"}"+    let concept =+          Concept+          {cid = "V123", clabel = person, ctype = Nothing, value = Nothing}+    decode json `shouldBe` Just concept+  it "instance can be parsed from JSON" $ do+    let number = Just (label "number")+    let val = Just (ValueNumber 3)+    let json =+          "{\"id\": \"V123\", \"type\": {\"label\": \"number\"}, \"value\": 3}"+    let concept =+          Concept {cid = "V123", clabel = Nothing, ctype = number, value = val}+    decode json `shouldBe` Just concept++newtype BaseUrl =+  BaseUrl S.BaseUrl+  deriving (Show)++instance Arbitrary BaseUrl where+  arbitrary = do+    let scheme = elements [S.Http, S.Https]+    BaseUrl <$> (S.BaseUrl <$> scheme <*> arbitrary <*> arbitrary <*> arbitrary)
+ test/GraknSpec.hs view
@@ -0,0 +1,70 @@+{-# LANGUAGE OverloadedStrings #-}++module GraknSpec+  ( spec+  ) where++import           Data.Function ((&))+import qualified Example+import           Grakn+import           Test.Hspec++spec :: Spec+spec = do+  it "a simple query string representation" $+    match [x `isa` person] & get [] ~= "match $x isa person; get;"+  it "a relation query string representation" $+    match [rel [x, y]] & get [] ~= "match ($x, $y); get;"+  it "a relation query string representation with types" $+    match [rel [husband .: x, wife .: y] -: marriage] & get [] ~=+    "match (husband: $x, wife: $y) isa marriage; get;"+  it "a resource query string representation" $+    match [x `hasText` firstName $ "Bob"] & get [] ~=+    "match $x has first-name \"Bob\"; get;"+  it "multiple patterns" $+    match [x -: person, y -: firstName] & get [] ~=+    "match $x isa person; $y isa first-name; get;"+  it "get query with type" $+    match [x -: y] & get [x, y] ~= "match $x isa $y; get $x, $y;"+  it "mix role types" $+    match [rel [husband .: x, rp y]] & get [] ~= "match (husband: $x, $y); get;"+  it "limit" $+    match [x -: person] & limit 10 & get [] ~=+    "match $x isa person; limit 10; get;"+  it "type of type" $+    match [person -: x] & get [] ~= "match person isa $x; get;"+  it "reify a relation" $+    match [x <: [y, z]] & get [] ~= "match $x ($y, $z); get;"+  it "match just a variable" $ match [x] & get [] ~= "match $x; get;"+  it "example query output" $+    Example.query ~=+    "match $x isa person; (husband: $x, wife: $y) isa marriage; get $y;"++x :: Var+x = var "x"++y :: Var+y = var "y"++z :: Var+z = var "z"++person :: Label+person = label "person"++firstName :: Label+firstName = label "first-name"++marriage :: Label+marriage = label "marriage"++husband :: Label+husband = label "husband"++wife :: Label+wife = label "wife"++infixr 0 ~=++(~=) :: GetQuery -> String -> Expectation+(~=) = shouldBe . show