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}