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
7package poller
8
9import (
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.
21const (
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.
30const (
31 StatusCancelled = "Cancelled"
32 StatusCompleted = "Completed"
33)
34
35// IsTerminalState returns true if the LRO's state is terminal.
36func IsTerminalState(s string) bool {
37 return Failed(s) || Succeeded(s)
38}
39
40// Failed returns true if the LRO's state is terminal failure.
41func 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.
46func 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
51func 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.
56func 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.
62var 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.
66func 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.
83func 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.
104func 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.
119func 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.
129func 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.
140func 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}