@@ -25,54 +25,122 @@ var ErrNilOp = errors.New("nil operation")
25
25
26
26
// VersionV21 indicates that the url will resolve
27
27
// to /restapi/v2.1
28
- var VersionV21 = & APIVersion {Version : "v2.1" }
28
+ var VersionV21 APIVersion = & apiVersion {
29
+ prefix : "/restapi" ,
30
+ accountReplace : true ,
31
+ versionPrefix : "/v2.1" ,
32
+ demoHost : "demo.docusign.net" ,
33
+ }
34
+
35
+ // VersionV2 indicates that the url will resolve
36
+ // to /restapi/v2
37
+ var VersionV2 APIVersion = & apiVersion {
38
+ prefix : "/restapi" ,
39
+ accountReplace : true ,
40
+ versionPrefix : "/v2" ,
41
+ demoHost : "demo.docusign.net" ,
42
+ }
43
+
44
+ // AdminV2 handles calls for the admin api and urls will
45
+ // resolve to start with /management
46
+ var AdminV2 APIVersion = & apiVersion {
47
+ prefix : "/Management" ,
48
+ host : "api.docusign.net" ,
49
+ demoHost : "api-d.docusign.net" ,
50
+ }
51
+
52
+ // RoomsV2 resolves urls for monitor dataset calls
53
+ var RoomsV2 APIVersion = & apiVersion {
54
+ prefix : "/restapi" ,
55
+ accountReplace : true ,
56
+ versionPrefix : "/v2" ,
57
+ host : "rooms.docusign.com" ,
58
+ demoHost : "demo.rooms.docusign.com" ,
59
+ }
60
+
61
+ // MonitorV2 resolves urls for monitor dataset calls
62
+ var MonitorV2 APIVersion = & apiVersion {
63
+ prefix : "" ,
64
+ host : "lens.docusign.net" ,
65
+ demoHost : "lens-d.docusign.net" ,
66
+ }
29
67
30
68
// ClickV1 defines url replacement for clickraps api
31
- var ClickV1 = & APIVersion {
32
- Version : "v1" ,
33
- Prefix : "clickapi" ,
69
+ var ClickV1 APIVersion = & apiVersion {
70
+ prefix : "/clickapi" ,
71
+ versionPrefix : "/v1" ,
72
+ accountReplace : true ,
73
+ demoHost : "demo.docusign.net" ,
74
+ }
75
+
76
+ type apiVersion struct {
77
+ prefix string
78
+ host string
79
+ demoHost string
80
+ accountReplace bool
81
+ versionPrefix string
34
82
}
35
83
36
84
// APIVersion defines the prefix used to resolve an operation's url. If
37
85
// nil or blank, "v2" is assumed.
38
- type APIVersion struct {
39
- Version string
40
- Prefix string
86
+ type APIVersion interface {
87
+ ResolveDSURL (u * url.URL , host string , accountID string , isDemo bool ) * url.URL
88
+ }
89
+
90
+ // ResolveAPIHost determines the url's host based upon the version
91
+ func (v * apiVersion ) resolveAPIHost (credentialHost string , isDemo bool ) string {
92
+ if isDemo {
93
+ return v .demoHost
94
+ }
95
+ if v .host != "" {
96
+ return v .host
97
+ }
98
+ return credentialHost
41
99
}
42
100
43
101
// ResolveDSURL updates the passed *url.URL's settings.
44
102
// https://developers.docusign.com/esign-rest-api/guides/authentication/user-info-endpoints#form-your-base-path
45
- func (v * APIVersion ) ResolveDSURL (u * url.URL , host string , accountID string ) * url.URL {
103
+ func (v * apiVersion ) ResolveDSURL (u * url.URL , host string , accountID string , isDemo bool ) * url.URL {
104
+ if v == nil {
105
+ return u
106
+ }
46
107
newURL := * u
47
108
newURL .Scheme = "https"
48
- newURL .Host = host
49
- var prefix = "/restapi"
50
- var version = "/v2" // default is v2 restapi
51
- if v != nil {
52
- if v .Prefix != "" {
53
- prefix = "/" + v .Prefix
54
- }
55
- if v .Version != "" {
56
- version = "/" + v .Version
57
- }
58
- }
59
- if strings .HasPrefix (u .Path , "/" ) {
60
- newURL .Path = prefix + u .Path
61
- } else {
62
- newURL .Path = prefix + version + "/accounts/" + accountID + "/" + u .Path
109
+ newURL .Host = v .resolveAPIHost (host , isDemo )
110
+
111
+ if v .accountReplace && ! strings .HasPrefix (u .Path , "/" ) {
112
+ newURL .Path = v .prefix + v .versionPrefix + "/accounts/" + accountID + "/" + u .Path
113
+ return & newURL
63
114
}
115
+ newURL .Path = v .prefix + u .Path
64
116
return & newURL
65
117
}
66
118
119
+ /* type ctxResponseReviewKeyType struct{}
120
+
121
+ // CtxResponseReviewKey used to identify an HTTPReadResponse interface in a context. If
122
+ // a context has a value with this key and the is an HTTPResponseReader, HTTPResponseReader
123
+ // ReadResponse method is passed the current http.Response. This may be used for debugging and
124
+ // is used the the ratelimit package to return rate limit header results.
125
+ var CtxResponseReviewKey = (*ctxResponseReviewKeyType)(nil)
126
+
127
+ // CtxResponseReviewer is used to review an api call's response prior to the response
128
+ // json being decoded or returned as a stream. Once added to a context with the value key
129
+ // CtxResponseReviewKey, this method is called immediately after receiving the esign response.
130
+ // If reading the body, the body should be copied to a io.ReadCloser and the response body must
131
+ // be closed. If an error occurs, a close of the response body is expected.
132
+ type CtxResponseReviewer interface {
133
+ Review(*http.Response) (io.ReadCloser, error)
134
+ } */
135
+
67
136
// Credential adds an authorization header(s) for the http request,
68
137
// resolves the http client and finalizes the url. Credentials may
69
138
// be created using the Oauth2Config and JWTConfig structs as well as
70
139
// legacy.Config.
71
140
type Credential interface {
72
141
// AuthDo attaches an authorization header to a request, prepends
73
- // account and user ids to url, and sends request. This func must
74
- // always close the request Body.
75
- AuthDo (context.Context , * http.Request , * APIVersion ) (* http.Response , error )
142
+ // account and user ids to url, and sends request.
143
+ AuthDo (context.Context , * Op ) (* http.Response , error )
76
144
}
77
145
78
146
// Op contains all needed information to perform a DocuSign operation.
@@ -95,13 +163,15 @@ type Op struct {
95
163
// Set Accept to a mimeType if response will
96
164
// not be application/json
97
165
Accept string
166
+ // ContentType is ContentType header value (usually application/json)
167
+ ContentType string
98
168
// Leave nil for v2
99
- Version * APIVersion
169
+ Version APIVersion
100
170
}
101
171
102
- // type requestHandler interface {
103
- // Do(context.Context, *http.Request) (*http.Response, error)
104
- // }
172
+ type requestHandler interface {
173
+ Do (context.Context , * http.Request ) (* http.Response , error )
174
+ }
105
175
106
176
// ResponseError describes DocuSign's server error response.
107
177
// https://developers.docusign.com/esign-rest-api/guides/status-and-error-codes#general-error-response-handling
@@ -129,10 +199,12 @@ func NewResponseError(buff []byte, status int) *ResponseError {
129
199
return & re
130
200
}
131
201
132
- func getBodyFromPayload (payload interface {}, files []* UploadFile ) (io.Reader , string , error ) {
202
+ // Body creates an io.Reader marshalling the payload in the appropriate
203
+ // format and if files are available create a multipart form.
204
+ func (op * Op ) Body () (io.Reader , string , error ) {
133
205
var body io.Reader
134
206
var ct string
135
- switch p := payload .(type ) {
207
+ switch p := op . Payload .(type ) {
136
208
case * UploadFile :
137
209
return p .Reader , p .ContentType , nil
138
210
case url.Values :
@@ -143,43 +215,42 @@ func getBodyFromPayload(payload interface{}, files []*UploadFile) (io.Reader, st
143
215
return nil , "" , err
144
216
}
145
217
}
146
- if len (files ) > 0 {
218
+ if len (op .Files ) > 0 {
219
+ var files = op .Files
147
220
if body != nil {
148
- files = append ([]* UploadFile {{Reader : body , ContentType : ct }}, files ... )
221
+ files = append ([]* UploadFile {{Reader : body , ContentType : ct }}, op . Files ... )
149
222
}
150
223
body , ct = multiPartBody (files )
151
224
}
152
225
return body , ct , nil
153
226
}
154
227
155
- // createOpRequest prepares an http.Request and optionally logs the request body.
228
+ // CreateRequest prepares an http.Request and optionally logs the request body.
156
229
// UploadFiles will be closed on error.
157
- func (op * Op ) createOpRequest (ctx context.Context , accept string ) (* http.Request , error ) {
158
-
159
- body , ct , err := getBodyFromPayload (op .Payload , op .Files )
230
+ func (op * Op ) CreateRequest () (* http.Request , error ) {
231
+ body , ct , err := op .Body ()
160
232
if err != nil {
161
- op .closeFiles () // close any open files on error
162
233
return nil , err
163
234
}
164
-
165
235
req , err := http .NewRequest (op .Method , op .Path , body )
166
236
if err != nil {
167
- // close body
168
- if f , ok := body .(io.Closer ); ok {
169
- f .Close ()
237
+ if b , ok := body .(io.ReadCloser ); ok {
238
+ b .Close ()
170
239
}
171
240
return nil , err
172
241
}
173
242
if len (op .QueryOpts ) > 0 {
174
243
req .URL .RawQuery = op .QueryOpts .Encode ()
175
244
}
176
- if len (ct ) > 0 {
245
+ if body != nil {
246
+ if len (ct ) == 0 {
247
+ ct = op .ContentType
248
+ }
177
249
req .Header .Set ("Content-Type" , ct )
178
250
}
179
- if len (accept ) > 0 {
180
- req .Header .Set ("Accept" , accept )
251
+ if len (op . Accept ) > 0 {
252
+ req .Header .Set ("Accept" , op . Accept )
181
253
}
182
-
183
254
return req , nil
184
255
}
185
256
@@ -230,44 +301,33 @@ func (op *Op) Do(ctx context.Context, result interface{}) error {
230
301
if err := op .validate (ctx ); err != nil {
231
302
return err
232
303
}
233
-
234
- acceptHdr := op .Accept
235
- if acceptHdr == "" {
236
- switch result .(type ) {
237
- case * * Download : // no accept header if **Download or nil
238
- case interface {}:
239
- acceptHdr = "application/json"
240
- }
241
- }
242
-
243
- // get request
244
- req , err := op .createOpRequest (ctx , acceptHdr )
304
+ res , err := op .Credential .AuthDo (ctx , op )
245
305
if err != nil {
246
306
return err
247
307
}
248
-
249
- res , err := op .Credential .AuthDo (ctx , req , op .Version )
250
- if err != nil {
251
- return err
308
+ // pass res.Body back if download
309
+ if f , ok := result .(* * Download ); ok {
310
+ * f = & Download {
311
+ ReadCloser : res .Body ,
312
+ ContentLength : res .ContentLength ,
313
+ ContentType : res .Header .Get ("Content-Type" ),
314
+ }
315
+ return nil
252
316
}
253
317
254
- switch f := result .(type ) {
255
- case * * Download : // return w/o closing response body
256
- * f = & Download {res .Body , res .ContentLength , res .Header .Get ("Content-Type" )}
257
- return nil
258
- case interface {}: // non-nil
259
- // parse response and check for context cancellation.
260
- done := make (chan error , 1 ) // buffered channel so go routine doesn't hang
261
- go func () {
318
+ defer res .Body .Close ()
319
+ done := make (chan error , 1 )
320
+ go func () {
321
+ defer close (done )
322
+ if result != nil {
262
323
done <- json .NewDecoder (res .Body ).Decode (result )
263
- }()
264
- select {
265
- case <- ctx .Done ():
266
- err = ctx .Err ()
267
- case err = <- done :
268
324
}
325
+ }()
326
+ select {
327
+ case <- ctx .Done ():
328
+ err = ctx .Err ()
329
+ case err = <- done :
269
330
}
270
- res .Body .Close ()
271
331
return err
272
332
273
333
}
0 commit comments