forked from wundergraph/cosmo
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcomposition.go
155 lines (141 loc) · 4.19 KB
/
composition.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//go:generate ./generate.sh
// Package composition implements federation composition for GraphQL.
package composition
import (
_ "embed"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
)
// Subgraph represents a graph to be federated. URL is optional.
type Subgraph struct {
// Name is the name of the subgraph
Name string `goja:"name"`
// URL is the URL of the subgraph used for sending operations
URL string `goja:"url"`
// Schema is the SDL of the subgraph as a string. If empty, the schema
// is retrieved from the URL using the _service query.
Schema string `goja:"schema"`
SubscriptionURL string `goja:"subscription_url"`
SubscriptionProtocol string `goja:"subscription_protocol"`
}
type FieldConfiguration struct {
ArgumentNames []string `goja:"argumentNames"`
FieldName string `goja:"fieldName"`
TypeName string `goja:"typeName"`
RequiresAuthentication bool `goja:"requiresAuthentication"`
RequiredScopes [][]string `goja:"requiredScopes"`
}
type FederatedGraph struct {
FieldConfigurations []*FieldConfiguration `goja:"fieldConfigurations"`
SDL string `goja:"sdl"`
}
type sdlResponse struct {
Data struct {
Service struct {
SDL string `json:"sdl"`
} `json:"_service"`
} `json:"data"`
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
}
const (
// This is required because the polyfill for events
// expects a browser environment and references navigator
jsPrelude = `var navigator = {
language: 'EN'
};`
sdlQuery = `{"query": "query {_service { sdl } }"}`
)
//go:embed index.global.js
var indexJs string
var (
pool sync.Pool
)
func introspectSubgraph(URL string) (string, error) {
if URL == "" {
return "", fmt.Errorf("no URL provided")
}
req, err := http.NewRequest("POST", URL, strings.NewReader(sdlQuery))
if err != nil {
return "", fmt.Errorf("could not create request: %w", err)
}
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", fmt.Errorf("could not retrieve schema: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("could not retrieve schema: unexpected status code %d", resp.StatusCode)
}
var response sdlResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return "", fmt.Errorf("could not decode SDL response: %w", err)
}
if len(response.Errors) > 0 {
var errorMessages []string
for _, err := range response.Errors {
errorMessages = append(errorMessages, err.Message)
}
return "", fmt.Errorf("SDL query returned errors: %s", strings.Join(errorMessages, ", "))
}
return response.Data.Service.SDL, nil
}
func updateSchemas(subgraphs []*Subgraph) ([]*Subgraph, error) {
updatedSubgraphs := make([]*Subgraph, 0, len(subgraphs))
for _, subgraph := range subgraphs {
if subgraph.Schema != "" {
updatedSubgraphs = append(updatedSubgraphs, subgraph)
continue
}
sdl, err := introspectSubgraph(subgraph.URL)
if err != nil {
return nil, fmt.Errorf("error introspecting subgraph %s: %w", subgraph.Name, err)
}
cpy := *subgraph
cpy.Schema = sdl
updatedSubgraphs = append(updatedSubgraphs, &cpy)
}
return updatedSubgraphs, nil
}
// Federate produces a federated graphs from the schemas and names
// of each of the subgraphs.
func Federate(subgraphs ...*Subgraph) (*FederatedGraph, error) {
updatedSubgraphs, err := updateSchemas(subgraphs)
if err != nil {
return nil, err
}
vm, _ := pool.Get().(*vm)
if vm == nil {
var err error
vm, err = newVM()
if err != nil {
return nil, err
}
}
defer pool.Put(vm)
return vm.FederateSubgraphs(updatedSubgraphs)
}
// BuildRouterConfiguration produces a federated router configuration
// as a string that can be saved to a file and used to configure the
// router data sources.
func BuildRouterConfiguration(subgraphs ...*Subgraph) (string, error) {
updatedSubgraphs, err := updateSchemas(subgraphs)
if err != nil {
return "", err
}
vm, _ := pool.Get().(*vm)
if vm == nil {
var err error
vm, err = newVM()
if err != nil {
return "", err
}
}
defer pool.Put(vm)
return vm.BuildRouterConfiguration(updatedSubgraphs)
}