github.com/MontFerret/ferret@v0.18.0/pkg/runtime/program.go (about)

     1  package runtime
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/pkg/errors"
     9  
    10  	"github.com/MontFerret/ferret/pkg/runtime/core"
    11  	"github.com/MontFerret/ferret/pkg/runtime/logging"
    12  	"github.com/MontFerret/ferret/pkg/runtime/values"
    13  )
    14  
    15  type Program struct {
    16  	src    string
    17  	body   core.Expression
    18  	params map[string]struct{}
    19  }
    20  
    21  func NewProgram(src string, body core.Expression, params map[string]struct{}) (*Program, error) {
    22  	if src == "" {
    23  		return nil, core.Error(core.ErrMissedArgument, "source")
    24  	}
    25  
    26  	if body == nil {
    27  		return nil, core.Error(core.ErrMissedArgument, "body")
    28  	}
    29  
    30  	return &Program{src, body, params}, nil
    31  }
    32  
    33  func (p *Program) Source() string {
    34  	return p.src
    35  }
    36  
    37  func (p *Program) Params() []string {
    38  	res := make([]string, 0, len(p.params))
    39  
    40  	for name := range p.params {
    41  		res = append(res, name)
    42  	}
    43  
    44  	return res
    45  }
    46  
    47  func (p *Program) Run(ctx context.Context, setters ...Option) (result []byte, err error) {
    48  	opts := NewOptions(setters)
    49  
    50  	err = p.validateParams(opts)
    51  
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	ctx = opts.WithContext(ctx)
    57  	logger := logging.FromContext(ctx)
    58  
    59  	defer func() {
    60  		if r := recover(); r != nil {
    61  			switch x := r.(type) {
    62  			case string:
    63  				err = errors.New(x)
    64  			case error:
    65  				err = errors.WithStack(err)
    66  			default:
    67  				err = errors.New("unknown panic")
    68  			}
    69  
    70  			logger.Error().
    71  				Timestamp().
    72  				Err(err).
    73  				Str("stack", fmt.Sprintf("%+v", err)).
    74  				Msg("panic")
    75  
    76  			result = nil
    77  		}
    78  	}()
    79  
    80  	scope, closeFn := core.NewRootScope()
    81  
    82  	defer func() {
    83  		if err := closeFn(); err != nil {
    84  			logger.Error().
    85  				Timestamp().
    86  				Err(err).
    87  				Msg("closing root scope")
    88  		}
    89  	}()
    90  
    91  	out, err := p.body.Exec(ctx, scope)
    92  
    93  	if err != nil {
    94  		js, _ := values.None.MarshalJSON()
    95  
    96  		return js, err
    97  	}
    98  
    99  	return out.MarshalJSON()
   100  }
   101  
   102  func (p *Program) MustRun(ctx context.Context, setters ...Option) []byte {
   103  	out, err := p.Run(ctx, setters...)
   104  
   105  	if err != nil {
   106  		panic(err)
   107  	}
   108  
   109  	return out
   110  }
   111  
   112  func (p *Program) validateParams(opts *Options) error {
   113  	if len(p.params) == 0 {
   114  		return nil
   115  	}
   116  
   117  	// There might be no errors.
   118  	// Thus, we allocate this slice lazily, on a first error.
   119  	var missedParams []string
   120  
   121  	for n := range p.params {
   122  		_, exists := opts.params[n]
   123  
   124  		if !exists {
   125  			if missedParams == nil {
   126  				missedParams = make([]string, 0, len(p.params))
   127  			}
   128  
   129  			missedParams = append(missedParams, "@"+n)
   130  		}
   131  	}
   132  
   133  	if len(missedParams) > 0 {
   134  		return core.Error(ErrMissedParam, strings.Join(missedParams, ", "))
   135  	}
   136  
   137  	return nil
   138  }