lambda/rapi/model/error_cause.go (73 lines of code) (raw):
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package model
import (
"encoding/json"
"fmt"
)
// MaxErrorCauseSizeBytes limits the size of a cause,
// since the max X-Ray document size of 64kB
const MaxErrorCauseSizeBytes = 64 << 10
type exceptionStackFrame struct {
Path string `json:"path,omitempty"`
Line int `json:"line,omitempty"`
Label string `json:"label,omitempty"`
}
type exception struct {
Message string `json:"message,omitempty"`
Type string `json:"type,omitempty"`
Stack []exceptionStackFrame `json:"stack,omitempty"`
}
// ErrorCause represents the cause of an error reported by
// the runtime, and may contain stack traces and exceptions
type ErrorCause struct {
Exceptions []exception `json:"exceptions"`
WorkingDir string `json:"working_directory"`
Paths []string `json:"paths"`
Message string `json:"message,omitempty"`
}
// newErrorCause unmarshals JSON into an ErrorCause
func newErrorCause(errorCauseJSON []byte) (*ErrorCause, error) {
var ec ErrorCause
if err := json.Unmarshal(errorCauseJSON, &ec); err != nil {
return nil, fmt.Errorf("failed to parse error cause JSON: %s", err)
}
return &ec, nil
}
// isValid validates the ErrorCause format
func (ec *ErrorCause) isValid() bool {
if len(ec.WorkingDir) == 0 && len(ec.Paths) == 0 && len(ec.Exceptions) == 0 && len(ec.Message) == 0 {
// X-Ray public docs imply WorkingDir, Paths & Exceptions are not optional,
// but we are less restrictive.
// Message is not a valid field as per the latest X-Ray docs, but native runtimes
// use it via LambdaSandboxRuntime and the X-Ray Data Plane frontend supports it.
return false
}
return true
}
func (ec *ErrorCause) croppedJSON() []byte {
// Iteratively crop the error cause by a factor of its size
// until it is within the size limit
truncationFactors := []float64{0.8, 0.6, 0.4, 0.2}
for _, factor := range truncationFactors {
compactor := newErrorCauseCompactor(*ec)
compactor.crop(factor)
validErrorCauseJSON, err := json.Marshal(compactor.cause())
if err != nil {
break
}
if len(validErrorCauseJSON) <= MaxErrorCauseSizeBytes {
return validErrorCauseJSON
}
}
// If compaction failed, drop Exceptions & Path, and truncate
// Message & WorkingDir, using smallest possible factor
compactor := newErrorCauseCompactor(*ec)
compactor.crop(0)
validErrorCauseJSON, err := json.Marshal(compactor.cause())
if err != nil {
return nil
}
return validErrorCauseJSON
}
// ValidatedErrorCauseJSON returns an error if
// the ErrorCause JSON has an invalid format
func ValidatedErrorCauseJSON(errorCauseJSON []byte) ([]byte, error) {
errorCause, err := newErrorCause(errorCauseJSON)
if err != nil {
return nil, err
}
if !errorCause.isValid() {
return nil, fmt.Errorf("error cause body has invalid format: %s", errorCauseJSON)
}
validErrorCauseJSON, err := json.Marshal(errorCause)
if err != nil {
return nil, err
}
if len(validErrorCauseJSON) > MaxErrorCauseSizeBytes {
return errorCause.croppedJSON(), nil
}
return validErrorCauseJSON, nil
}