-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
runner.go
145 lines (122 loc) · 3.27 KB
/
runner.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
// Package apigen provides a generator which generates API clients and request/response types via specific
// execution environment such as curl.
package apigen
import (
"context"
"fmt"
"io"
"net/http"
"os"
"strings"
"golang.org/x/sync/errgroup"
)
// Generate generates client interfaces, types for requests and responses based on the passed API definitions.
// If the API definition is invalid, Generate returns an error wrapping ErrInvalidDefinition.
func Generate(ctx context.Context, def *Definition, opts ...Option) error {
return newRunner(opts...).run(ctx, def)
}
type runner struct {
client *http.Client
decoder decoder
writer io.Writer
pkg string
}
func newRunner(opts ...Option) *runner {
r := &runner{
client: http.DefaultClient,
decoder: &jsonDecoder{},
writer: os.Stdout,
pkg: "main",
}
for _, o := range opts {
o(r)
}
return r
}
func (r *runner) run(ctx context.Context, def *Definition) error {
if err := def.validate(); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
eg, cctx := errgroup.WithContext(ctx)
for service, methods := range def.Services {
service := service
methods := methods
eg.Go(func() error {
return r.processService(cctx, service, methods)
})
}
return eg.Wait()
}
func (r *runner) processService(ctx context.Context, service string, methods []*Method) error {
eg, cctx := errgroup.WithContext(ctx)
gen := newGenerator(r.writer)
for _, m := range methods {
req, err := m.Request(cctx)
if err != nil {
return fmt.Errorf("failed to instantiate a new request: %w", err)
}
m := m
eg.Go(func() error {
switch req.Method {
case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
default:
return fmt.Errorf("unsupported method %s: %w", req.Method, ErrUnimplemented)
}
res, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("failed to call API: %w", err)
}
defer res.Body.Close()
methRes, err := r.decoder.Decode(res.Body)
if err != nil {
return fmt.Errorf("failed to decode response body: %w", err)
}
var methReq request
if m.ParamHint != "" {
methReq.path = structFromPathParams(m.ParamHint, req.URL)
}
if q := req.URL.Query(); len(q) != 0 {
methReq.query = structFromQuery(q)
}
switch req.Method {
case http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete:
if req.GetBody != nil {
b, err := req.GetBody()
if err != nil {
return fmt.Errorf("failed to get request body: %w", err)
}
req, err := r.decoder.Decode(b)
if err != nil {
return fmt.Errorf("failed to decode request body: %w", err)
}
methReq.body = &structType{
fields: []*structField{
{
name: "Body",
_type: &definedType{
name: fmt.Sprintf("%sRequestBody", m.Name),
pointer: true,
_type: req,
},
},
},
}
}
}
u := req.URL
u.RawQuery = ""
gen.addMethod(service+"Client", &method{
name: m.Name,
method: req.Method,
url: strings.ReplaceAll(u.String(), "%25s", "%s"), // Replace URL-encoded '%s'.
req: &methReq,
res: methRes,
})
return nil
})
}
if err := eg.Wait(); err != nil {
return err
}
return gen.generate(r.pkg)
}