-
Notifications
You must be signed in to change notification settings - Fork 326
/
client_options.go
196 lines (179 loc) · 6.92 KB
/
client_options.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright 2018 Twitch Interactive, Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the License is
// located at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// or in the "license" file accompanying this file. This file is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.
package twirp
import (
"context"
"net/http"
"reflect"
)
// ClientOption is a functional option for extending a Twirp client.
type ClientOption func(*ClientOptions)
// WithClientHooks defines the hooks for a Twirp client.
func WithClientHooks(hooks *ClientHooks) ClientOption {
return func(opts *ClientOptions) {
opts.Hooks = hooks
}
}
// WithClientInterceptors defines the interceptors for a Twirp client.
func WithClientInterceptors(interceptors ...Interceptor) ClientOption {
return func(opts *ClientOptions) {
opts.Interceptors = append(opts.Interceptors, interceptors...)
}
}
// WithClientPathPrefix specifies a different prefix to use for routing.
// If not specified, the "/twirp" prefix is used by default.
// The service must be configured to serve on the same prefix.
// An empty value "" can be specified to use no prefix.
// URL format: "<baseURL>[<prefix>]/<package>.<Service>/<Method>"
// More info on Twirp docs: https://twitchtv.github.io/twirp/docs/routing.html
func WithClientPathPrefix(prefix string) ClientOption {
return func(opts *ClientOptions) {
opts.setOpt("pathPrefix", prefix)
opts.pathPrefix = &prefix // for code generated before v8.1.0
}
}
// WithClientLiteralURLs configures the Twirp client to use the exact values
// as defined in the proto file for Service and Method names,
// fixing the issue https://github.com/twitchtv/twirp/issues/244, which is manifested
// when working with Twirp services implemented other languages (e.g. Python) and the proto file definitions
// are not properly following the [Protobuf Style Guide](https://developers.google.com/protocol-buffers/docs/style#services).
// By default (false), Go clients modify the routes by CamelCasing the values. For example,
// with Service: `haberdasher`, Method: `make_hat`, the URLs generated by Go clients are `Haberdasher/MakeHat`,
// but with this option enabled (true) the client will properly use `haberdasher/make_hat` instead.
func WithClientLiteralURLs(b bool) ClientOption {
return func(opts *ClientOptions) {
opts.setOpt("literalURLs", b)
opts.LiteralURLs = b // for code generated before v8.1.0
}
}
// ClientHooks is a container for callbacks that can instrument a
// Twirp-generated client. These callbacks all accept a context and some return
// a context. They can use this to add to the context, appending values or
// deadlines to it.
//
// The RequestPrepared hook is special because it can return errors. If it
// returns non-nil error, handling for that request will be stopped at that
// point. The Error hook will then be triggered.
//
// The RequestPrepared hook will always be called first and will be called for
// each outgoing request from the Twirp client. The last hook to be called
// will either be Error or ResponseReceived, so be sure to handle both cases in
// your hooks.
type ClientHooks struct {
// RequestPrepared is called as soon as a request has been created and before
// it has been sent to the Twirp server.
RequestPrepared func(context.Context, *http.Request) (context.Context, error)
// ResponseReceived is called after a request has finished sending. Since this
// is terminal, the context is not returned. ResponseReceived will not be
// called in the case of an error being returned from the request.
ResponseReceived func(context.Context)
// Error hook is called whenever an error occurs during the sending of a
// request. The Error is passed as an argument to the hook.
Error func(context.Context, Error)
}
// ChainClientHooks creates a new *ClientHooks which chains the callbacks in
// each of the constituent hooks passed in. Each hook function will be
// called in the order of the ClientHooks values passed in.
//
// For the erroring hook, RequestPrepared, any returned
// errors prevent processing by later hooks.
func ChainClientHooks(hooks ...*ClientHooks) *ClientHooks {
if len(hooks) == 0 {
return nil
}
if len(hooks) == 1 {
return hooks[0]
}
return &ClientHooks{
RequestPrepared: func(ctx context.Context, req *http.Request) (context.Context, error) {
for _, h := range hooks {
if h != nil && h.RequestPrepared != nil {
var err error
ctx, err = h.RequestPrepared(ctx, req)
if err != nil {
return ctx, err
}
}
}
return ctx, nil
},
ResponseReceived: func(ctx context.Context) {
for _, h := range hooks {
if h != nil && h.ResponseReceived != nil {
h.ResponseReceived(ctx)
}
}
},
Error: func(ctx context.Context, twerr Error) {
for _, h := range hooks {
if h != nil && h.Error != nil {
h.Error(ctx, twerr)
}
}
},
}
}
// ClientOptions encapsulate the configurable parameters on a Twirp client.
// This type is meant to be used only by generated code.
type ClientOptions struct {
// Untyped options map. The methods setOpt and ReadOpt are used to set
// and read options. The options are untyped so when a new option is added,
// newly generated code can still work with older versions of the runtime.
m map[string]interface{}
Hooks *ClientHooks
Interceptors []Interceptor
// Properties below are only used by code that was
// generated by older versions of Twirp (before v8.1.0).
// New options with standard types added in the future
// don't need new properties, they should use ReadOpt.
LiteralURLs bool
pathPrefix *string
}
// ReadOpt extracts an option to a pointer value,
// returns true if the option exists and was extracted.
// This method is meant to be used by generated code,
// keeping the type dependency outside of the runtime.
//
// Usage example:
//
// opts.setOpt("fooOpt", 123)
// var foo int
// ok := opts.ReadOpt("fooOpt", &int)
//
func (opts *ClientOptions) ReadOpt(key string, out interface{}) bool {
val, ok := opts.m[key]
if !ok {
return false
}
rout := reflect.ValueOf(out)
if rout.Kind() != reflect.Ptr {
panic("ReadOpt(key, out); out must be a pointer but it was not")
}
rout.Elem().Set(reflect.ValueOf(val))
return true
}
// setOpt adds an option key/value. It is used by ServerOption helpers.
// The value can be extracted with ReadOpt by passing a pointer to the same type.
func (opts *ClientOptions) setOpt(key string, val interface{}) {
if opts.m == nil {
opts.m = make(map[string]interface{})
}
opts.m[key] = val
}
// PathPrefix() is used only by clients generated before v8.1.0
func (opts *ClientOptions) PathPrefix() string {
if opts.pathPrefix == nil {
return "/twirp" // default prefix
}
return *opts.pathPrefix
}