1 //go:build go1.18
2 // +build go1.18
3 4 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // Licensed under the MIT License.
6 7 package poller
8 9 import (
10 "encoding/json"
11 "errors"
12 "fmt"
13 "net/http"
14 "net/url"
15 "strings"
16 17 "github.com/Azure/azure-sdk-for-go/sdk/internal/exported"
18 )
19 20 // the well-known set of LRO status/provisioning state values.
21 const (
22 StatusSucceeded = "Succeeded"
23 StatusCanceled = "Canceled"
24 StatusFailed = "Failed"
25 StatusInProgress = "InProgress"
26 )
27 28 // these are non-conformant states that we've seen in the wild.
29 // we support them for back-compat.
30 const (
31 StatusCancelled = "Cancelled"
32 StatusCompleted = "Completed"
33 )
34 35 // IsTerminalState returns true if the LRO's state is terminal.
36 func IsTerminalState(s string) bool {
37 return Failed(s) || Succeeded(s)
38 }
39 40 // Failed returns true if the LRO's state is terminal failure.
41 func Failed(s string) bool {
42 return strings.EqualFold(s, StatusFailed) || strings.EqualFold(s, StatusCanceled) || strings.EqualFold(s, StatusCancelled)
43 }
44 45 // Succeeded returns true if the LRO's state is terminal success.
46 func Succeeded(s string) bool {
47 return strings.EqualFold(s, StatusSucceeded) || strings.EqualFold(s, StatusCompleted)
48 }
49 50 // returns true if the LRO response contains a valid HTTP status code
51 func StatusCodeValid(resp *http.Response) bool {
52 return exported.HasStatusCode(resp, http.StatusOK, http.StatusAccepted, http.StatusCreated, http.StatusNoContent)
53 }
54 55 // IsValidURL verifies that the URL is valid and absolute.
56 func IsValidURL(s string) bool {
57 u, err := url.Parse(s)
58 return err == nil && u.IsAbs()
59 }
60 61 // ErrNoBody is returned if the response didn't contain a body.
62 var ErrNoBody = errors.New("the response did not contain a body")
63 64 // GetJSON reads the response body into a raw JSON object.
65 // It returns ErrNoBody if there was no content.
66 func GetJSON(resp *http.Response) (map[string]any, error) {
67 body, err := exported.Payload(resp, nil)
68 if err != nil {
69 return nil, err
70 }
71 if len(body) == 0 {
72 return nil, ErrNoBody
73 }
74 // unmarshall the body to get the value
75 var jsonBody map[string]any
76 if err = json.Unmarshal(body, &jsonBody); err != nil {
77 return nil, err
78 }
79 return jsonBody, nil
80 }
81 82 // provisioningState returns the provisioning state from the response or the empty string.
83 func provisioningState(jsonBody map[string]any) string {
84 jsonProps, ok := jsonBody["properties"]
85 if !ok {
86 return ""
87 }
88 props, ok := jsonProps.(map[string]any)
89 if !ok {
90 return ""
91 }
92 rawPs, ok := props["provisioningState"]
93 if !ok {
94 return ""
95 }
96 ps, ok := rawPs.(string)
97 if !ok {
98 return ""
99 }
100 return ps
101 }
102 103 // status returns the status from the response or the empty string.
104 func status(jsonBody map[string]any) string {
105 rawStatus, ok := jsonBody["status"]
106 if !ok {
107 return ""
108 }
109 status, ok := rawStatus.(string)
110 if !ok {
111 return ""
112 }
113 return status
114 }
115 116 // GetStatus returns the LRO's status from the response body.
117 // Typically used for Azure-AsyncOperation flows.
118 // If there is no status in the response body the empty string is returned.
119 func GetStatus(resp *http.Response) (string, error) {
120 jsonBody, err := GetJSON(resp)
121 if err != nil {
122 return "", err
123 }
124 return status(jsonBody), nil
125 }
126 127 // GetProvisioningState returns the LRO's state from the response body.
128 // If there is no state in the response body the empty string is returned.
129 func GetProvisioningState(resp *http.Response) (string, error) {
130 jsonBody, err := GetJSON(resp)
131 if err != nil {
132 return "", err
133 }
134 return provisioningState(jsonBody), nil
135 }
136 137 // GetResourceLocation returns the LRO's resourceLocation value from the response body.
138 // Typically used for Operation-Location flows.
139 // If there is no resourceLocation in the response body the empty string is returned.
140 func GetResourceLocation(resp *http.Response) (string, error) {
141 jsonBody, err := GetJSON(resp)
142 if err != nil {
143 return "", err
144 }
145 v, ok := jsonBody["resourceLocation"]
146 if !ok {
147 // it might be ok if the field doesn't exist, the caller must make that determination
148 return "", nil
149 }
150 vv, ok := v.(string)
151 if !ok {
152 return "", fmt.Errorf("the resourceLocation value %v was not in string format", v)
153 }
154 return vv, nil
155 }
156