1package autorest
2
3// Copyright 2017 Microsoft Corporation
4//
5//  Licensed under the Apache License, Version 2.0 (the "License");
6//  you may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at
8//
9//      http://www.apache.org/licenses/LICENSE-2.0
10//
11//  Unless required by applicable law or agreed to in writing, software
12//  distributed under the License is distributed on an "AS IS" BASIS,
13//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14//  See the License for the specific language governing permissions and
15//  limitations under the License.
16
17import (
18	"bytes"
19	"encoding/json"
20	"encoding/xml"
21	"fmt"
22	"io"
23	"io/ioutil"
24	"net/http"
25	"strings"
26)
27
28// Responder is the interface that wraps the Respond method.
29//
30// Respond accepts and reacts to an http.Response. Implementations must ensure to not share or hold
31// state since Responders may be shared and re-used.
32type Responder interface {
33	Respond(*http.Response) error
34}
35
36// ResponderFunc is a method that implements the Responder interface.
37type ResponderFunc func(*http.Response) error
38
39// Respond implements the Responder interface on ResponderFunc.
40func (rf ResponderFunc) Respond(r *http.Response) error {
41	return rf(r)
42}
43
44// RespondDecorator takes and possibly decorates, by wrapping, a Responder. Decorators may react to
45// the http.Response and pass it along or, first, pass the http.Response along then react.
46type RespondDecorator func(Responder) Responder
47
48// CreateResponder creates, decorates, and returns a Responder. Without decorators, the returned
49// Responder returns the passed http.Response unmodified. Responders may or may not be safe to share
50// and re-used: It depends on the applied decorators. For example, a standard decorator that closes
51// the response body is fine to share whereas a decorator that reads the body into a passed struct
52// is not.
53//
54// To prevent memory leaks, ensure that at least one Responder closes the response body.
55func CreateResponder(decorators ...RespondDecorator) Responder {
56	return DecorateResponder(
57		Responder(ResponderFunc(func(r *http.Response) error { return nil })),
58		decorators...)
59}
60
61// DecorateResponder accepts a Responder and a, possibly empty, set of RespondDecorators, which it
62// applies to the Responder. Decorators are applied in the order received, but their affect upon the
63// request depends on whether they are a pre-decorator (react to the http.Response and then pass it
64// along) or a post-decorator (pass the http.Response along and then react).
65func DecorateResponder(r Responder, decorators ...RespondDecorator) Responder {
66	for _, decorate := range decorators {
67		r = decorate(r)
68	}
69	return r
70}
71
72// Respond accepts an http.Response and a, possibly empty, set of RespondDecorators.
73// It creates a Responder from the decorators it then applies to the passed http.Response.
74func Respond(r *http.Response, decorators ...RespondDecorator) error {
75	if r == nil {
76		return nil
77	}
78	return CreateResponder(decorators...).Respond(r)
79}
80
81// ByIgnoring returns a RespondDecorator that ignores the passed http.Response passing it unexamined
82// to the next RespondDecorator.
83func ByIgnoring() RespondDecorator {
84	return func(r Responder) Responder {
85		return ResponderFunc(func(resp *http.Response) error {
86			return r.Respond(resp)
87		})
88	}
89}
90
91// ByCopying copies the contents of the http.Response Body into the passed bytes.Buffer as
92// the Body is read.
93func ByCopying(b *bytes.Buffer) RespondDecorator {
94	return func(r Responder) Responder {
95		return ResponderFunc(func(resp *http.Response) error {
96			err := r.Respond(resp)
97			if err == nil && resp != nil && resp.Body != nil {
98				resp.Body = TeeReadCloser(resp.Body, b)
99			}
100			return err
101		})
102	}
103}
104
105// ByDiscardingBody returns a RespondDecorator that first invokes the passed Responder after which
106// it copies the remaining bytes (if any) in the response body to ioutil.Discard. Since the passed
107// Responder is invoked prior to discarding the response body, the decorator may occur anywhere
108// within the set.
109func ByDiscardingBody() RespondDecorator {
110	return func(r Responder) Responder {
111		return ResponderFunc(func(resp *http.Response) error {
112			err := r.Respond(resp)
113			if err == nil && resp != nil && resp.Body != nil {
114				if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
115					return fmt.Errorf("Error discarding the response body: %v", err)
116				}
117			}
118			return err
119		})
120	}
121}
122
123// ByClosing returns a RespondDecorator that first invokes the passed Responder after which it
124// closes the response body. Since the passed Responder is invoked prior to closing the response
125// body, the decorator may occur anywhere within the set.
126func ByClosing() RespondDecorator {
127	return func(r Responder) Responder {
128		return ResponderFunc(func(resp *http.Response) error {
129			err := r.Respond(resp)
130			if resp != nil && resp.Body != nil {
131				if err := resp.Body.Close(); err != nil {
132					return fmt.Errorf("Error closing the response body: %v", err)
133				}
134			}
135			return err
136		})
137	}
138}
139
140// ByClosingIfError returns a RespondDecorator that first invokes the passed Responder after which
141// it closes the response if the passed Responder returns an error and the response body exists.
142func ByClosingIfError() RespondDecorator {
143	return func(r Responder) Responder {
144		return ResponderFunc(func(resp *http.Response) error {
145			err := r.Respond(resp)
146			if err != nil && resp != nil && resp.Body != nil {
147				if err := resp.Body.Close(); err != nil {
148					return fmt.Errorf("Error closing the response body: %v", err)
149				}
150			}
151			return err
152		})
153	}
154}
155
156// ByUnmarshallingBytes returns a RespondDecorator that copies the Bytes returned in the
157// response Body into the value pointed to by v.
158func ByUnmarshallingBytes(v *[]byte) RespondDecorator {
159	return func(r Responder) Responder {
160		return ResponderFunc(func(resp *http.Response) error {
161			err := r.Respond(resp)
162			if err == nil {
163				bytes, errInner := ioutil.ReadAll(resp.Body)
164				if errInner != nil {
165					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
166				} else {
167					*v = bytes
168				}
169			}
170			return err
171		})
172	}
173}
174
175// ByUnmarshallingJSON returns a RespondDecorator that decodes a JSON document returned in the
176// response Body into the value pointed to by v.
177func ByUnmarshallingJSON(v interface{}) RespondDecorator {
178	return func(r Responder) Responder {
179		return ResponderFunc(func(resp *http.Response) error {
180			err := r.Respond(resp)
181			if err == nil {
182				b, errInner := ioutil.ReadAll(resp.Body)
183				// Some responses might include a BOM, remove for successful unmarshalling
184				b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf"))
185				if errInner != nil {
186					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
187				} else if len(strings.Trim(string(b), " ")) > 0 {
188					errInner = json.Unmarshal(b, v)
189					if errInner != nil {
190						err = fmt.Errorf("Error occurred unmarshalling JSON - Error = '%v' JSON = '%s'", errInner, string(b))
191					}
192				}
193			}
194			return err
195		})
196	}
197}
198
199// ByUnmarshallingXML returns a RespondDecorator that decodes a XML document returned in the
200// response Body into the value pointed to by v.
201func ByUnmarshallingXML(v interface{}) RespondDecorator {
202	return func(r Responder) Responder {
203		return ResponderFunc(func(resp *http.Response) error {
204			err := r.Respond(resp)
205			if err == nil {
206				b, errInner := ioutil.ReadAll(resp.Body)
207				if errInner != nil {
208					err = fmt.Errorf("Error occurred reading http.Response#Body - Error = '%v'", errInner)
209				} else {
210					errInner = xml.Unmarshal(b, v)
211					if errInner != nil {
212						err = fmt.Errorf("Error occurred unmarshalling Xml - Error = '%v' Xml = '%s'", errInner, string(b))
213					}
214				}
215			}
216			return err
217		})
218	}
219}
220
221// WithErrorUnlessStatusCode returns a RespondDecorator that emits an error unless the response
222// StatusCode is among the set passed. On error, response body is fully read into a buffer and
223// presented in the returned error, as well as in the response body.
224func WithErrorUnlessStatusCode(codes ...int) RespondDecorator {
225	return func(r Responder) Responder {
226		return ResponderFunc(func(resp *http.Response) error {
227			err := r.Respond(resp)
228			if err == nil && !ResponseHasStatusCode(resp, codes...) {
229				derr := NewErrorWithResponse("autorest", "WithErrorUnlessStatusCode", resp, "%v %v failed with %s",
230					resp.Request.Method,
231					resp.Request.URL,
232					resp.Status)
233				if resp.Body != nil {
234					defer resp.Body.Close()
235					b, _ := ioutil.ReadAll(resp.Body)
236					derr.ServiceError = b
237					resp.Body = ioutil.NopCloser(bytes.NewReader(b))
238				}
239				err = derr
240			}
241			return err
242		})
243	}
244}
245
246// WithErrorUnlessOK returns a RespondDecorator that emits an error if the response StatusCode is
247// anything other than HTTP 200.
248func WithErrorUnlessOK() RespondDecorator {
249	return WithErrorUnlessStatusCode(http.StatusOK)
250}
251
252// ExtractHeader extracts all values of the specified header from the http.Response. It returns an
253// empty string slice if the passed http.Response is nil or the header does not exist.
254func ExtractHeader(header string, resp *http.Response) []string {
255	if resp != nil && resp.Header != nil {
256		return resp.Header[http.CanonicalHeaderKey(header)]
257	}
258	return nil
259}
260
261// ExtractHeaderValue extracts the first value of the specified header from the http.Response. It
262// returns an empty string if the passed http.Response is nil or the header does not exist.
263func ExtractHeaderValue(header string, resp *http.Response) string {
264	h := ExtractHeader(header, resp)
265	if len(h) > 0 {
266		return h[0]
267	}
268	return ""
269}
270