1 // Package azure provides Azure-specific implementations used with AutoRest.
2 // See the included examples for more detail.
5 // Copyright 2017 Microsoft Corporation
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
28 "github.com/Azure/go-autorest/autorest"
32 // HeaderClientID is the Azure extension header to set a user-specified request ID.
33 HeaderClientID = "x-ms-client-request-id"
35 // HeaderReturnClientID is the Azure extension header to set if the user-specified request ID
36 // should be included in the response.
37 HeaderReturnClientID = "x-ms-return-client-request-id"
39 // HeaderRequestID is the Azure extension header of the service generated request ID returned
41 HeaderRequestID = "x-ms-request-id"
44 // ServiceError encapsulates the error response from an Azure service.
45 // It adhears to the OData v4 specification for error responses.
46 type ServiceError struct {
47 Code string `json:"code"`
48 Message string `json:"message"`
49 Target *string `json:"target"`
50 Details []map[string]interface{} `json:"details"`
51 InnerError map[string]interface{} `json:"innererror"`
52 AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
55 func (se ServiceError) Error() string {
56 result := fmt.Sprintf("Code=%q Message=%q", se.Code, se.Message)
59 result += fmt.Sprintf(" Target=%q", *se.Target)
62 if se.Details != nil {
63 d, err := json.Marshal(se.Details)
65 result += fmt.Sprintf(" Details=%v", se.Details)
67 result += fmt.Sprintf(" Details=%v", string(d))
70 if se.InnerError != nil {
71 d, err := json.Marshal(se.InnerError)
73 result += fmt.Sprintf(" InnerError=%v", se.InnerError)
75 result += fmt.Sprintf(" InnerError=%v", string(d))
78 if se.AdditionalInfo != nil {
79 d, err := json.Marshal(se.AdditionalInfo)
81 result += fmt.Sprintf(" AdditionalInfo=%v", se.AdditionalInfo)
83 result += fmt.Sprintf(" AdditionalInfo=%v", string(d))
89 // UnmarshalJSON implements the json.Unmarshaler interface for the ServiceError type.
90 func (se *ServiceError) UnmarshalJSON(b []byte) error {
91 // per the OData v4 spec the details field must be an array of JSON objects.
92 // unfortunately not all services adhear to the spec and just return a single
93 // object instead of an array with one object. so we have to perform some
94 // shenanigans to accommodate both cases.
95 // http://docs.oasis-open.org/odata/odata-json-format/v4.0/os/odata-json-format-v4.0-os.html#_Toc372793091
97 type serviceError1 struct {
98 Code string `json:"code"`
99 Message string `json:"message"`
100 Target *string `json:"target"`
101 Details []map[string]interface{} `json:"details"`
102 InnerError map[string]interface{} `json:"innererror"`
103 AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
106 type serviceError2 struct {
107 Code string `json:"code"`
108 Message string `json:"message"`
109 Target *string `json:"target"`
110 Details map[string]interface{} `json:"details"`
111 InnerError map[string]interface{} `json:"innererror"`
112 AdditionalInfo []map[string]interface{} `json:"additionalInfo"`
115 se1 := serviceError1{}
116 err := json.Unmarshal(b, &se1)
118 se.populate(se1.Code, se1.Message, se1.Target, se1.Details, se1.InnerError, se1.AdditionalInfo)
122 se2 := serviceError2{}
123 err = json.Unmarshal(b, &se2)
125 se.populate(se2.Code, se2.Message, se2.Target, nil, se2.InnerError, se2.AdditionalInfo)
126 se.Details = append(se.Details, se2.Details)
132 func (se *ServiceError) populate(code, message string, target *string, details []map[string]interface{}, inner map[string]interface{}, additional []map[string]interface{}) {
137 se.InnerError = inner
138 se.AdditionalInfo = additional
141 // RequestError describes an error response returned by Azure service.
142 type RequestError struct {
143 autorest.DetailedError
145 // The error returned by the Azure service.
146 ServiceError *ServiceError `json:"error"`
148 // The request id (from the x-ms-request-id-header) of the request.
152 // Error returns a human-friendly error message from service error.
153 func (e RequestError) Error() string {
154 return fmt.Sprintf("autorest/azure: Service returned an error. Status=%v %v",
155 e.StatusCode, e.ServiceError)
158 // IsAzureError returns true if the passed error is an Azure Service error; false otherwise.
159 func IsAzureError(e error) bool {
160 _, ok := e.(*RequestError)
164 // Resource contains details about an Azure resource.
165 type Resource struct {
166 SubscriptionID string
173 // ParseResourceID parses a resource ID into a ResourceDetails struct.
174 // See https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-resource#return-value-4.
175 func ParseResourceID(resourceID string) (Resource, error) {
177 const resourceIDPatternText = `(?i)subscriptions/(.+)/resourceGroups/(.+)/providers/(.+?)/(.+?)/(.+)`
178 resourceIDPattern := regexp.MustCompile(resourceIDPatternText)
179 match := resourceIDPattern.FindStringSubmatch(resourceID)
182 return Resource{}, fmt.Errorf("parsing failed for %s. Invalid resource Id format", resourceID)
185 v := strings.Split(match[5], "/")
186 resourceName := v[len(v)-1]
189 SubscriptionID: match[1],
190 ResourceGroup: match[2],
192 ResourceType: match[4],
193 ResourceName: resourceName,
199 // NewErrorWithError creates a new Error conforming object from the
200 // passed packageType, method, statusCode of the given resp (UndefinedStatusCode
201 // if resp is nil), message, and original error. message is treated as a format
202 // string to which the optional args apply.
203 func NewErrorWithError(original error, packageType string, method string, resp *http.Response, message string, args ...interface{}) RequestError {
204 if v, ok := original.(*RequestError); ok {
208 statusCode := autorest.UndefinedStatusCode
210 statusCode = resp.StatusCode
213 DetailedError: autorest.DetailedError{
215 PackageType: packageType,
217 StatusCode: statusCode,
218 Message: fmt.Sprintf(message, args...),
223 // WithReturningClientID returns a PrepareDecorator that adds an HTTP extension header of
224 // x-ms-client-request-id whose value is the passed, undecorated UUID (e.g.,
225 // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA"). It also sets the x-ms-return-client-request-id
226 // header to true such that UUID accompanies the http.Response.
227 func WithReturningClientID(uuid string) autorest.PrepareDecorator {
228 preparer := autorest.CreatePreparer(
230 WithReturnClientID(true))
232 return func(p autorest.Preparer) autorest.Preparer {
233 return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
234 r, err := p.Prepare(r)
238 return preparer.Prepare(r)
243 // WithClientID returns a PrepareDecorator that adds an HTTP extension header of
244 // x-ms-client-request-id whose value is passed, undecorated UUID (e.g.,
245 // "0F39878C-5F76-4DB8-A25D-61D2C193C3CA").
246 func WithClientID(uuid string) autorest.PrepareDecorator {
247 return autorest.WithHeader(HeaderClientID, uuid)
250 // WithReturnClientID returns a PrepareDecorator that adds an HTTP extension header of
251 // x-ms-return-client-request-id whose boolean value indicates if the value of the
252 // x-ms-client-request-id header should be included in the http.Response.
253 func WithReturnClientID(b bool) autorest.PrepareDecorator {
254 return autorest.WithHeader(HeaderReturnClientID, strconv.FormatBool(b))
257 // ExtractClientID extracts the client identifier from the x-ms-client-request-id header set on the
258 // http.Request sent to the service (and returned in the http.Response)
259 func ExtractClientID(resp *http.Response) string {
260 return autorest.ExtractHeaderValue(HeaderClientID, resp)
263 // ExtractRequestID extracts the Azure server generated request identifier from the
264 // x-ms-request-id header.
265 func ExtractRequestID(resp *http.Response) string {
266 return autorest.ExtractHeaderValue(HeaderRequestID, resp)
269 // WithErrorUnlessStatusCode returns a RespondDecorator that emits an
270 // azure.RequestError by reading the response body unless the response HTTP status code
271 // is among the set passed.
273 // If there is a chance service may return responses other than the Azure error
274 // format and the response cannot be parsed into an error, a decoding error will
275 // be returned containing the response body. In any case, the Responder will
276 // return an error if the status code is not satisfied.
278 // If this Responder returns an error, the response body will be replaced with
279 // an in-memory reader, which needs no further closing.
280 func WithErrorUnlessStatusCode(codes ...int) autorest.RespondDecorator {
281 return func(r autorest.Responder) autorest.Responder {
282 return autorest.ResponderFunc(func(resp *http.Response) error {
283 err := r.Respond(resp)
284 if err == nil && !autorest.ResponseHasStatusCode(resp, codes...) {
286 defer resp.Body.Close()
288 // Copy and replace the Body in case it does not contain an error object.
289 // This will leave the Body available to the caller.
290 b, decodeErr := autorest.CopyAndDecode(autorest.EncodedAsJSON, resp.Body, &e)
291 resp.Body = ioutil.NopCloser(&b)
292 if decodeErr != nil {
293 return fmt.Errorf("autorest/azure: error response cannot be parsed: %q error: %v", b.String(), decodeErr)
295 if e.ServiceError == nil {
296 // Check if error is unwrapped ServiceError
297 if err := json.Unmarshal(b.Bytes(), &e.ServiceError); err != nil {
301 if e.ServiceError.Message == "" {
302 // if we're here it means the returned error wasn't OData v4 compliant.
303 // try to unmarshal the body as raw JSON in hopes of getting something.
304 rawBody := map[string]interface{}{}
305 if err := json.Unmarshal(b.Bytes(), &rawBody); err != nil {
308 e.ServiceError = &ServiceError{
310 Message: "Unknown service error",
312 if len(rawBody) > 0 {
313 e.ServiceError.Details = []map[string]interface{}{rawBody}
317 e.RequestID = ExtractRequestID(resp)
318 if e.StatusCode == nil {
319 e.StatusCode = resp.StatusCode