github.com/akamai/AkamaiOPEN-edgegrid-golang/v4@v4.1.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/v4/pkg/edgegriderr"
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v4/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.Required.When(i.Network == ActivationNetworkProduction).
   224  				Error("ComplianceRecord is required for production network"),
   225  			validation.When(i.Network == ActivationNetworkProduction, validation.By(unitTestedFieldValidationRule))),
   226  	})
   227  }
   228  
   229  func unitTestedFieldValidationRule(value interface{}) error {
   230  	switch value.(type) {
   231  	case *ComplianceRecordNone:
   232  		if value.(*ComplianceRecordNone).UnitTested == false {
   233  			return errors.New("for PRODUCTION activation network and nonComplianceRecord, UnitTested value has to be set to true, otherwise API will not work correctly")
   234  		}
   235  	}
   236  	return nil
   237  }
   238  
   239  func (c *ComplianceRecordNone) noncomplianceReasonType() string {
   240  	return NoncomplianceReasonNone
   241  }
   242  
   243  // Validate validates ComplianceRecordNone
   244  func (c *ComplianceRecordNone) Validate() error {
   245  	return validation.Errors{
   246  		"CustomerEmail":  validation.Validate(c.CustomerEmail, validation.Required),
   247  		"PeerReviewedBy": validation.Validate(c.PeerReviewedBy, validation.Required),
   248  	}.Filter()
   249  }
   250  
   251  // MarshalJSON is a custom marshaller for ComplianceRecordNone struct
   252  func (c ComplianceRecordNone) MarshalJSON() ([]byte, error) {
   253  	type ComplianceRecord ComplianceRecordNone
   254  	v := struct {
   255  		ComplianceRecord
   256  		NoncomplianceReason string `json:"noncomplianceReason"`
   257  	}{
   258  		ComplianceRecord(c),
   259  		c.noncomplianceReasonType(),
   260  	}
   261  	return json.Marshal(v)
   262  }
   263  
   264  func (c *ComplianceRecordOther) noncomplianceReasonType() string {
   265  	return NoncomplianceReasonOther
   266  }
   267  
   268  // Validate validates ComplianceRecordOther
   269  func (c *ComplianceRecordOther) Validate() error {
   270  	return validation.Errors{
   271  		"OtherNoncomplianceReason": validation.Validate(c.OtherNoncomplianceReason, validation.Required),
   272  	}.Filter()
   273  }
   274  
   275  // MarshalJSON is a custom marshaller for ComplianceRecordOther struct
   276  func (c ComplianceRecordOther) MarshalJSON() ([]byte, error) {
   277  	type ComplianceRecord ComplianceRecordOther
   278  	v := struct {
   279  		ComplianceRecord
   280  		NoncomplianceReason string `json:"noncomplianceReason"`
   281  	}{
   282  		ComplianceRecord(c),
   283  		c.noncomplianceReasonType(),
   284  	}
   285  	return json.Marshal(v)
   286  }
   287  
   288  func (c *ComplianceRecordNoProductionTraffic) noncomplianceReasonType() string {
   289  	return NoncomplianceReasonNoProductionTraffic
   290  }
   291  
   292  // MarshalJSON is a custom marshaller for ComplianceRecordNoProductionTraffic struct
   293  func (c ComplianceRecordNoProductionTraffic) MarshalJSON() ([]byte, error) {
   294  	type ComplianceRecord ComplianceRecordNoProductionTraffic
   295  	v := struct {
   296  		ComplianceRecord
   297  		NoncomplianceReason string `json:"noncomplianceReason"`
   298  	}{
   299  		ComplianceRecord(c),
   300  		c.noncomplianceReasonType(),
   301  	}
   302  	return json.Marshal(v)
   303  }
   304  
   305  func (c *ComplianceRecordEmergency) noncomplianceReasonType() string {
   306  	return NoncomplianceReasonEmergency
   307  }
   308  
   309  // MarshalJSON is a custom marshaller for ComplianceRecordEmergency struct
   310  func (c ComplianceRecordEmergency) MarshalJSON() ([]byte, error) {
   311  	type ComplianceRecord ComplianceRecordEmergency
   312  	v := struct {
   313  		ComplianceRecord
   314  		NoncomplianceReason string `json:"noncomplianceReason"`
   315  	}{
   316  		ComplianceRecord(c),
   317  		c.noncomplianceReasonType(),
   318  	}
   319  	return json.Marshal(v)
   320  }
   321  
   322  // Validate validates DeactivateIncludeRequest
   323  func (i DeactivateIncludeRequest) Validate() error {
   324  	return edgegriderr.ParseValidationErrors(validation.Errors{
   325  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   326  		"Version":      validation.Validate(i.Version, validation.Required),
   327  		"Network":      validation.Validate(i.Network, validation.Required),
   328  		"NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required),
   329  	})
   330  }
   331  
   332  // Validate validates GetIncludeActivationRequest
   333  func (i GetIncludeActivationRequest) Validate() error {
   334  	return edgegriderr.ParseValidationErrors(validation.Errors{
   335  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   336  		"ActivationID": validation.Validate(i.ActivationID, validation.Required),
   337  	})
   338  }
   339  
   340  // Validate validates CancelIncludeActivationRequest
   341  func (i CancelIncludeActivationRequest) Validate() error {
   342  	return edgegriderr.ParseValidationErrors(validation.Errors{
   343  		"ContractID":   validation.Validate(i.ContractID, validation.Required),
   344  		"GroupID":      validation.Validate(i.GroupID, validation.Required),
   345  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   346  		"ActivationID": validation.Validate(i.ActivationID, validation.Required),
   347  	})
   348  }
   349  
   350  // Validate validates ListIncludeActivationsRequest
   351  func (i ListIncludeActivationsRequest) Validate() error {
   352  	return edgegriderr.ParseValidationErrors(validation.Errors{
   353  		"IncludeID":  validation.Validate(i.IncludeID, validation.Required),
   354  		"ContractID": validation.Validate(i.ContractID, validation.Required),
   355  		"GroupID":    validation.Validate(i.GroupID, validation.Required),
   356  	})
   357  }
   358  
   359  var (
   360  	// ErrActivateInclude is returned in case an error occurs on ActivateInclude operation
   361  	ErrActivateInclude = errors.New("activate include")
   362  	// ErrDeactivateInclude is returned in case an error occurs on DeactivateInclude operation
   363  	ErrDeactivateInclude = errors.New("deactivate include")
   364  	// ErrCancelIncludeActivation is returned in case an error occurs on CancelIncludeActivation operation
   365  	ErrCancelIncludeActivation = errors.New("cancel include activation")
   366  	// ErrGetIncludeActivation is returned in case an error occurs on GetIncludeActivation operation
   367  	ErrGetIncludeActivation = errors.New("get include activation")
   368  	// ErrListIncludeActivations is returned in case an error occurs on ListIncludeActivations operation
   369  	ErrListIncludeActivations = errors.New("list include activations")
   370  )
   371  
   372  func (p *papi) ActivateInclude(ctx context.Context, params ActivateIncludeRequest) (*ActivationIncludeResponse, error) {
   373  	logger := p.Log(ctx)
   374  	logger.Debug("ActivateInclude")
   375  
   376  	if err := params.Validate(); err != nil {
   377  		return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrStructValidation, err)
   378  	}
   379  
   380  	if params.IgnoreHTTPErrors == nil {
   381  		params.IgnoreHTTPErrors = tools.BoolPtr(true)
   382  	}
   383  
   384  	requestBody := struct {
   385  		ActivateIncludeRequest
   386  		ActivationType ActivationType `json:"activationType"`
   387  	}{
   388  		params,
   389  		ActivationTypeActivate,
   390  	}
   391  
   392  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID)
   393  
   394  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
   395  	if err != nil {
   396  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrActivateInclude, err)
   397  	}
   398  
   399  	var result ActivationIncludeResponse
   400  	resp, err := p.Exec(req, &result, requestBody)
   401  	if err != nil {
   402  		return nil, fmt.Errorf("%w: request failed: %s", ErrActivateInclude, err)
   403  	}
   404  
   405  	if resp.StatusCode != http.StatusCreated {
   406  		return nil, fmt.Errorf("%s: %w", ErrActivateInclude, p.Error(resp))
   407  	}
   408  
   409  	id, err := ResponseLinkParse(result.ActivationLink)
   410  	if err != nil {
   411  		return nil, fmt.Errorf("%s: %w: %s", ErrActivateInclude, ErrInvalidResponseLink, err)
   412  	}
   413  	result.ActivationID = id
   414  
   415  	return &result, nil
   416  }
   417  
   418  func (p *papi) DeactivateInclude(ctx context.Context, params DeactivateIncludeRequest) (*DeactivationIncludeResponse, error) {
   419  	logger := p.Log(ctx)
   420  	logger.Debug("DeactivateInclude")
   421  
   422  	if err := params.Validate(); err != nil {
   423  		return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrStructValidation, err)
   424  	}
   425  
   426  	if params.IgnoreHTTPErrors == nil {
   427  		params.IgnoreHTTPErrors = tools.BoolPtr(true)
   428  	}
   429  
   430  	requestBody := struct {
   431  		DeactivateIncludeRequest
   432  		ActivationType ActivationType `json:"activationType"`
   433  	}{
   434  		params,
   435  		ActivationTypeDeactivate,
   436  	}
   437  
   438  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID)
   439  
   440  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, nil)
   441  	if err != nil {
   442  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrDeactivateInclude, err)
   443  	}
   444  
   445  	var result DeactivationIncludeResponse
   446  	resp, err := p.Exec(req, &result, requestBody)
   447  	if err != nil {
   448  		return nil, fmt.Errorf("%w: request failed: %s", ErrDeactivateInclude, err)
   449  	}
   450  
   451  	if resp.StatusCode != http.StatusCreated {
   452  		return nil, fmt.Errorf("%s: %w", ErrDeactivateInclude, p.Error(resp))
   453  	}
   454  
   455  	id, err := ResponseLinkParse(result.ActivationLink)
   456  	if err != nil {
   457  		return nil, fmt.Errorf("%s: %w: %s", ErrDeactivateInclude, ErrInvalidResponseLink, err)
   458  	}
   459  	result.ActivationID = id
   460  
   461  	return &result, nil
   462  }
   463  
   464  func (p *papi) CancelIncludeActivation(ctx context.Context, params CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error) {
   465  	logger := p.Log(ctx)
   466  	logger.Debug("CancelIncludeActivation")
   467  
   468  	if err := params.Validate(); err != nil {
   469  		return nil, fmt.Errorf("%s: %w: %s", ErrCancelIncludeActivation, ErrStructValidation, err)
   470  	}
   471  
   472  	uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID))
   473  	if err != nil {
   474  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrCancelIncludeActivation, err)
   475  	}
   476  
   477  	q := uri.Query()
   478  	q.Add("contractId", params.ContractID)
   479  	q.Add("groupId", params.GroupID)
   480  	uri.RawQuery = q.Encode()
   481  
   482  	req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri.String(), nil)
   483  	if err != nil {
   484  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrCancelIncludeActivation, err)
   485  	}
   486  
   487  	var result CancelIncludeActivationResponse
   488  	resp, err := p.Exec(req, &result)
   489  	if err != nil {
   490  		return nil, fmt.Errorf("%w: request failed: %s", ErrCancelIncludeActivation, err)
   491  	}
   492  
   493  	if resp.StatusCode != http.StatusOK {
   494  		return nil, fmt.Errorf("%s: %w", ErrCancelIncludeActivation, p.Error(resp))
   495  	}
   496  
   497  	return &result, nil
   498  }
   499  
   500  func (p *papi) GetIncludeActivation(ctx context.Context, params GetIncludeActivationRequest) (*GetIncludeActivationResponse, error) {
   501  	logger := p.Log(ctx)
   502  	logger.Debug("GetIncludeActivation")
   503  
   504  	if err := params.Validate(); err != nil {
   505  		return nil, fmt.Errorf("%s: %w: %s", ErrGetIncludeActivation, ErrStructValidation, err)
   506  	}
   507  
   508  	uri := fmt.Sprintf("/papi/v1/includes/%s/activations/%s", params.IncludeID, params.ActivationID)
   509  
   510  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
   511  	if err != nil {
   512  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetIncludeActivation, err)
   513  	}
   514  
   515  	var result GetIncludeActivationResponse
   516  	resp, err := p.Exec(req, &result)
   517  	if err != nil {
   518  		return nil, fmt.Errorf("%w: request failed: %s", ErrGetIncludeActivation, err)
   519  	}
   520  
   521  	if resp.StatusCode != http.StatusOK {
   522  		return nil, fmt.Errorf("%s: %w", ErrGetIncludeActivation, p.Error(resp))
   523  	}
   524  
   525  	if len(result.Activations.Items) == 0 {
   526  		return nil, fmt.Errorf("%s: %w: ActivationID: %s", ErrGetIncludeActivation, ErrNotFound, params.ActivationID)
   527  	}
   528  	result.Activation = result.Activations.Items[0]
   529  
   530  	return &result, nil
   531  }
   532  
   533  func (p *papi) ListIncludeActivations(ctx context.Context, params ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) {
   534  	logger := p.Log(ctx)
   535  	logger.Debug("ListIncludeActivations")
   536  
   537  	if err := params.Validate(); err != nil {
   538  		return nil, fmt.Errorf("%s: %w: %s", ErrListIncludeActivations, ErrStructValidation, err)
   539  	}
   540  
   541  	uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID))
   542  	if err != nil {
   543  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListIncludeActivations, err)
   544  	}
   545  
   546  	q := uri.Query()
   547  	q.Add("contractId", params.ContractID)
   548  	q.Add("groupId", params.GroupID)
   549  	uri.RawQuery = q.Encode()
   550  
   551  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil)
   552  	if err != nil {
   553  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListIncludeActivations, err)
   554  	}
   555  
   556  	var result ListIncludeActivationsResponse
   557  	resp, err := p.Exec(req, &result)
   558  	if err != nil {
   559  		return nil, fmt.Errorf("%w: request failed: %s", ErrListIncludeActivations, err)
   560  	}
   561  
   562  	if resp.StatusCode != http.StatusOK {
   563  		return nil, fmt.Errorf("%s: %w", ErrListIncludeActivations, p.Error(resp))
   564  	}
   565  
   566  	return &result, nil
   567  }