Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added basic http client #7

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
153 changes: 153 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package surrealdb

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

// Client is a wrapper to more easily make HTTP calls to the SurrealDB engine.
// Any function that accepts a body of type interface{} will do one of two things depending on type.
// If it is of type string, the body will be the plaintext string, otherwise it will attempt to marshal
// it into JSON and send that
type Client struct {
// URL is the base URL in SurrealDB to be called
URL string
// Namespace that you want to connect to
NS string
// Database that you want to connect to
DB string
// The user to authenticate as
User string
// The password to authenticate with
Pass string
}

type Response struct {
Time string `json:"time"`
Status string `json:"status"`
Result interface{} `json:"result"`
}

// New creates a new instance of a Client
func NewClient(url, ns, db, user, pass string) Client {
return Client{
URL: url,
NS: ns,
DB: db,
User: user,
Pass: pass,
}
}

// Execute calls the endpoint POST /sql, executing whatever given statement
func (sc Client) Execute(query string) (Response, error) {
return sc.Request("/sql", "POST", query)
}

// CreateOne calls the endpoint POST /key/:table/:id, executing the statement
//
// CREATE type::table($table) CONTENT $body;
func (sc Client) CreateOne(table, id, body interface{}) (Response, error) {
return sc.Request(fmt.Sprintf("/key/%s/%s", table, id), "POST", body)
}

// CreateAll calls the endpoint POST /key/:table, executing the statement
//
// CREATE type::thing($table, $id) CONTENT $body;
func (sc Client) CreateAll(table string, body interface{}) (Response, error) {
tobiemh marked this conversation as resolved.
Show resolved Hide resolved
return sc.Request(fmt.Sprintf("/key/%s", table), "POST", body)
}

// SelectAll calls the endpoint GET /key/:table, executing the statement
//
// SELECT * FROM type::table($table);
func (sc Client) SelectAll(table string) (Response, error) {
return sc.Request(fmt.Sprintf("/key/%s", table), "GET", "")
}

// SelectOne calls the endpoint GET /key/:table/:id, executing the statement
//
// SELECT * FROM type::thing(:table, :id);
func (sc Client) SelectOne(table string, id string) (Response, error) {
return sc.Request(fmt.Sprintf("/key/%s/%s", table, id), "GET", "")
}

// ReplaceOne calls the endpoint PUT /key/:table/:id, executing the statement
//
// UPDATE type::thing($table, $id) CONTENT $body;
func (sc Client) ReplaceOne(table, id string, body interface{}) (Response, error) {
MrShutCo marked this conversation as resolved.
Show resolved Hide resolved
return sc.Request(fmt.Sprintf("/key/%s/%s", table, id), "PUT", body)
}

// UpsertOne calls the endpoint PUT /key/:table/:id, executing the statement
//
// UPDATE type::thing($table, $id) MERGE $body;
func (sc Client) UpsertOne(table, id string, body interface{}) (Response, error) {
MrShutCo marked this conversation as resolved.
Show resolved Hide resolved
return sc.Request(fmt.Sprintf("/key/%s/%s", table, id), "PATCH", body)
}

// DeleteOne calls the endpoint DELETE /key/:table/:id, executing the statement
//
// DELETE FROM type::thing($table, $id);
func (sc Client) DeleteOne(table, id string) (Response, error) {
return sc.Request(fmt.Sprintf("/key/%s/%s", table, id), "DELETE", "")
}

// DeleteAll calls the endpoint DELETE /key/:table/, executing the statement
//
// DELETE FROM type::table($table);
func (sc Client) DeleteAll(table string) (Response, error) {
return sc.Request(fmt.Sprintf("/key/%s", table), "DELETE", "")
}

// Request makes a request to surrealdb to the given endpoint, with the given data. Responses returned from
// surrealdb vary, and this function will only return the first response
// TODO: have it return the array, or some other data type that more properly reflects the responses
func (sc Client) Request(endpoint string, requestType string, body interface{}) (Response, error) {
client := &http.Client{}
var bodyBytes []byte
var err error

// If it is a string, send it directly though, otherwise try to unmarshal, throwing an error if it fails
switch v := body.(type) {
case string:
bodyBytes = []byte(v)
default:
bodyBytes, err = json.Marshal(v)
if err != nil {
return Response{}, err
}
}

// TODO: verify its a valid requesttype
req, err := http.NewRequest(requestType, sc.URL+endpoint, bytes.NewBuffer(bodyBytes))
if err != nil {
return Response{}, err
}
req.Header.Set("NS", sc.NS)
req.Header.Set("DB", sc.DB)
req.Header.Set("Content-Type", "application/json")
tobiemh marked this conversation as resolved.
Show resolved Hide resolved
req.SetBasicAuth(sc.User, sc.Pass)

resp, err := client.Do(req)
if err != nil {
return Response{}, err
}
defer resp.Body.Close()

data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return Response{}, err
}

var realResp []Response
err = json.Unmarshal(data, &realResp)
if err != nil {
return Response{}, err
}

return realResp[0], err
}
176 changes: 176 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package surrealdb

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TODO for testing: adding in more edge case detections, including NULLs and other weird scenarios. Most of these are just
// best case scenarios

type person struct {
Child string `json:"child"`
Name string `json:"name"`
Age int `json:"age"`
}

func Test_Nominal(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

resp, err := client.Execute("INFO FOR DB;")
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
}

func Test_CreateAll(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

resp, err := client.CreateAll("person", `{child: null, name: "FooBar"}`)
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)

fmt.Println(resp.Result)
}

func Test_CreateOne(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

resp, err := client.CreateOne("person", "surrealcreate", `{child: null, name: "FooBar"}`)
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)
}

func Test_SelectAll(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

resp, err := client.SelectAll("person")
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)
}

func Test_SelectOne(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

expectedPerson := person{
Child: "hello",
Name: "FooBar",
}

resp, err := client.CreateOne("person", "surreal", `{child: "hello", name: "FooBar"}`)
require.Nil(t, err)
fmt.Println(resp.Result)

resp, err = client.SelectOne("person", "surreal")
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)

var actualPerson person
err = Unmarshal(resp.Result, &actualPerson)
require.Nil(t, err)
assert.Equal(t, expectedPerson, actualPerson)
}

func Test_ReplaceOne(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

resp, err := client.CreateOne("personreplace", "surreal", `{child: null, name: "FooBar"}`)
require.Nil(t, err)
fmt.Println(resp.Result)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we change these println to testify asserts please?


resp, err = client.ReplaceOne("personreplace", "surreal", `{child: "DB", name: "FooBar", age: 1000}`)
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)
}

func Test_ReplaceOne_PassInInterface(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

expectedPerson := person{
Child: "interface",
Name: "interface",
Age: 100,
}

resp, err := client.CreateOne("personreplace", "surrealinterface", `{child: null, name: "FooBar"}`)
require.Nil(t, err)
fmt.Println(resp.Result)

// Replace with the struct
resp, err = client.ReplaceOne("personreplace", "surrealinterface", expectedPerson)
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)

// Now select it to ensure that it is correct still
resp, err = client.SelectOne("personreplace", "surrealinterface")
require.Nil(t, err)
assert.Equal(t, "OK", resp.Status)
fmt.Println(resp.Result)

// Unmarshal and verify its the same
var actualPerson person
err = Unmarshal(resp.Result, &actualPerson)
require.Nil(t, err)
assert.Equal(t, expectedPerson, actualPerson)
}

func Test_UpsertOne(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")
expectedPerson := person{
Child: "DB",
Name: "FooBar",
Age: 0,
}

_, err := client.CreateOne("person", "surrealupsert", `{child: null, name: "FooBar"}`)
require.Nil(t, err)

resp, err := client.UpsertOne("person", "surrealupsert", `{child: "DB", name: "FooBar"}`)
require.Nil(t, err)

// Unmarshal and verify its the same
var actualPerson person
err = Unmarshal(resp.Result, &actualPerson)
require.Nil(t, err)
assert.Equal(t, expectedPerson, actualPerson)
}

func Test_DeleteOne(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

_, err := client.CreateOne("person", "surrealdelete", `{child: null, name: "FooBar"}`)
require.Nil(t, err)

resp, err := client.DeleteOne("person", "surrealdelete")
require.Nil(t, err)
assert.Equal(t, []interface{}{}, resp.Result)

resp2, err := client.SelectOne("person", "surrealdelete")
require.Nil(t, err)
assert.Equal(t, []interface{}{}, resp2.Result)
}

func Test_DeleteAll(t *testing.T) {
client := NewClient("http://localhost:8000", "test", "test", "root", "root")

_, err := client.CreateOne("person", "surrealdeleteall1", `{child: null, name: "FooBar"}`)
require.Nil(t, err)

_, err = client.CreateOne("person", "surrealdeleteall2", `{child: null, name: "FooBar"}`)
require.Nil(t, err)

resp, err := client.DeleteAll("person")
require.Nil(t, err)
assert.Equal(t, []interface{}{}, resp.Result)

resp2, err := client.SelectAll("person")
require.Nil(t, err)
assert.Equal(t, []interface{}{}, resp2.Result)
}
11 changes: 10 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,13 @@ module github.com/surrealdb/surrealdb.go

go 1.18

require github.com/gorilla/websocket v1.5.0
require (
github.com/gorilla/websocket v1.5.0
github.com/stretchr/testify v1.8.0
tobiemh marked this conversation as resolved.
Show resolved Hide resolved
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE=
github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=