github.com/akamai/AkamaiOPEN-edgegrid-golang/v5@v5.0.0/pkg/papi/include_activations.go (about)

     1  package papi
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"net/http"
     9  	"net/url"
    10  
    11  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v5/pkg/edgegriderr"
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v5/pkg/tools"
    13  	validation "github.com/go-ozzo/ozzo-validation/v4"
    14  )
    15  
    16  type (
    17  	// IncludeActivations contains operations available on IncludeVersion resource
    18  	IncludeActivations interface {
    19  		// ActivateInclude creates a new include activation, which deactivates any current activation
    20  		//
    21  		// See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation
    22  		ActivateInclude(context.Context, ActivateIncludeRequest) (*ActivationIncludeResponse, error)
    23  
    24  		// DeactivateInclude deactivates the include activation
    25  		//
    26  		// See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation
    27  		DeactivateInclude(context.Context, DeactivateIncludeRequest) (*DeactivationIncludeResponse, error)
    28  
    29  		// CancelIncludeActivation cancels specified include activation, if it is still in `PENDING` state
    30  		//
    31  		// See: https://techdocs.akamai.com/property-mgr/reference/delete-include-activation
    32  		CancelIncludeActivation(context.Context, CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error)
    33  
    34  		// GetIncludeActivation gets details about an activation
    35  		//
    36  		// See: https://techdocs.akamai.com/property-mgr/reference/get-include-activation
    37  		GetIncludeActivation(context.Context, GetIncludeActivationRequest) (*GetIncludeActivationResponse, error)
    38  
    39  		// ListIncludeActivations lists all activations for all versions of the include, on both production and staging networks
    40  		//
    41  		// See: https://techdocs.akamai.com/property-mgr/reference/get-include-activations
    42  		ListIncludeActivations(context.Context, ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error)
    43  	}
    44  
    45  	// ActivateIncludeRequest contains parameters used to activate include
    46  	ActivateIncludeRequest ActivateOrDeactivateIncludeRequest
    47  
    48  	// DeactivateIncludeRequest contains parameters used to deactivate include
    49  	DeactivateIncludeRequest ActivateOrDeactivateIncludeRequest
    50  
    51  	// ActivateOrDeactivateIncludeRequest contains parameters used to activate or deactivate include
    52  	ActivateOrDeactivateIncludeRequest struct {
    53  		IncludeID              string            `json:"-"`
    54  		Version                int               `json:"includeVersion"`
    55  		Network                ActivationNetwork `json:"network"`
    56  		Note                   string            `json:"note"`
    57  		NotifyEmails           []string          `json:"notifyEmails"`
    58  		AcknowledgeWarnings    []string          `json:"acknowledgeWarnings,omitempty"`
    59  		AcknowledgeAllWarnings bool              `json:"acknowledgeAllWarnings"`
    60  		IgnoreHTTPErrors       *bool             `json:"ignoreHttpErrors,omitempty"`
    61  		ComplianceRecord       complianceRecord  `json:"complianceRecord,omitempty"`
    62  	}
    63  
    64  	// CancelIncludeActivationRequest contains parameters used to cancel pending activation of include
    65  	CancelIncludeActivationRequest struct {
    66  		ContractID   string
    67  		GroupID      string
    68  		IncludeID    string
    69  		ActivationID string
    70  	}
    71  
    72  	// CancelIncludeActivationResponse represents a response object returned by CancelIncludeActivation operation
    73  	CancelIncludeActivationResponse ListIncludeActivationsResponse
    74  
    75  	// ActivationIncludeResponse represents a response object returned by ActivateInclude operation
    76  	ActivationIncludeResponse struct {
    77  		ActivationID   string `json:"-"`
    78  		ActivationLink string `json:"activationLink"`
    79  	}
    80  
    81  	// DeactivationIncludeResponse represents a response object returned by DeactivateInclude operation
    82  	DeactivationIncludeResponse struct {
    83  		ActivationID   string `json:"-"`
    84  		ActivationLink string `json:"activationLink"`
    85  	}
    86  
    87  	// GetIncludeActivationRequest contains parameters used to get the include activation
    88  	GetIncludeActivationRequest struct {
    89  		IncludeID    string
    90  		ActivationID string
    91  	}
    92  
    93  	// GetIncludeActivationResponse represents a response object returned by GetIncludeActivation
    94  	GetIncludeActivationResponse struct {
    95  		AccountID   string                `json:"accountId"`
    96  		ContractID  string                `json:"contractId"`
    97  		GroupID     string                `json:"groupId"`
    98  		Activations IncludeActivationsRes `json:"activations"`
    99  		Validations *Validations          `json:"validations,omitempty"`
   100  		Activation  IncludeActivation     `json:"-"`
   101  	}
   102  
   103  	// Validations represent include activation validation object
   104  	Validations struct {
   105  		ValidationSummary          ValidationSummary  `json:"validationSummary"`
   106  		ValidationProgressItemList ValidationProgress `json:"validationProgressItemList"`
   107  		Network                    ActivationNetwork  `json:"network"`
   108  	}
   109  
   110  	// ValidationSummary represent include activation validation summary object
   111  	ValidationSummary struct {
   112  		CompletePercent      float64 `json:"completePercent"`
   113  		HasValidationError   bool    `json:"hasValidationError"`
   114  		HasValidationWarning bool    `json:"hasValidationWarning"`
   115  		HasSystemError       bool    `json:"hasSystemError"`
   116  		HasClientError       bool    `json:"hasClientError"`
   117  		MessageState         string  `json:"messageState"`
   118  	}
   119  
   120  	// ValidationProgress represents include activation validation progress object
   121  	ValidationProgress struct {
   122  		ErrorItems []ErrorItem `json:"errorItemsList"`
   123  	}
   124  
   125  	// ErrorItem represents validation progress error item object
   126  	ErrorItem struct {
   127  		VersionID             int    `json:"versionId"`
   128  		PropertyName          string `json:"propertyName"`
   129  		VersionNumber         int    `json:"versionNumber"`
   130  		HasValidationError    bool   `json:"hasValidationError"`
   131  		HasValidationWarning  bool   `json:"hasValidationWarning"`
   132  		ValidationResultsLink string `json:"validationResultsLink"`
   133  	}
   134  
   135  	// ListIncludeActivationsRequest contains parameters used to list the include activations
   136  	ListIncludeActivationsRequest struct {
   137  		IncludeID  string
   138  		ContractID string
   139  		GroupID    string
   140  	}
   141  
   142  	// ListIncludeActivationsResponse represents a response object returned by ListIncludeActivations
   143  	ListIncludeActivationsResponse struct {
   144  		AccountID   string                `json:"accountId"`
   145  		ContractID  string                `json:"contractId"`
   146  		GroupID     string                `json:"groupId"`
   147  		Activations IncludeActivationsRes `json:"activations"`
   148  	}
   149  
   150  	// IncludeActivationsRes represents Activations object
   151  	IncludeActivationsRes struct {
   152  		Items []IncludeActivation `json:"items"`
   153  	}
   154  
   155  	// IncludeActivation represents an include activation object
   156  	IncludeActivation struct {
   157  		ActivationID        string                  `json:"activationId"`
   158  		Network             ActivationNetwork       `json:"network"`
   159  		ActivationType      ActivationType          `json:"activationType"`
   160  		Status              ActivationStatus        `json:"status"`
   161  		SubmitDate          string                  `json:"submitDate"`
   162  		UpdateDate          string                  `json:"updateDate"`
   163  		Note                string                  `json:"note"`
   164  		NotifyEmails        []string                `json:"notifyEmails"`
   165  		FMAActivationState  string                  `json:"fmaActivationState"`
   166  		FallbackInfo        *ActivationFallbackInfo `json:"fallbackInfo"`
   167  		IncludeID           string                  `json:"includeId"`
   168  		IncludeName         string                  `json:"includeName"`
   169  		IncludeType         IncludeType             `json:"includeType"`
   170  		IncludeVersion      int                     `json:"includeVersion"`
   171  		IncludeActivationID string                  `json:"includeActivationId"`
   172  	}
   173  
   174  	// complianceRecord is an interface for ComplianceRecord data type
   175  	complianceRecord interface {
   176  		noncomplianceReasonType() string
   177  	}
   178  
   179  	// ComplianceRecordNone holds data relevant for ComplianceRecord with noncomplianceReason 'None'
   180  	ComplianceRecordNone struct {
   181  		CustomerEmail  string `json:"customerEmail"`
   182  		PeerReviewedBy string `json:"peerReviewedBy"`
   183  		UnitTested     bool   `json:"unitTested"`
   184  		TicketID       string `json:"ticketId,omitempty"`
   185  	}
   186  
   187  	// ComplianceRecordOther holds data relevant for ComplianceRecord with noncomplianceReason 'Other'
   188  	ComplianceRecordOther struct {
   189  		OtherNoncomplianceReason string `json:"otherNoncomplianceReason"`
   190  		TicketID                 string `json:"ticketId,omitempty"`
   191  	}
   192  
   193  	// ComplianceRecordNoProductionTraffic holds data relevant for ComplianceRecord with noncomplianceReason 'NoProductionTraffic'
   194  	ComplianceRecordNoProductionTraffic struct {
   195  		TicketID string `json:"ticketId,omitempty"`
   196  	}
   197  
   198  	// ComplianceRecordEmergency holds data relevant for ComplianceRecord with noncomplianceReason 'Emergency'
   199  	ComplianceRecordEmergency struct {
   200  		TicketID string `json:"ticketId,omitempty"`
   201  	}
   202  )
   203  
   204  const (
   205  	// NoncomplianceReasonNoProductionTraffic is noncompliance reason type for compliance record
   206  	NoncomplianceReasonNoProductionTraffic = "NO_PRODUCTION_TRAFFIC"
   207  	// NoncomplianceReasonOther is noncompliance reason type for compliance record
   208  	NoncomplianceReasonOther = "OTHER"
   209  	// NoncomplianceReasonEmergency is noncompliance reason type for compliance record
   210  	NoncomplianceReasonEmergency = "EMERGENCY"
   211  	// NoncomplianceReasonNone is noncompliance reason type for compliance record
   212  	NoncomplianceReasonNone = "NONE"
   213  )
   214  
   215  // Validate validates ActivateIncludeRequest
   216  func (i ActivateIncludeRequest) Validate() error {
   217  	return edgegriderr.ParseValidationErrors(validation.Errors{
   218  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   219  		"Version":      validation.Validate(i.Version, validation.Required),
   220  		"Network":      validation.Validate(i.Network, validation.Required),
   221  		"NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required),
   222  		"ComplianceRecord": validation.Validate(i.ComplianceRecord,
   223  			validation.When(i.Network == ActivationNetworkProduction, validation.By(unitTestedFieldValidationRule))),
   224  	})
   225  }
   226  
   227  func unitTestedFieldValidationRule(value interface{}) error {
   228  	switch value.(type) {
   229  	case *ComplianceRecordNone:
   230  		if value.(*ComplianceRecordNone).UnitTested == false {
   231  			return errors.New("for PRODUCTION activation network and nonComplianceRecord, UnitTested value has to be set to true, otherwise API will not work correctly")
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func (c *ComplianceRecordNone) noncomplianceReasonType() string {
   238  	return NoncomplianceReasonNone
   239  }
   240  
   241  // Validate validates ComplianceRecordNone
   242  func (c *ComplianceRecordNone) Validate() error {
   243  	return validation.Errors{
   244  		"CustomerEmail":  validation.Validate(c.CustomerEmail, validation.Required),
   245  		"PeerReviewedBy": validation.Validate(c.PeerReviewedBy, validation.Required),
   246  	}.Filter()
   247  }
   248  
   249  // MarshalJSON is a custom marshaller for ComplianceRecordNone struct
   250  func (c ComplianceRecordNone) MarshalJSON() ([]byte, error) {
   251  	type ComplianceRecord ComplianceRecordNone
   252  	v := struct {
   253  		ComplianceRecord
   254  		NoncomplianceReason string `json:"noncomplianceReason"`
   255  	}{
   256  		ComplianceRecord(c),
   257  		c.noncomplianceReasonType(),
   258  	}
   259  	return json.Marshal(v)
   260  }
   261  
   262  func (c *ComplianceRecordOther) noncomplianceReasonType() string {
   263  	return NoncomplianceReasonOther
   264  }
   265  
   266  // Validate validates ComplianceRecordOther
   267  func (c *ComplianceRecordOther) Validate() error {
   268  	return validation.Errors{
   269  		"OtherNoncomplianceReason": validation.Validate(c.OtherNoncomplianceReason, validation.Required),
   270  	}.Filter()
   271  }
   272  
   273  // MarshalJSON is a custom marshaller for ComplianceRecordOther struct
   274  func (c ComplianceRecordOther) MarshalJSON() ([]byte, error) {
   275  	type ComplianceRecord ComplianceRecordOther
   276  	v := struct {
   277  		ComplianceRecord
   278  		NoncomplianceReason string `json:"noncomplianceReason"`
   279  	}{
   280  		ComplianceRecord(c),
   281  		c.noncomplianceReasonType(),
   282  	}
   283  	return json.Marshal(v)
   284  }
   285  
   286  func (c *ComplianceRecordNoProductionTraffic) noncomplianceReasonType() string {
   287  	return NoncomplianceReasonNoProductionTraffic
   288  }
   289  
   290  // MarshalJSON is a custom marshaller for ComplianceRecordNoProductionTraffic struct
   291  func (c ComplianceRecordNoProductionTraffic) MarshalJSON() ([]byte, error) {
   292  	type ComplianceRecord ComplianceRecordNoProductionTraffic
   293  	v := struct {
   294  		ComplianceRecord
   295  		NoncomplianceReason string `json:"noncomplianceReason"`
   296  	}{
   297  		ComplianceRecord(c),
   298  		c.noncomplianceReasonType(),
   299  	}
   300  	return json.Marshal(v)
   301  }
   302  
   303  func (c *ComplianceRecordEmergency) noncomplianceReasonType() string {
   304  	return NoncomplianceReasonEmergency
   305  }
   306  
   307  // MarshalJSON is a custom marshaller for ComplianceRecordEmergency struct
   308  func (c ComplianceRecordEmergency) MarshalJSON() ([]byte, error) {
   309  	type ComplianceRecord ComplianceRecordEmergency
   310  	v := struct {
   311  		ComplianceRecord
   312  		NoncomplianceReason string `json:"noncomplianceReason"`
   313  	}{
   314  		ComplianceRecord(c),
   315  		c.noncomplianceReasonType(),
   316  	}
   317  	return json.Marshal(v)
   318  }
   319  
   320  // Validate validates DeactivateIncludeRequest
   321  func (i DeactivateIncludeRequest) Validate() error {
   322  	return edgegriderr.ParseValidationErrors(validation.Errors{
   323  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   324  		"Version":      validation.Validate(i.Version, validation.Required),
   325  		"Network":      validation.Validate(i.Network, validation.Required),
   326  		"NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required),
   327  	})
   328  }
   329  
   330  // Validate validates GetIncludeActivationRequest
   331  func (i GetIncludeActivationRequest) Validate() error {
   332  	return edgegriderr.ParseValidationErrors(validation.Errors{
   333  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   334  		"ActivationID": validation.Validate(i.ActivationID, validation.Required),
   335  	})
   336  }
   337  
   338  // Validate validates CancelIncludeActivationRequest
   339  func (i CancelIncludeActivationRequest) Validate() error {
   340  	return edgegriderr.ParseValidationErrors(validation.Errors{
   341  		"ContractID":   validation.Validate(i.ContractID, validation.Required),
   342  		"GroupID":      validation.Validate(i.GroupID, validation.Required),
   343  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   344  		"ActivationID": validation.Validate(i.ActivationID, validation.Required),
   345  	})
   346  }
   347  
   348  // Validate validates ListIncludeActivationsRequest
   349  func (i ListIncludeActivationsRequest) Validate() error {
   350  	return edgegriderr.ParseValidationErrors(validation.Errors{
   351  		"IncludeID":  validation.Validate(i.IncludeID, validation.Required),
   352  		"ContractID": validation.Validate(i.ContractID, validation.Required),
   353  		"GroupID":    validation.Validate(i.GroupID, validation.Required),
   354  	})
   355  }
   356  
   357  var (
   358  	// ErrActivateInclude is returned in case an error occurs on ActivateInclude operation
   359  	ErrActivateInclude = errors.New("activate include")
   360  	// ErrDeactivateInclude is returned in case an error occurs on DeactivateInclude operation
   361  	ErrDeactivateInclude = errors.New("deactivate include")
   362  	// ErrCancelIncludeActivation is returned in case an error occurs on CancelIncludeActivation operation
   363  	ErrCancelIncludeActivation = errors.New("cancel include activation")
   364  	// ErrGetIncludeActivation is returned in case an error occurs on GetIncludeActivation operation
   365  	ErrGetIncludeActivation = errors.New("get include activation")
   366  	// ErrListIncludeActivations is returned in case an error occurs on ListIncludeActivations operation
   367  	ErrListIncludeActivations = errors.New("list include activations")
   368  )
   369  
   370  func (p *papi) ActivateInclude(ctx context.Context, params ActivateIncludeRequest) (*ActivationIncludeResponse, error) {
   371  	logger := p.Log(ctx)
   372  	logger.Debug("ActivateInclude")
   373  
   374  	if err := params.Validate(); err != nil {
   375  		return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrStructValidation, err)
   376  	}
   377  
   378  	if params.IgnoreHTTPErrors == nil {
   379  		params.IgnoreHTTPErrors = tools.BoolPtr(true)
   380  	}
   381  
   382  	requestBody := struct {
   383  		ActivateIncludeRequest
   384  		ActivationType ActivationType `json:"activationType"`
   385  	}{
   386  		params,
   387  		ActivationTypeActivate,
   388  	}
   389  
   390  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID)
   391  
   392  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
   393  	if err != nil {
   394  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrActivateInclude, err)
   395  	}
   396  
   397  	var result ActivationIncludeResponse
   398  	resp, err := p.Exec(req, &result, requestBody)
   399  	if err != nil {
   400  		return nil, fmt.Errorf("%w: request failed: %s", ErrActivateInclude, err)
   401  	}
   402  
   403  	if resp.StatusCode != http.StatusCreated {
   404  		return nil, fmt.Errorf("%s: %w", ErrActivateInclude, p.Error(resp))
   405  	}
   406  
   407  	id, err := ResponseLinkParse(result.ActivationLink)
   408  	if err != nil {
   409  		return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrInvalidResponseLink, err)
   410  	}
   411  	result.ActivationID = id
   412  
   413  	return &result, nil
   414  }
   415  
   416  func (p *papi) DeactivateInclude(ctx context.Context, params DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) {
   417  	logger := p.Log(ctx)
   418  	logger.Debug("DeactivateInclude")
   419  
   420  	if err := params.Validate(); err != nil {
   421  		return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrStructValidation, err)
   422  	}
   423  
   424  	if params.IgnoreHTTPErrors == nil {
   425  		params.IgnoreHTTPErrors = tools.BoolPtr(true)
   426  	}
   427  
   428  	requestBody := struct {
   429  		DeactivateIncludeRequest
   430  		ActivationType ActivationType `json:"activationType"`
   431  	}{
   432  		params,
   433  		ActivationTypeDeactivate,
   434  	}
   435  
   436  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID)
   437  
   438  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
   439  	if err != nil {
   440  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrDeactivateInclude, err)
   441  	}
   442  
   443  	var result DeactivationIncludeResponse
   444  	resp, err := p.Exec(req, &result, requestBody)
   445  	if err != nil {
   446  		return nil, fmt.Errorf("%w: request failed: %s", ErrDeactivateInclude, err)
   447  	}
   448  
   449  	if resp.StatusCode != http.StatusCreated {
   450  		return nil, fmt.Errorf("%s: %w", ErrDeactivateInclude, p.Error(resp))
   451  	}
   452  
   453  	id, err := ResponseLinkParse(result.ActivationLink)
   454  	if err != nil {
   455  		return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrInvalidResponseLink, err)
   456  	}
   457  	result.ActivationID = id
   458  
   459  	return &result, nil
   460  }
   461  
   462  func (p *papi) CancelIncludeActivation(ctx context.Context, params CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) {
   463  	logger := p.Log(ctx)
   464  	logger.Debug("CancelIncludeActivation")
   465  
   466  	if err := params.Validate(); err != nil {
   467  		return nil, fmt.Errorf("%s: %w: %s", ErrCancelIncludeActivation, ErrStructValidation, err)
   468  	}
   469  
   470  	uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID))
   471  	if err != nil {
   472  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCancelIncludeActivation, err)
   473  	}
   474  
   475  	q := uri.Query()
   476  	q.Add("contractId", params.ContractID)
   477  	q.Add("groupId", params.GroupID)
   478  	uri.RawQuery = q.Encode()
   479  
   480  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil)
   481  	if err != nil {
   482  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrCancelIncludeActivation, err)
   483  	}
   484  
   485  	var result CancelIncludeActivationResponse
   486  	resp, err := p.Exec(req, &result)
   487  	if err != nil {
   488  		return nil, fmt.Errorf("%w: request failed: %s", ErrCancelIncludeActivation, err)
   489  	}
   490  
   491  	if resp.StatusCode != http.StatusOK {
   492  		return nil, fmt.Errorf("%s: %w", ErrCancelIncludeActivation, p.Error(resp))
   493  	}
   494  
   495  	return &result, nil
   496  }
   497  
   498  func (p *papi) GetIncludeActivation(ctx context.Context, params GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) {
   499  	logger := p.Log(ctx)
   500  	logger.Debug("GetIncludeActivation")
   501  
   502  	if err := params.Validate(); err != nil {
   503  		return nil, fmt.Errorf("%s: %w: %s", ErrGetIncludeActivation, ErrStructValidation, err)
   504  	}
   505  
   506  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID)
   507  
   508  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   509  	if err != nil {
   510  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetIncludeActivation, err)
   511  	}
   512  
   513  	var result GetIncludeActivationResponse
   514  	resp, err := p.Exec(req, &result)
   515  	if err != nil {
   516  		return nil, fmt.Errorf("%w: request failed: %s", ErrGetIncludeActivation, err)
   517  	}
   518  
   519  	if resp.StatusCode != http.StatusOK {
   520  		return nil, fmt.Errorf("%s: %w", ErrGetIncludeActivation, p.Error(resp))
   521  	}
   522  
   523  	if len(result.Activations.Items) == 0 {
   524  		return nil, fmt.Errorf("%s: %w: ActivationID: %s", ErrGetIncludeActivation, ErrNotFound, params.ActivationID)
   525  	}
   526  	result.Activation = result.Activations.Items[0]
   527  
   528  	return &result, nil
   529  }
   530  
   531  func (p *papi) ListIncludeActivations(ctx context.Context, params ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) {
   532  	logger := p.Log(ctx)
   533  	logger.Debug("ListIncludeActivations")
   534  
   535  	if err := params.Validate(); err != nil {
   536  		return nil, fmt.Errorf("%s: %w: %s", ErrListIncludeActivations, ErrStructValidation, err)
   537  	}
   538  
   539  	uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID))
   540  	if err != nil {
   541  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListIncludeActivations, err)
   542  	}
   543  
   544  	q := uri.Query()
   545  	q.Add("contractId", params.ContractID)
   546  	q.Add("groupId", params.GroupID)
   547  	uri.RawQuery = q.Encode()
   548  
   549  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil)
   550  	if err != nil {
   551  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListIncludeActivations, err)
   552  	}
   553  
   554  	var result ListIncludeActivationsResponse
   555  	resp, err := p.Exec(req, &result)
   556  	if err != nil {
   557  		return nil, fmt.Errorf("%w: request failed: %s", ErrListIncludeActivations, err)
   558  	}
   559  
   560  	if resp.StatusCode != http.StatusOK {
   561  		return nil, fmt.Errorf("%s: %w", ErrListIncludeActivations, p.Error(resp))
   562  	}
   563  
   564  	return &result, nil
   565  }