github.com/akamai/AkamaiOPEN-edgegrid-golang/v8@v8.1.0/pkg/imaging/policy.go (about) 1 package imaging 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "net/http" 9 10 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v8/pkg/edgegriderr" 11 12 validation "github.com/go-ozzo/ozzo-validation/v4" 13 ) 14 15 // Code in this package is using autogenerated code located in the policy.gen.go file. 16 // Generator code is located in `dxe-tools` repo, and it is using OpenApi schema from 17 // https://git.source.akamai.com/users/eleclair/repos/terraform/browse/docs/schemas 18 19 type ( 20 // Policies is an Image and Video Manager API interface for Policy 21 // 22 // See: https://techdocs.akamai.com/ivm/reference/api 23 Policies interface { 24 // ListPolicies lists all Policies for the given network and an account 25 // 26 // See: https://techdocs.akamai.com/ivm/reference/get-policies 27 ListPolicies(context.Context, ListPoliciesRequest) (*ListPoliciesResponse, error) 28 29 // GetPolicy gets specific policy by PolicyID 30 GetPolicy(context.Context, GetPolicyRequest) (PolicyOutput, error) 31 32 // UpsertPolicy creates or updates the configuration for a policy 33 UpsertPolicy(context.Context, UpsertPolicyRequest) (*PolicyResponse, error) 34 35 // DeletePolicy deletes a policy 36 DeletePolicy(context.Context, DeletePolicyRequest) (*PolicyResponse, error) 37 38 // GetPolicyHistory retrieves history of changes for a policy 39 GetPolicyHistory(context.Context, GetPolicyHistoryRequest) (*GetPolicyHistoryResponse, error) 40 41 // RollbackPolicy reverts a policy to its previous version and deploys it to the network 42 RollbackPolicy(ctx context.Context, request RollbackPolicyRequest) (*PolicyResponse, error) 43 } 44 45 // ListPoliciesRequest describes the parameters of the ListPolicies request 46 ListPoliciesRequest struct { 47 Network PolicyNetwork 48 ContractID string 49 PolicySetID string 50 } 51 52 // ListPoliciesResponse is a response returned by ListPolicies operations 53 ListPoliciesResponse struct { 54 ItemKind string `json:"itemKind"` 55 Items PolicyOutputs `json:"items"` 56 TotalItems int `json:"totalItems"` 57 } 58 59 // GetPolicyRequest describes the parameters of the GetPolicy request 60 GetPolicyRequest policyRequest 61 // DeletePolicyRequest describes the parameters of the DeletePolicy request 62 DeletePolicyRequest policyRequest 63 // GetPolicyHistoryRequest describes the parameters of the GetHistoryPolicy request 64 GetPolicyHistoryRequest policyRequest 65 // RollbackPolicyRequest describes the parameters of the RollbackPolicy request 66 RollbackPolicyRequest policyRequest 67 68 // policyRequest describes the parameters of the various policy requests 69 policyRequest struct { 70 PolicyID string 71 Network PolicyNetwork 72 ContractID string 73 PolicySetID string 74 } 75 76 // UpsertPolicyRequest describes the parameters of the UpsertPolicy request 77 UpsertPolicyRequest struct { 78 PolicyID string 79 Network PolicyNetwork 80 ContractID string 81 PolicySetID string 82 PolicyInput 83 } 84 85 // PolicyResponse describes response of the UpsertPolicy, DeletePolicy and RollbackPolicy responses 86 PolicyResponse struct { 87 Description string `json:"description"` 88 ID string `json:"id"` 89 OperationPerformed string `json:"operationPerformed"` 90 } 91 92 // GetPolicyHistoryResponse describes the parameters of the GetPolicyHistory response 93 GetPolicyHistoryResponse struct { 94 ItemKind string `json:"itemKind"` 95 TotalItems int `json:"totalItems"` 96 Items []PolicyHistoryItem `json:"items"` 97 } 98 99 // PolicyHistoryItem describes items of the history for policy 100 PolicyHistoryItem struct { 101 ID string `json:"id"` 102 DateCreated string `json:"dateCreated"` 103 Policy string `json:"policy"` 104 Action string `json:"action"` 105 User string `json:"user"` 106 Version int `json:"version"` 107 } 108 109 // PolicyOutput is implemented by PolicyOutput types (image and video) 110 PolicyOutput interface { 111 policyOutputType() string 112 } 113 114 // PolicyInput is implemented by PolicyInput types (image and video) 115 PolicyInput interface { 116 policyInputType() string 117 } 118 119 // PolicyOutputs is an array of PolicyOutput types (image and video) 120 PolicyOutputs []PolicyOutput 121 122 // PolicyNetwork represents the network where policy set is stored 123 PolicyNetwork string 124 125 // PolicyInputImage Specifies details for each policy, such as transformations to apply and variations in image size and formats 126 PolicyInputImage struct { 127 // Breakpoints The breakpoint widths (in pixels) to use to create derivative images/videos 128 Breakpoints *Breakpoints `json:"breakpoints,omitempty"` 129 // Hosts Hosts that are allowed for image/video URLs within transformations or variables 130 Hosts []string `json:"hosts,omitempty"` 131 // Output Dictates the output quality (either `quality` or `perceptualQuality`) and formats that are created for each resized image If unspecified, image formats are created to support all browsers at the default quality level (`85`), which includes formats such as WEBP, JPEG2000 and JPEG-XR for specific browsers 132 Output *OutputImage `json:"output,omitempty"` 133 // PostBreakpointTransformations Post-processing Transformations are applied to the image after image and quality settings have been applied 134 PostBreakpointTransformations PostBreakpointTransformations `json:"postBreakpointTransformations,omitempty"` 135 // RolloutDuration The amount of time in seconds that the policy takes to rollout. During the rollout an increasing proportion of images/videos will begin to use the new policy instead of the cached images/videos from the previous version 136 RolloutDuration *int `json:"rolloutDuration,omitempty"` 137 // ServeStaleDuration The amount of time in seconds that the policy will serve stale images. During the serve stale period realtime images will attempt to use the offline image from the previous policy version first if possible. 138 ServeStaleDuration *int `json:"serveStaleDuration,omitempty"` 139 // Transformations Set of image transformations to apply to the source image. If unspecified, no operations are performed 140 Transformations Transformations `json:"transformations,omitempty"` 141 // Variables Declares variables for use within the policy. Any variable declared here can be invoked throughout transformations as a [Variable](#variable) object, so that you don't have to specify values separately You can also pass in these variable names and values dynamically as query parameters in the image's request URL 142 Variables []Variable `json:"variables,omitempty"` 143 } 144 145 // PolicyInputVideo Specifies details for each policy such as video size 146 PolicyInputVideo struct { 147 // Breakpoints The breakpoint widths (in pixels) to use to create derivative images/videos 148 Breakpoints *Breakpoints `json:"breakpoints,omitempty"` 149 // Hosts Hosts that are allowed for image/video URLs within transformations or variables 150 Hosts []string `json:"hosts,omitempty"` 151 // Output Dictates the output quality that are created for each resized video 152 Output *OutputVideo `json:"output,omitempty"` 153 // RolloutDuration The amount of time in seconds that the policy takes to rollout. During the rollout an increasing proportion of images/videos will begin to use the new policy instead of the cached images/videos from the previous version 154 RolloutDuration *int `json:"rolloutDuration,omitempty"` 155 // Variables Declares variables for use within the policy. Any variable declared here can be invoked throughout transformations as a [Variable](#variable) object, so that you don't have to specify values separately You can also pass in these variable names and values dynamically as query parameters in the image's request URL 156 Variables []Variable `json:"variables,omitempty"` 157 } 158 ) 159 160 const ( 161 // PolicyNetworkStaging represents staging network 162 PolicyNetworkStaging PolicyNetwork = "staging" 163 // PolicyNetworkProduction represents production network 164 PolicyNetworkProduction PolicyNetwork = "production" 165 ) 166 167 var ( 168 // ErrUnmarshalPolicyOutputList represents an error while unmarshalling transformation list 169 ErrUnmarshalPolicyOutputList = errors.New("unmarshalling policy output list") 170 171 // ErrListPolicies is returned when ListPolicies fails 172 ErrListPolicies = errors.New("list policies") 173 174 // ErrGetPolicy is returned when GetPolicy fails 175 ErrGetPolicy = errors.New("get policy") 176 177 // ErrUpsertPolicy is returned when UpsertPolicy fails 178 ErrUpsertPolicy = errors.New("upsert policy") 179 180 // ErrDeletePolicy is returned when DeletePolicy fails 181 ErrDeletePolicy = errors.New("delete policy") 182 183 // ErrGetPolicyHistory is returned when GetPolicyHistory fails 184 ErrGetPolicyHistory = errors.New("get policy history") 185 186 // ErrRollbackPolicy is returned when RollbackPolicy fails 187 ErrRollbackPolicy = errors.New("rollback policy") 188 ) 189 190 func (*PolicyOutputImage) policyOutputType() string { 191 return "Image" 192 } 193 194 func (*PolicyOutputVideo) policyOutputType() string { 195 return "Video" 196 } 197 198 func (*PolicyInputImage) policyInputType() string { 199 return "Image" 200 } 201 202 func (*PolicyInputVideo) policyInputType() string { 203 return "Video" 204 } 205 206 // Validate validates PolicyInputImage 207 func (p *PolicyInputImage) Validate() error { 208 return validation.Errors{ 209 "Breakpoints": validation.Validate(p.Breakpoints), 210 "Hosts": validation.Validate(p.Hosts, validation.Each()), 211 "Output": validation.Validate(p.Output), 212 "PostBreakpointTransformations": validation.Validate(p.PostBreakpointTransformations), 213 "RolloutDuration": validation.Validate(p.RolloutDuration, 214 validation.Min(3600), 215 validation.Max(604800), 216 ), 217 "ServeStaleDuration": validation.Validate(p.ServeStaleDuration, 218 validation.Min(0), 219 validation.Max(2592000), 220 ), 221 "Transformations": validation.Validate(p.Transformations), 222 "Variables": validation.Validate(p.Variables, validation.Each()), 223 }.Filter() 224 } 225 226 // Validate validates PolicyInputVideo 227 func (p *PolicyInputVideo) Validate() error { 228 return validation.Errors{ 229 "Breakpoints": validation.Validate(p.Breakpoints), 230 "Hosts": validation.Validate(p.Hosts, validation.Each()), 231 "Output": validation.Validate(p.Output), 232 "RolloutDuration": validation.Validate(p.RolloutDuration, 233 validation.Min(3600), 234 validation.Max(604800), 235 ), 236 "Variables": validation.Validate(p.Variables, validation.Each()), 237 }.Filter() 238 } 239 240 var policyOutputHandlers = map[bool]func() PolicyOutput{ 241 false: func() PolicyOutput { return &PolicyOutputImage{} }, 242 true: func() PolicyOutput { return &PolicyOutputVideo{} }, 243 } 244 245 // UnmarshalJSON is a custom unmarshaler used to decode a slice of PolicyOutput interfaces 246 func (po *PolicyOutputs) UnmarshalJSON(in []byte) error { 247 data := make([]map[string]interface{}, 0) 248 if err := json.Unmarshal(in, &data); err != nil { 249 return fmt.Errorf("%w: %s", ErrUnmarshalPolicyOutputList, err) 250 } 251 for _, policyOutput := range data { 252 p, err := unmarshallPolicyOutput(policyOutput) 253 if err != nil { 254 return err 255 } 256 *po = append(*po, p) 257 } 258 return nil 259 } 260 261 func unmarshallPolicyOutput(policyOutput map[string]interface{}) (PolicyOutput, error) { 262 video, ok := policyOutput["video"] 263 if !ok { 264 return nil, fmt.Errorf("%w: policyOutput should contain 'video' field", ErrUnmarshalPolicyOutputList) 265 } 266 isVideo, ok := video.(bool) 267 if !ok { 268 return nil, fmt.Errorf("%w: 'video' field on policyOutput entry should be a boolean", ErrUnmarshalPolicyOutputList) 269 } 270 271 bytes, err := json.Marshal(policyOutput) 272 if err != nil { 273 return nil, fmt.Errorf("%w: %s", ErrUnmarshalPolicyOutputList, err) 274 } 275 276 indicatedPolicyOutputType, ok := policyOutputHandlers[isVideo] 277 if !ok { 278 return nil, fmt.Errorf("%w: unsupported policyOutput type: %v", ErrUnmarshalPolicyOutputList, isVideo) 279 } 280 ipt := indicatedPolicyOutputType() 281 err = json.Unmarshal(bytes, ipt) 282 if err != nil { 283 return nil, fmt.Errorf("%w: %s", ErrUnmarshalPolicyOutputList, err) 284 } 285 return ipt, nil 286 } 287 288 // Validate validates ListPoliciesRequest 289 func (v ListPoliciesRequest) Validate() error { 290 errs := validation.Errors{ 291 "ContractID": validation.Validate(v.ContractID, validation.Required), 292 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 293 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 294 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 295 } 296 return edgegriderr.ParseValidationErrors(errs) 297 } 298 299 // Validate validates GetPolicyRequest 300 func (v GetPolicyRequest) Validate() error { 301 errs := validation.Errors{ 302 "PolicyID": validation.Validate(v.PolicyID, validation.Required), 303 "ContractID": validation.Validate(v.ContractID, validation.Required), 304 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 305 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 306 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 307 } 308 return edgegriderr.ParseValidationErrors(errs) 309 } 310 311 // Validate validates UpsertPolicyRequest 312 func (v UpsertPolicyRequest) Validate() error { 313 errs := validation.Errors{ 314 "PolicyID": validation.Validate(v.PolicyID, validation.Required), 315 "ContractID": validation.Validate(v.ContractID, validation.Required), 316 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 317 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 318 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 319 "Policy": validation.Validate(v.PolicyInput, validation.Required), 320 //Validate, Policy Input 321 } 322 return edgegriderr.ParseValidationErrors(errs) 323 } 324 325 // Validate validates DeletePolicyRequest 326 func (v DeletePolicyRequest) Validate() error { 327 errs := validation.Errors{ 328 "PolicyID": validation.Validate(v.PolicyID, validation.Required), 329 "ContractID": validation.Validate(v.ContractID, validation.Required), 330 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 331 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 332 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 333 } 334 return edgegriderr.ParseValidationErrors(errs) 335 } 336 337 // Validate validates GetPolicyHistoryRequest 338 func (v GetPolicyHistoryRequest) Validate() error { 339 errs := validation.Errors{ 340 "PolicyID": validation.Validate(v.PolicyID, validation.Required), 341 "ContractID": validation.Validate(v.ContractID, validation.Required), 342 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 343 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 344 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 345 } 346 return edgegriderr.ParseValidationErrors(errs) 347 } 348 349 // Validate validates RollbackPolicyRequest 350 func (v RollbackPolicyRequest) Validate() error { 351 errs := validation.Errors{ 352 "PolicyID": validation.Validate(v.PolicyID, validation.Required), 353 "ContractID": validation.Validate(v.ContractID, validation.Required), 354 "PolicySetID": validation.Validate(v.PolicySetID, validation.Required), 355 "Network": validation.Validate(v.Network, validation.Required, validation.In(PolicyNetworkStaging, PolicyNetworkProduction). 356 Error(fmt.Sprintf("network has to be '%s', '%s'", PolicyNetworkStaging, PolicyNetworkProduction))), 357 } 358 return edgegriderr.ParseValidationErrors(errs) 359 } 360 361 func (i *imaging) ListPolicies(ctx context.Context, params ListPoliciesRequest) (*ListPoliciesResponse, error) { 362 logger := i.Log(ctx) 363 logger.Debug("ListPolicies") 364 365 if err := params.Validate(); err != nil { 366 return nil, fmt.Errorf("%s: %w:\n%s", ErrListPolicies, ErrStructValidation, err) 367 } 368 369 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/", params.Network) 370 371 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 372 if err != nil { 373 return nil, fmt.Errorf("%w: failed to create request: %s", ErrListPolicies, err) 374 } 375 376 req.Header.Set("Contract", params.ContractID) 377 req.Header.Set("Policy-Set", params.PolicySetID) 378 379 var result ListPoliciesResponse 380 resp, err := i.Exec(req, &result) 381 if err != nil { 382 return nil, fmt.Errorf("%w: request failed: %s", ErrListPolicies, err) 383 } 384 385 if resp.StatusCode != http.StatusOK { 386 return nil, fmt.Errorf("%s: %w", ErrListPolicies, i.Error(resp)) 387 } 388 389 return &result, nil 390 } 391 392 func (i *imaging) GetPolicy(ctx context.Context, params GetPolicyRequest) (PolicyOutput, error) { 393 logger := i.Log(ctx) 394 logger.Debug("GetPolicy") 395 396 if err := params.Validate(); err != nil { 397 return nil, fmt.Errorf("%s: %w:\n%s", ErrGetPolicy, ErrStructValidation, err) 398 } 399 400 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/%s", params.Network, params.PolicyID) 401 402 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 403 if err != nil { 404 return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetPolicy, err) 405 } 406 407 req.Header.Set("Contract", params.ContractID) 408 req.Header.Set("Policy-Set", params.PolicySetID) 409 410 var result map[string]interface{} 411 resp, err := i.Exec(req, &result) 412 if err != nil { 413 return nil, fmt.Errorf("%w: request failed: %s", ErrGetPolicy, err) 414 } 415 416 if resp.StatusCode != http.StatusOK { 417 return nil, fmt.Errorf("%s: %w", ErrGetPolicy, i.Error(resp)) 418 } 419 420 policyOutput, err := unmarshallPolicyOutput(result) 421 if err != nil { 422 return nil, err 423 } 424 425 return policyOutput, nil 426 } 427 428 func (i *imaging) UpsertPolicy(ctx context.Context, params UpsertPolicyRequest) (*PolicyResponse, error) { 429 logger := i.Log(ctx) 430 logger.Debug("UpsertPolicy") 431 432 if err := params.Validate(); err != nil { 433 return nil, fmt.Errorf("%s: %w:\n%s", ErrUpsertPolicy, ErrStructValidation, err) 434 } 435 436 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/%s", params.Network, params.PolicyID) 437 438 req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil) 439 if err != nil { 440 return nil, fmt.Errorf("%w: failed to create request: %s", ErrUpsertPolicy, err) 441 } 442 443 req.Header.Set("Contract", params.ContractID) 444 req.Header.Set("Policy-Set", params.PolicySetID) 445 446 var result PolicyResponse 447 resp, err := i.Exec(req, &result, params.PolicyInput) 448 if err != nil { 449 return nil, fmt.Errorf("%w: request failed: %s", ErrUpsertPolicy, err) 450 } 451 452 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated { 453 return nil, fmt.Errorf("%s: %w", ErrUpsertPolicy, i.Error(resp)) 454 } 455 456 return &result, nil 457 } 458 459 func (i *imaging) DeletePolicy(ctx context.Context, params DeletePolicyRequest) (*PolicyResponse, error) { 460 logger := i.Log(ctx) 461 logger.Debug("DeletePolicy") 462 463 if err := params.Validate(); err != nil { 464 return nil, fmt.Errorf("%s: %w:\n%s", ErrDeletePolicy, ErrStructValidation, err) 465 } 466 467 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/%s", params.Network, params.PolicyID) 468 469 req, err := http.NewRequestWithContext(ctx, http.MethodDelete, uri, nil) 470 if err != nil { 471 return nil, fmt.Errorf("%w: failed to create request: %s", ErrDeletePolicy, err) 472 } 473 474 req.Header.Set("Contract", params.ContractID) 475 req.Header.Set("Policy-Set", params.PolicySetID) 476 477 var result PolicyResponse 478 resp, err := i.Exec(req, &result) 479 if err != nil { 480 return nil, fmt.Errorf("%w: request failed: %s", ErrDeletePolicy, err) 481 } 482 483 if resp.StatusCode != http.StatusOK { 484 return nil, fmt.Errorf("%s: %w", ErrDeletePolicy, i.Error(resp)) 485 } 486 487 return &result, nil 488 } 489 490 func (i *imaging) GetPolicyHistory(ctx context.Context, params GetPolicyHistoryRequest) (*GetPolicyHistoryResponse, error) { 491 logger := i.Log(ctx) 492 logger.Debug("GetPolicyHistory") 493 494 if err := params.Validate(); err != nil { 495 return nil, fmt.Errorf("%s: %w:\n%s", ErrGetPolicyHistory, ErrStructValidation, err) 496 } 497 498 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/history/%s", params.Network, params.PolicyID) 499 500 req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil) 501 if err != nil { 502 return nil, fmt.Errorf("%w: failed to create request: %s", ErrGetPolicyHistory, err) 503 } 504 505 req.Header.Set("Contract", params.ContractID) 506 req.Header.Set("Policy-Set", params.PolicySetID) 507 508 var result GetPolicyHistoryResponse 509 resp, err := i.Exec(req, &result) 510 if err != nil { 511 return nil, fmt.Errorf("%w: request failed: %s", ErrGetPolicyHistory, err) 512 } 513 514 if resp.StatusCode != http.StatusOK { 515 return nil, fmt.Errorf("%s: %w", ErrGetPolicyHistory, i.Error(resp)) 516 } 517 518 return &result, nil 519 } 520 521 func (i *imaging) RollbackPolicy(ctx context.Context, params RollbackPolicyRequest) (*PolicyResponse, error) { 522 logger := i.Log(ctx) 523 logger.Debug("RollbackPolicy") 524 525 if err := params.Validate(); err != nil { 526 return nil, fmt.Errorf("%s: %w:\n%s", ErrRollbackPolicy, ErrStructValidation, err) 527 } 528 529 uri := fmt.Sprintf("/imaging/v2/network/%s/policies/rollback/%s", params.Network, params.PolicyID) 530 531 req, err := http.NewRequestWithContext(ctx, http.MethodPut, uri, nil) 532 if err != nil { 533 return nil, fmt.Errorf("%w: failed to create request: %s", ErrRollbackPolicy, err) 534 } 535 536 req.Header.Set("Contract", params.ContractID) 537 req.Header.Set("Policy-Set", params.PolicySetID) 538 539 var result PolicyResponse 540 resp, err := i.Exec(req, &result) 541 if err != nil { 542 return nil, fmt.Errorf("%w: request failed: %s", ErrRollbackPolicy, err) 543 } 544 545 if resp.StatusCode != http.StatusOK { 546 return nil, fmt.Errorf("%s: %w", ErrRollbackPolicy, i.Error(resp)) 547 } 548 549 return &result, nil 550 }