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 }