-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
181 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package client | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/gorilla/websocket" | ||
"github.com/vektah/gqlgen/neelance/errors" | ||
) | ||
|
||
const ( | ||
connectionInitMsg = "connection_init" // Client -> Server | ||
connectionTerminateMsg = "connection_terminate" // Client -> Server | ||
startMsg = "start" // Client -> Server | ||
stopMsg = "stop" // Client -> Server | ||
connectionAckMsg = "connection_ack" // Server -> Client | ||
connectionErrorMsg = "connection_error" // Server -> Client | ||
connectionKeepAliveMsg = "ka" // Server -> Client | ||
dataMsg = "data" // Server -> Client | ||
errorMsg = "error" // Server -> Client | ||
completeMsg = "complete" // Server -> Client | ||
) | ||
|
||
type operationMessage struct { | ||
Payload json.RawMessage `json:"payload,omitempty"` | ||
ID string `json:"id,omitempty"` | ||
Type string `json:"type"` | ||
} | ||
|
||
type Subscription struct { | ||
Close func() error | ||
Next func(response interface{}) error | ||
} | ||
|
||
func errorSubscription(err error) *Subscription { | ||
return &Subscription{ | ||
Close: func() error { return nil }, | ||
Next: func(response interface{}) error { | ||
return err | ||
}, | ||
} | ||
} | ||
|
||
func (p *Client) Websocket(query string, options ...Option) *Subscription { | ||
r := p.mkRequest(query, options...) | ||
requestBody, err := json.Marshal(r) | ||
if err != nil { | ||
return errorSubscription(fmt.Errorf("encode: %s", err.Error())) | ||
} | ||
|
||
url := strings.Replace(p.url, "http://", "ws://", -1) | ||
url = strings.Replace(url, "https://", "wss://", -1) | ||
|
||
c, _, err := websocket.DefaultDialer.Dial(url, nil) | ||
if err != nil { | ||
return errorSubscription(fmt.Errorf("dial: %s", err.Error())) | ||
} | ||
|
||
if err = c.WriteJSON(operationMessage{Type: connectionInitMsg}); err != nil { | ||
return errorSubscription(fmt.Errorf("init: %s", err.Error())) | ||
} | ||
|
||
var ack operationMessage | ||
if err := c.ReadJSON(&ack); err != nil { | ||
return errorSubscription(fmt.Errorf("ack: %s", err.Error())) | ||
} | ||
if ack.Type != connectionAckMsg { | ||
return errorSubscription(fmt.Errorf("expected ack message, got %#v", ack)) | ||
} | ||
|
||
if err = c.WriteJSON(operationMessage{Type: startMsg, ID: "1", Payload: requestBody}); err != nil { | ||
return errorSubscription(fmt.Errorf("start: %s", err.Error())) | ||
} | ||
|
||
return &Subscription{ | ||
Close: c.Close, | ||
Next: func(response interface{}) error { | ||
var op operationMessage | ||
c.ReadJSON(&op) | ||
if op.Type != dataMsg { | ||
return fmt.Errorf("expected data message, got %#v", op) | ||
} | ||
|
||
respDataRaw := map[string]interface{}{} | ||
err = json.Unmarshal(op.Payload, &respDataRaw) | ||
if err != nil { | ||
return fmt.Errorf("decode: %s", err.Error()) | ||
} | ||
|
||
if respDataRaw["errors"] != nil { | ||
var errs []*errors.QueryError | ||
if err := unpack(respDataRaw["errors"], errs); err != nil { | ||
return err | ||
} | ||
if len(errs) > 0 { | ||
return fmt.Errorf("errors: %s", errs) | ||
} | ||
} | ||
|
||
return unpack(respDataRaw["data"], response) | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package chat | ||
|
||
import ( | ||
"net/http/httptest" | ||
"sync" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
"github.com/vektah/gqlgen/client" | ||
"github.com/vektah/gqlgen/handler" | ||
) | ||
|
||
func TestChat(t *testing.T) { | ||
srv := httptest.NewServer(handler.GraphQL(MakeExecutableSchema(New()))) | ||
c := client.New(srv.URL) | ||
var wg sync.WaitGroup | ||
wg.Add(1) | ||
|
||
t.Run("subscribe to chat events", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
sub := c.Websocket(`subscription { messageAdded(roomName:"#gophers") { text createdBy } }`) | ||
defer sub.Close() | ||
|
||
wg.Done() | ||
var resp struct { | ||
MessageAdded struct { | ||
Text string | ||
CreatedBy string | ||
} | ||
} | ||
require.NoError(t, sub.Next(&resp)) | ||
require.Equal(t, "Hello!", resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", resp.MessageAdded.CreatedBy) | ||
|
||
require.NoError(t, sub.Next(&resp)) | ||
require.Equal(t, "Whats up?", resp.MessageAdded.Text) | ||
require.Equal(t, "vektah", resp.MessageAdded.CreatedBy) | ||
}) | ||
|
||
t.Run("post two messages", func(t *testing.T) { | ||
t.Parallel() | ||
|
||
wg.Wait() | ||
var resp interface{} | ||
c.MustPost(`mutation { | ||
a:post(text:"Hello!", roomName:"#gophers", username:"vektah") { id } | ||
b:post(text:"Whats up?", roomName:"#gophers", username:"vektah") { id } | ||
}`, &resp) | ||
}) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters