github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.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  	"strings"
    11  
    12  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr"
    13  	"github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/tools"
    14  	validation "github.com/go-ozzo/ozzo-validation/v4"
    15  )
    16  
    17  type (
    18  	// IncludeActivations contains operations available on IncludeVersion resource
    19  	IncludeActivations interface {
    20  		// ActivateInclude creates a new include activation, which deactivates any current activation
    21  		//
    22  		// See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation
    23  		ActivateInclude(context.Context, ActivateIncludeRequest) (*ActivationIncludeResponse, error)
    24  
    25  		// DeactivateInclude deactivates the include activation
    26  		//
    27  		// See: https://techdocs.akamai.com/property-mgr/reference/post-include-activation
    28  		DeactivateInclude(context.Context, DeactivateIncludeRequest) (*DeactivationIncludeResponse, error)
    29  
    30  		// CancelIncludeActivation cancels specified include activation, if it is still in `PENDING` state
    31  		//
    32  		// See: https://techdocs.akamai.com/property-mgr/reference/delete-include-activation
    33  		CancelIncludeActivation(context.Context, CancelIncludeActivationRequest) (*CancelIncludeActivationResponse, error)
    34  
    35  		// GetIncludeActivation gets details about an activation
    36  		//
    37  		// See: https://techdocs.akamai.com/property-mgr/reference/get-include-activation
    38  		GetIncludeActivation(context.Context, GetIncludeActivationRequest) (*GetIncludeActivationResponse, error)
    39  
    40  		// ListIncludeActivations lists all activations for all versions of the include, on both production and staging networks
    41  		//
    42  		// See: https://techdocs.akamai.com/property-mgr/reference/get-include-activations
    43  		ListIncludeActivations(context.Context, ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error)
    44  	}
    45  
    46  	// ActivateIncludeRequest contains parameters used to activate include
    47  	ActivateIncludeRequest ActivateOrDeactivateIncludeRequest
    48  
    49  	// DeactivateIncludeRequest contains parameters used to deactivate include
    50  	DeactivateIncludeRequest ActivateOrDeactivateIncludeRequest
    51  
    52  	// ActivateOrDeactivateIncludeRequest contains parameters used to activate or deactivate include
    53  	ActivateOrDeactivateIncludeRequest struct {
    54  		IncludeID              string            `json:"-"`
    55  		Version                int               `json:"includeVersion"`
    56  		Network                ActivationNetwork `json:"network"`
    57  		Note                   string            `json:"note"`
    58  		NotifyEmails           []string          `json:"notifyEmails"`
    59  		AcknowledgeWarnings    []string          `json:"acknowledgeWarnings,omitempty"`
    60  		AcknowledgeAllWarnings bool              `json:"acknowledgeAllWarnings"`
    61  		IgnoreHTTPErrors       *bool             `json:"ignoreHttpErrors,omitempty"`
    62  		ComplianceRecord       complianceRecord  `json:"complianceRecord,omitempty"`
    63  	}
    64  
    65  	// CancelIncludeActivationRequest contains parameters used to cancel pending activation of include
    66  	CancelIncludeActivationRequest struct {
    67  		ContractID   string
    68  		GroupID      string
    69  		IncludeID    string
    70  		ActivationID string
    71  	}
    72  
    73  	// CancelIncludeActivationResponse represents a response object returned by CancelIncludeActivation operation
    74  	CancelIncludeActivationResponse ListIncludeActivationsResponse
    75  
    76  	// ActivationIncludeResponse represents a response object returned by ActivateInclude operation
    77  	ActivationIncludeResponse struct {
    78  		ActivationID   string `json:"-"`
    79  		ActivationLink string `json:"activationLink"`
    80  	}
    81  
    82  	// DeactivationIncludeResponse represents a response object returned by DeactivateInclude operation
    83  	DeactivationIncludeResponse struct {
    84  		ActivationID   string `json:"-"`
    85  		ActivationLink string `json:"activationLink"`
    86  	}
    87  
    88  	// GetIncludeActivationRequest contains parameters used to get the include activation
    89  	GetIncludeActivationRequest struct {
    90  		IncludeID    string
    91  		ActivationID string
    92  	}
    93  
    94  	// GetIncludeActivationResponse represents a response object returned by GetIncludeActivation
    95  	GetIncludeActivationResponse struct {
    96  		AccountID   string                `json:"accountId"`
    97  		ContractID  string                `json:"contractId"`
    98  		GroupID     string                `json:"groupId"`
    99  		Activations IncludeActivationsRes `json:"activations"`
   100  		Validations *Validations          `json:"validations,omitempty"`
   101  		Activation  IncludeActivation     `json:"-"`
   102  	}
   103  
   104  	// Validations represent include activation validation object
   105  	Validations struct {
   106  		ValidationSummary          ValidationSummary  `json:"validationSummary"`
   107  		ValidationProgressItemList ValidationProgress `json:"validationProgressItemList"`
   108  		Network                    ActivationNetwork  `json:"network"`
   109  	}
   110  
   111  	// ValidationSummary represent include activation validation summary object
   112  	ValidationSummary struct {
   113  		CompletePercent      float64 `json:"completePercent"`
   114  		HasValidationError   bool    `json:"hasValidationError"`
   115  		HasValidationWarning bool    `json:"hasValidationWarning"`
   116  		HasSystemError       bool    `json:"hasSystemError"`
   117  		HasClientError       bool    `json:"hasClientError"`
   118  		MessageState         string  `json:"messageState"`
   119  		ErrorMessage         string  `json:"errorMessage"`
   120  	}
   121  
   122  	// ValidationProgress represents include activation validation progress object
   123  	ValidationProgress struct {
   124  		ErrorItems []ErrorItem `json:"errorItemsList"`
   125  	}
   126  
   127  	// ErrorItem represents validation progress error item object
   128  	ErrorItem struct {
   129  		VersionID             int    `json:"versionId"`
   130  		PropertyName          string `json:"propertyName"`
   131  		VersionNumber         int    `json:"versionNumber"`
   132  		HasValidationError    bool   `json:"hasValidationError"`
   133  		HasValidationWarning  bool   `json:"hasValidationWarning"`
   134  		ValidationResultsLink string `json:"validationResultsLink"`
   135  	}
   136  
   137  	// ListIncludeActivationsRequest contains parameters used to list the include activations
   138  	ListIncludeActivationsRequest struct {
   139  		IncludeID  string
   140  		ContractID string
   141  		GroupID    string
   142  	}
   143  
   144  	// ListIncludeActivationsResponse represents a response object returned by ListIncludeActivations
   145  	ListIncludeActivationsResponse struct {
   146  		AccountID   string                `json:"accountId"`
   147  		ContractID  string                `json:"contractId"`
   148  		GroupID     string                `json:"groupId"`
   149  		Activations IncludeActivationsRes `json:"activations"`
   150  	}
   151  
   152  	// IncludeActivationsRes represents Activations object
   153  	IncludeActivationsRes struct {
   154  		Items []IncludeActivation `json:"items"`
   155  	}
   156  
   157  	// IncludeActivation represents an include activation object
   158  	IncludeActivation struct {
   159  		ActivationID        string                  `json:"activationId"`
   160  		Network             ActivationNetwork       `json:"network"`
   161  		ActivationType      ActivationType          `json:"activationType"`
   162  		Status              ActivationStatus        `json:"status"`
   163  		SubmitDate          string                  `json:"submitDate"`
   164  		UpdateDate          string                  `json:"updateDate"`
   165  		Note                string                  `json:"note"`
   166  		NotifyEmails        []string                `json:"notifyEmails"`
   167  		FMAActivationState  string                  `json:"fmaActivationState"`
   168  		FallbackInfo        *ActivationFallbackInfo `json:"fallbackInfo"`
   169  		IncludeID           string                  `json:"includeId"`
   170  		IncludeName         string                  `json:"includeName"`
   171  		IncludeType         IncludeType             `json:"includeType"`
   172  		IncludeVersion      int                     `json:"includeVersion"`
   173  		IncludeActivationID string                  `json:"includeActivationId"`
   174  	}
   175  
   176  	// complianceRecord is an interface for ComplianceRecord data type
   177  	complianceRecord interface {
   178  		noncomplianceReasonType() string
   179  	}
   180  
   181  	// ComplianceRecordNone holds data relevant for ComplianceRecord with noncomplianceReason 'None'
   182  	ComplianceRecordNone struct {
   183  		CustomerEmail  string `json:"customerEmail"`
   184  		PeerReviewedBy string `json:"peerReviewedBy"`
   185  		UnitTested     bool   `json:"unitTested"`
   186  		TicketID       string `json:"ticketId,omitempty"`
   187  	}
   188  
   189  	// ComplianceRecordOther holds data relevant for ComplianceRecord with noncomplianceReason 'Other'
   190  	ComplianceRecordOther struct {
   191  		OtherNoncomplianceReason string `json:"otherNoncomplianceReason"`
   192  		TicketID                 string `json:"ticketId,omitempty"`
   193  	}
   194  
   195  	// ComplianceRecordNoProductionTraffic holds data relevant for ComplianceRecord with noncomplianceReason 'NoProductionTraffic'
   196  	ComplianceRecordNoProductionTraffic struct {
   197  		TicketID string `json:"ticketId,omitempty"`
   198  	}
   199  
   200  	// ComplianceRecordEmergency holds data relevant for ComplianceRecord with noncomplianceReason 'Emergency'
   201  	ComplianceRecordEmergency struct {
   202  		TicketID string `json:"ticketId,omitempty"`
   203  	}
   204  )
   205  
   206  const (
   207  	// NoncomplianceReasonNoProductionTraffic is noncompliance reason type for compliance record
   208  	NoncomplianceReasonNoProductionTraffic = "NO_PRODUCTION_TRAFFIC"
   209  	// NoncomplianceReasonOther is noncompliance reason type for compliance record
   210  	NoncomplianceReasonOther = "OTHER"
   211  	// NoncomplianceReasonEmergency is noncompliance reason type for compliance record
   212  	NoncomplianceReasonEmergency = "EMERGENCY"
   213  	// NoncomplianceReasonNone is noncompliance reason type for compliance record
   214  	NoncomplianceReasonNone = "NONE"
   215  )
   216  
   217  // Validate validates ActivateIncludeRequest
   218  func (i ActivateIncludeRequest) Validate() error {
   219  	return edgegriderr.ParseValidationErrors(validation.Errors{
   220  		"IncludeID":    validation.Validate(i.IncludeID, validation.Required),
   221  		"Version":      validation.Validate(i.Version, validation.Required),
   222  		"Network":      validation.Validate(i.Network, validation.Required),
   223  		"NotifyEmails": validation.Validate(i.NotifyEmails, validation.Required),
   224  		"ComplianceRecord": validation.Validate(i.ComplianceRecord,
   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 result.Validations != nil {
   526  		val := result.Validations.ValidationSummary
   527  		if val.HasClientError || val.HasValidationError || val.HasSystemError {
   528  			if err = extractError(val.ErrorMessage); err != nil {
   529  				return nil, fmt.Errorf("%s: %w", ErrGetIncludeActivation, err)
   530  			}
   531  		}
   532  	}
   533  
   534  	if len(result.Activations.Items) == 0 {
   535  		return nil, fmt.Errorf("%s: %w: ActivationID: %s", ErrGetIncludeActivation, ErrNotFound, params.ActivationID)
   536  	}
   537  	result.Activation = result.Activations.Items[0]
   538  
   539  	return &result, nil
   540  }
   541  
   542  func (p *papi) ListIncludeActivations(ctx context.Context, params ListIncludeActivationsRequest) (*ListIncludeActivationsResponse, error) {
   543  	logger := p.Log(ctx)
   544  	logger.Debug("ListIncludeActivations")
   545  
   546  	if err := params.Validate(); err != nil {
   547  		return nil, fmt.Errorf("%s: %w: %s", ErrListIncludeActivations, ErrStructValidation, err)
   548  	}
   549  
   550  	uri, err := url.Parse(fmt.Sprintf("/papi/v1/includes/%s/activations", params.IncludeID))
   551  	if err != nil {
   552  		return nil, fmt.Errorf("%w: failed to parse url: %s", ErrListIncludeActivations, err)
   553  	}
   554  
   555  	q := uri.Query()
   556  	q.Add("contractId", params.ContractID)
   557  	q.Add("groupId", params.GroupID)
   558  	uri.RawQuery = q.Encode()
   559  
   560  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil)
   561  	if err != nil {
   562  		return nil, fmt.Errorf("%w: failed to create request: %s", ErrListIncludeActivations, err)
   563  	}
   564  
   565  	var result ListIncludeActivationsResponse
   566  	resp, err := p.Exec(req, &result)
   567  	if err != nil {
   568  		return nil, fmt.Errorf("%w: request failed: %s", ErrListIncludeActivations, err)
   569  	}
   570  
   571  	if resp.StatusCode != http.StatusOK {
   572  		return nil, fmt.Errorf("%s: %w", ErrListIncludeActivations, p.Error(resp))
   573  	}
   574  
   575  	return &result, nil
   576  }
   577  
   578  // extractError extracts error from validation object in GetIncludeActivation response if it is present
   579  func extractError(rawError string) error {
   580  	startIndex := strings.Index(rawError, "{")
   581  	endIndex := strings.LastIndex(rawError, "}") + 1
   582  	formattedError := rawError[startIndex:endIndex]
   583  
   584  	var e ActivationError
   585  
   586  	if err := json.Unmarshal([]byte(formattedError), &e); err != nil {
   587  		return err
   588  	}
   589  
   590  	return &e
   591  }