util.go raw

   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