1//go:build go1.16
2// +build go1.16
3
4// Copyright (c) Microsoft Corporation. All rights reserved.
5// Licensed under the MIT License.
6
7package body
8
9import (
10	"errors"
11	"net/http"
12
13	armpollers "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/internal/pollers"
14	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/pollers"
15	"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
16	"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
17)
18
19// Kind is the identifier of this type in a resume token.
20const Kind = "Body"
21
22// Applicable returns true if the LRO is using no headers, just provisioning state.
23// This is only applicable to PATCH and PUT methods and assumes no polling headers.
24func Applicable(resp *http.Response) bool {
25	// we can't check for absense of headers due to some misbehaving services
26	// like redis that return a Location header but don't actually use that protocol
27	return resp.Request.Method == http.MethodPatch || resp.Request.Method == http.MethodPut
28}
29
30// Poller is an LRO poller that uses the Body pattern.
31type Poller struct {
32	// The poller's type, used for resume token processing.
33	Type string `json:"type"`
34
35	// The URL for polling.
36	PollURL string `json:"pollURL"`
37
38	// The LRO's current state.
39	CurState string `json:"state"`
40}
41
42// New creates a new Poller from the provided initial response.
43func New(resp *http.Response, pollerID string) (*Poller, error) {
44	log.Write(log.LongRunningOperation, "Using Body poller.")
45	p := &Poller{
46		Type:    pollers.MakeID(pollerID, Kind),
47		PollURL: resp.Request.URL.String(),
48	}
49	// default initial state to InProgress.  depending on the HTTP
50	// status code and provisioning state, we might change the value.
51	curState := pollers.StatusInProgress
52	provState, err := armpollers.GetProvisioningState(resp)
53	if err != nil && !errors.Is(err, shared.ErrNoBody) {
54		return nil, err
55	}
56	if resp.StatusCode == http.StatusCreated && provState != "" {
57		// absense of provisioning state is ok for a 201, means the operation is in progress
58		curState = provState
59	} else if resp.StatusCode == http.StatusOK {
60		if provState != "" {
61			curState = provState
62		} else if provState == "" {
63			// for a 200, absense of provisioning state indicates success
64			curState = pollers.StatusSucceeded
65		}
66	} else if resp.StatusCode == http.StatusNoContent {
67		curState = pollers.StatusSucceeded
68	}
69	p.CurState = curState
70	return p, nil
71}
72
73// URL returns the polling URL.
74func (p *Poller) URL() string {
75	return p.PollURL
76}
77
78// Done returns true if the LRO has reached a terminal state.
79func (p *Poller) Done() bool {
80	return pollers.IsTerminalState(p.Status())
81}
82
83// Update updates the Poller from the polling response.
84func (p *Poller) Update(resp *http.Response) error {
85	if resp.StatusCode == http.StatusNoContent {
86		p.CurState = pollers.StatusSucceeded
87		return nil
88	}
89	state, err := armpollers.GetProvisioningState(resp)
90	if errors.Is(err, shared.ErrNoBody) {
91		// a missing response body in non-204 case is an error
92		return err
93	} else if state == "" {
94		// a response body without provisioning state is considered terminal success
95		state = pollers.StatusSucceeded
96	} else if err != nil {
97		return err
98	}
99	p.CurState = state
100	return nil
101}
102
103// FinalGetURL returns the empty string as no final GET is required for this poller type.
104func (*Poller) FinalGetURL() string {
105	return ""
106}
107
108// Status returns the status of the LRO.
109func (p *Poller) Status() string {
110	return p.CurState
111}
112