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 }