github.com/Jeffail/benthos/v3@v3.65.0/public/bloblang/environment.go (about)

     1  package bloblang
     2  
     3  import (
     4  	"github.com/Jeffail/benthos/v3/internal/bloblang"
     5  	"github.com/Jeffail/benthos/v3/internal/bloblang/parser"
     6  	"github.com/Jeffail/benthos/v3/internal/bloblang/query"
     7  )
     8  
     9  // Environment provides an isolated Bloblang environment where the available
    10  // features, functions and methods can be modified.
    11  type Environment struct {
    12  	env *bloblang.Environment
    13  }
    14  
    15  // GlobalEnvironment returns the global default environment. Modifying this
    16  // environment will impact all Bloblang parses that aren't initialized with an
    17  // isolated environment, as well as any new environments initialized after the
    18  // changes.
    19  func GlobalEnvironment() *Environment {
    20  	return &Environment{
    21  		env: bloblang.GlobalEnvironment(),
    22  	}
    23  }
    24  
    25  // NewEnvironment creates a fresh Bloblang environment, starting with the full
    26  // range of globally defined features (functions and methods), and provides APIs
    27  // for expanding or contracting the features available to this environment.
    28  //
    29  // It's worth using an environment when you need to restrict the access or
    30  // capabilities that certain bloblang mappings have versus others.
    31  //
    32  // For example, an environment could be created that removes any functions for
    33  // accessing environment variables or reading data from the host disk, which
    34  // could be used in certain situations without removing those functions globally
    35  // for all mappings.
    36  func NewEnvironment() *Environment {
    37  	return GlobalEnvironment().WithoutFunctions().WithoutMethods()
    38  }
    39  
    40  // NewEmptyEnvironment creates a fresh Bloblang environment starting completely
    41  // empty, where no functions or methods are initially available.
    42  func NewEmptyEnvironment() *Environment {
    43  	return &Environment{
    44  		env: bloblang.NewEmptyEnvironment(),
    45  	}
    46  }
    47  
    48  // Parse a Bloblang mapping using the Environment to determine the features
    49  // (functions and methods) available to the mapping.
    50  //
    51  // When a parsing error occurs the error will be the type *ParseError, which
    52  // gives access to the line and column where the error occurred, as well as a
    53  // method for creating a well formatted error message.
    54  func (e *Environment) Parse(blobl string) (*Executor, error) {
    55  	exec, err := e.env.NewMapping(blobl)
    56  	if err != nil {
    57  		if pErr, ok := err.(*parser.Error); ok {
    58  			return nil, internalToPublicParserError([]rune(blobl), pErr)
    59  		}
    60  		return nil, err
    61  	}
    62  	return newExecutor(exec), nil
    63  }
    64  
    65  // RegisterMethod adds a new Bloblang method to the environment. All method
    66  // names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/ (snake
    67  // case).
    68  func (e *Environment) RegisterMethod(name string, ctor MethodConstructor) error {
    69  	spec := query.NewMethodSpec(name, "").InCategory(query.MethodCategoryPlugin, "")
    70  	spec.Params = query.VariadicParams()
    71  	return e.env.RegisterMethod(spec, func(target query.Function, args *query.ParsedParams) (query.Function, error) {
    72  		fn, err := ctor(args.Raw()...)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		return query.ClosureFunction("method "+name, func(ctx query.FunctionContext) (interface{}, error) {
    77  			v, err := target.Exec(ctx)
    78  			if err != nil {
    79  				return nil, err
    80  			}
    81  			return fn(v)
    82  		}, target.QueryTargets), nil
    83  	})
    84  }
    85  
    86  // RegisterMethodV2 adds a new Bloblang method to the environment using a
    87  // provided ParamsSpec to define the name of the method and its parameters.
    88  //
    89  // Plugin names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
    90  // (snake case).
    91  func (e *Environment) RegisterMethodV2(name string, spec *PluginSpec, ctor MethodConstructorV2) error {
    92  	category := query.MethodCategory(spec.category)
    93  	if category == "" {
    94  		category = query.MethodCategoryPlugin
    95  	}
    96  	var examples []query.ExampleSpec
    97  	for _, e := range spec.examples {
    98  		var res []string
    99  		for _, inputOutput := range e.inputOutputs {
   100  			res = append(res, inputOutput[0], inputOutput[1])
   101  		}
   102  		examples = append(examples, query.NewExampleSpec(e.summary, e.mapping, res...))
   103  	}
   104  	iSpec := query.NewMethodSpec(name, spec.description).InCategory(category, "", examples...)
   105  	iSpec.Params = spec.params
   106  	return e.env.RegisterMethod(iSpec, func(target query.Function, args *query.ParsedParams) (query.Function, error) {
   107  		fn, err := ctor(&ParsedParams{par: args})
   108  		if err != nil {
   109  			return nil, err
   110  		}
   111  		return query.ClosureFunction("method "+name, func(ctx query.FunctionContext) (interface{}, error) {
   112  			v, err := target.Exec(ctx)
   113  			if err != nil {
   114  				return nil, err
   115  			}
   116  			return fn(v)
   117  		}, target.QueryTargets), nil
   118  	})
   119  }
   120  
   121  // RegisterFunction adds a new Bloblang function to the environment. All
   122  // function names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
   123  // (snake case).
   124  func (e *Environment) RegisterFunction(name string, ctor FunctionConstructor) error {
   125  	spec := query.NewFunctionSpec(query.FunctionCategoryPlugin, name, "")
   126  	spec.Params = query.VariadicParams()
   127  	return e.env.RegisterFunction(spec, func(args *query.ParsedParams) (query.Function, error) {
   128  		fn, err := ctor(args.Raw()...)
   129  		if err != nil {
   130  			return nil, err
   131  		}
   132  		return query.ClosureFunction("function "+name, func(ctx query.FunctionContext) (interface{}, error) {
   133  			return fn()
   134  		}, nil), nil
   135  	})
   136  }
   137  
   138  // RegisterFunctionV2 adds a new Bloblang function to the environment using a
   139  // provided ParamsSpec to define the name of the function and its parameters.
   140  //
   141  // Plugin names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
   142  // (snake case).
   143  func (e *Environment) RegisterFunctionV2(name string, spec *PluginSpec, ctor FunctionConstructorV2) error {
   144  	category := query.FunctionCategory(spec.category)
   145  	if category == "" {
   146  		category = query.FunctionCategoryPlugin
   147  	}
   148  	var examples []query.ExampleSpec
   149  	for _, e := range spec.examples {
   150  		var res []string
   151  		for _, inputOutput := range e.inputOutputs {
   152  			res = append(res, inputOutput[0], inputOutput[1])
   153  		}
   154  		examples = append(examples, query.NewExampleSpec(e.summary, e.mapping, res...))
   155  	}
   156  	iSpec := query.NewFunctionSpec(category, name, spec.description, examples...)
   157  	iSpec.Params = spec.params
   158  	return e.env.RegisterFunction(iSpec, func(args *query.ParsedParams) (query.Function, error) {
   159  		fn, err := ctor(&ParsedParams{par: args})
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		return query.ClosureFunction("function "+name, func(ctx query.FunctionContext) (interface{}, error) {
   164  			return fn()
   165  		}, nil), nil
   166  	})
   167  }
   168  
   169  // WithoutMethods returns a copy of the environment but with a variadic list of
   170  // method names removed. Instantiation of these removed methods within a mapping
   171  // will cause errors at parse time.
   172  func (e *Environment) WithoutMethods(names ...string) *Environment {
   173  	return &Environment{
   174  		env: e.env.WithoutMethods(names...),
   175  	}
   176  }
   177  
   178  // WithoutFunctions returns a copy of the environment but with a variadic list
   179  // of function names removed. Instantiation of these removed functions within a
   180  // mapping will cause errors at parse time.
   181  func (e *Environment) WithoutFunctions(names ...string) *Environment {
   182  	return &Environment{
   183  		env: e.env.WithoutFunctions(names...),
   184  	}
   185  }
   186  
   187  // WithDisabledImports returns a copy of the environment where imports within
   188  // mappings are disabled.
   189  func (e *Environment) WithDisabledImports() *Environment {
   190  	return &Environment{
   191  		env: e.env.WithDisabledImports(),
   192  	}
   193  }
   194  
   195  // WithCustomImporter returns a copy of the environment where imports from
   196  // mappings are done via a provided closure function.
   197  func (e *Environment) WithCustomImporter(fn func(name string) ([]byte, error)) *Environment {
   198  	return &Environment{
   199  		env: e.env.WithCustomImporter(fn),
   200  	}
   201  }
   202  
   203  // WithMaxMapRecursion returns a copy of the environment where the maximum
   204  // recursion allowed for maps is set to a given value. If the execution of a
   205  // mapping from this environment matches this number of recursive map calls the
   206  // mapping will error out.
   207  func (e *Environment) WithMaxMapRecursion(n int) *Environment {
   208  	return &Environment{
   209  		env: e.env.WithMaxMapRecursion(n),
   210  	}
   211  }
   212  
   213  //------------------------------------------------------------------------------
   214  
   215  // Parse a Bloblang mapping allowing the use of the globally accessible range of
   216  // features (functions and methods).
   217  //
   218  // When a parsing error occurs the error will be the type *ParseError, which
   219  // gives access to the line and column where the error occurred, as well as a
   220  // method for creating a well formatted error message.
   221  func Parse(blobl string) (*Executor, error) {
   222  	exec, err := parser.ParseMapping(parser.GlobalContext(), blobl)
   223  	if err != nil {
   224  		return nil, internalToPublicParserError([]rune(blobl), err)
   225  	}
   226  	return newExecutor(exec), nil
   227  }
   228  
   229  // RegisterMethod adds a new Bloblang method to the global environment. All
   230  // method names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
   231  // (snake case).
   232  func RegisterMethod(name string, ctor MethodConstructor) error {
   233  	return GlobalEnvironment().RegisterMethod(name, ctor)
   234  }
   235  
   236  // RegisterMethodV2 adds a new Bloblang method to the global environment. All
   237  // method names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
   238  // (snake case).
   239  func RegisterMethodV2(name string, spec *PluginSpec, ctor MethodConstructorV2) error {
   240  	return GlobalEnvironment().RegisterMethodV2(name, spec, ctor)
   241  }
   242  
   243  // RegisterFunction adds a new Bloblang function to the global environment. All
   244  // function names must match the regular expression /^[a-z0-9]+(_[a-z0-9]+)*$/
   245  // (snake case).
   246  func RegisterFunction(name string, ctor FunctionConstructor) error {
   247  	return GlobalEnvironment().RegisterFunction(name, ctor)
   248  }
   249  
   250  // RegisterFunctionV2 adds a new Bloblang function to the global environment.
   251  // All function names must match the regular expression
   252  // /^[a-z0-9]+(_[a-z0-9]+)*$/ (snake case).
   253  func RegisterFunctionV2(name string, spec *PluginSpec, ctor FunctionConstructorV2) error {
   254  	return GlobalEnvironment().RegisterFunctionV2(name, spec, ctor)
   255  }
   256  
   257  // WalkFunctions executes a provided function argument for every function that
   258  // has been registered to the environment.
   259  func (e *Environment) WalkFunctions(fn func(name string, spec *FunctionView)) {
   260  	e.env.WalkFunctions(func(name string, spec query.FunctionSpec) {
   261  		v := &FunctionView{spec: spec}
   262  		fn(name, v)
   263  	})
   264  }
   265  
   266  // WalkMethods executes a provided function argument for every method that has
   267  // been registered to the environment.
   268  func (e *Environment) WalkMethods(fn func(name string, spec *MethodView)) {
   269  	e.env.WalkMethods(func(name string, spec query.MethodSpec) {
   270  		v := &MethodView{spec: spec}
   271  		fn(name, v)
   272  	})
   273  }