lookup

package module
v0.0.0-...-d92006c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 24, 2022 License: AGPL-3.0 Imports: 7 Imported by: 0

README

lookup

This is a "simple" lookup library I wrote for go.. It's designed to bring some of the dynamicness you can get with lookup solutions like Jsonpath and Jsonata to structures inside go. Inspired by Jackson's .Path()

It's minimal to my needs. I look forwards to hearing from others and working with them to expand the scope.

It works by trying to "find" the next component you have requested. It will dynamically create arrays as necessary.

Say for instance you have this structure here:

var (
	Err1 = errors.New("one")
)

type Root struct {
    Node1 Node1
    Node2 *Node2
}

func (r *Root) Method1 () (string, error) {
	return "hi", nil
}

func (r *Root) Method1 () (string, error) {
	return "", Err1
}

root := &Root{
    Node1: Node1{
        Name: "asdf",
    },
    Node2: []*Node2{
        {Size: 1,},
        {Size: 12,},
        {Size: 35,},
    },
}

You could run the following code on it:

log.Printf("%#v", lookup.Reflector(root).Find("Node1").Find("Name").Raw()) // "asdf"
log.Printf("%#v", lookup.Reflector(root).Find("Node1").Find("DoesntExist").Raw()) // nil
log.Printf("%#v", lookup.Reflector(root).Find("Node1").Find("DoesntExist", lookup.NewDefault("N/A")).Raw()) // "N/A"
log.Printf("%#v", lookup.Reflector(root).Find("Node2").Find("Size").Raw()) // []int{ 1,12,35 }
log.Printf("%#v", lookup.Reflector(root).Find("Node2", Index("1")).Find("Size").Raw()) // 12
log.Printf("%#v", lookup.Reflector(root).Find("Node2", Index("-1")).Find("Size").Raw()) // 35

It will even execute functions (provided they have no arguments, and 1 primitive return, or a primitive and an error return)

log.Printf("%#v", lookup.Reflector(root).Find("Method1").Raw()) // "hi"

All usages of the program should be null-safe you shouldn't be able to create a panic from inside the lookup codebase. (If you write a crashing function it does /not/ call recover())

When you get to an invalid path or an error, the object being returned from find() is a valid error

result := lookup.Reflector(root).Find("Node1").Find("DoesntExist")
if err, ok := result.(error); ok {
	panic(err)
}

It properly raps errors returned by functions:

result := lookup.Reflector(root).Find("Method2")
if errors.Is(result, Err1); ok {
	// We expected this error
}

find() is the main implementation. It is designed to be simple and "null safe" (as in doesn't create any itself you can create them though!) It hasn't been fully edge tested as I wrote it for my own testing - quickly. But expect the Find() function to be relatively stable.

Feel free to log issues and PRs:

  • For any reason really
  • Opinions welcome but not obligated to

How to use the library

The basic idea behind the library is to act a lot like a meta language for jsonpath or some such. Such as you would write a query as such:

Root.Field.ChildField.ArrayElements.Field

If it encounters an array, it selects every element and every subsequent field become an implicit map operation. Each field navigation is followed by a "modifier" such as in the following query, the "index" is a modifier.

lookup.Reflector(root).Find("Node2", Index("1")).Find("Size")

So .Find("Node2" extracts the array. Each modifier then is run over the results of "Node2", in this case the modifier "Index" takes the array and returns the single element.

Supported data structures

Input Data structure Description
Reflector The most developed data structure and the basis. It takes any go input and will attempt to use it. It will not support channels however. It uses reflection for navigation, that includes functions.
Invalidor This is typically to indicate that the search function has reached and invalid path. It provides an error interface, however doesn't necessarily mean that an error has occurred, it could simply be that there was no where to go. You can use this in conjunction with the modifiers to simply mean "false"
Constantor This is similar to the invalidor however it contains a constant and can mean true or false. Attempting to navigate a constant will not change your position. Use a Reflector if you need to navigation. Constantor can mean the end of a search. It's often used just for nagivation events.
Interfacteor This is like Reflector but it expects the data structure passed in to adhere to a interface Interface it is a naive implementation and is likely to change.
Relator This stores a path, which can be replayed. It's used mostly in modifiers for the purpose of providing relative queries (Via This() Parent() or Find(). On it's own it will act as a modifier meaning "If Exists". Such as lookup.Reflector(root).Find("Node2", This("Name")).Find("Size") Will filter Node2 and return an array of Size for all elements which have a valid .Name Field.

Todo data structures

Data structure Description
json.Raw / Jsonor A version I wish to develop which does on-demand deserialization of Json based on the query - In a way which would also work for yaml etc if possible without including them as libraries
Simpleor A typecast version of Reflector which works using type switching, type assertions rather than reflection, but will only work with a much more limited set of input

Modifiers (AKA Runners)

The modifier functions available:

Modifier Category Description Input Output
Index(i) Collections Selects a single index in an array An int (raw or as a string.) Another modifier which is run for another compatible result. The element referred to by index, or an Invalidor
Filter(?) Collections Runs a Modifier over a collection and filters out value based on boolean returned
Map(?) Collections Runs a modifier over a collection and converts it to another value based on content
Contains(?) Collections Returns Constantor(True) if scope contains ?
In(?) Collections Returns Constantor(True) if scope is in ?
Every(?) Collections Returns Constantor(True) if every element in scope is in ?
Any(?) Collections Returns Constantor(True) if any element in scope is in ?
Constant(?) Constant Returns Constantor(?)
True() Constant Returns Constantor(True)
False() Constant Constantor(False)
Array(?...) Constant Constantor(? as an array)
Match(?) Expression Permits scope if ? is True or Not Is Zero otherwise Invalidor Boolean
ToBool(?) Expression Converts scope to Constantor(bool) if possible otehrwise returns Invalidor Boolean
Truthy(?) Expression Converts scope to bool using truthy like logic otherwise returns Invalidor
Not(?) Expression Toggles Constantor(bool)
IsZero(?) Expression Uses Go Reflect's Value.IsZero to return Constantor(bool)
Default(?) Expression If scope is Invalidor Converts it to Constantor(?)
Find(?) Relator Runs a series of paths and Runners against the Scope.Current position
Parent(?) Relator Runs a series of paths and Runners against the Scope.Parent position (Note this changes)
This(?) Relator Runs a series of paths and Runners against the Scope.Current position
Result(?) Relator Runs a series of paths and Runners against the Scope.Position position
ValueOf(?) Valuor Evaluates ? as a Pathor and returns it as scope.

Planned / TODO

Modifier Category Description Input Output
Map(?) Collections Runs a modifier over a collection and converts it to another value based on content
Union(?) Collections Combine two results with no duplicates
Append(?) Collections Combine two results with duplicates
Intersection(?) Collections Combine two results only returning common values
First(?) Collections Returns the first value only that matches a predicate, using a Modifier as a predicate
Last(?) Collections Returns the last value only that matches a predicate, using a Modifier as a predicate
Range(?, ?) Collections Like Index but returns an array
If(?, ?, ?) Expression Conditional
Error(?) Invalidor Returns an invalid / failed result

Internals - Scope

(IIRC) Modifiers run with a scope. Depending on if they are Nested, or sequential modifies the scope. Scope doesn't escape out of a query.

So with:

lookup.Reflector(root).Find("Node2", Index(Constant("-1")), Index(Constant("-2"))).Find("Size", Index(Constant("-3"))

and

Node2:
  - Sizes:
      - 1
      - 2
      - 3
  - Sizes:
      - 4
      - 5
      - 6
  - Sizes:
      - 7
      - 8
      - 9

In all of the examples:

Index(Constant("-1")) sees

  • Scope.Parent = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Current = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Position = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Result: {Sizes: [7,8,9]}

Constant("-1") sees

  • Scope.Parent = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Current = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Position = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Result: -1

Index(Constant("-2")) sees

  • Scope.Parent = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Current = {Sizes: [7,8,9]}
  • Scope.Position = {Sizes: [7,8,9]}
  • Result: 8

Constant("-2") sees

  • Scope.Parent = [ { Sizes: [1,2,3] }, {Sizes: [4,5,6]}, {Sizes: [7,8,9]} ]
  • Scope.Current = {Sizes: [7,8,9]}
  • Scope.Position = {Sizes: [7,8,9]}
  • Result: -2

Note: With other Modifiers than index Scope.Current would be different to Scope.Position.

Public Extensions

Please put any library that extends this in this section here:

  • ...

Public License

This project is publicly available under the Affero GPL license.

Custom Licensing

If the AGPL is not suitable for your purposes, please log an issue or email me, and let's talk.

Q/A

Can I use it as part of tests in a private library

Yes. Tests are not considered part of the released binary.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNoSuchPath                = errors.New("no such path")
	ErrInvalidEvaluationFunction = errors.New("invalid evaluation function")
	ErrEvalFail                  = errors.New("path succeeded but evaluator failed")
	ErrMatchFail                 = errors.New("path succeeded match failed")
	ErrIndexOfNotArray           = errors.New("tried to index a non-array")
	ErrIndexValueNotValid        = errors.New("index value not valid")
	ErrUnknownIndexMode          = errors.New("unknown index mode")
	ErrIndexOutOfRange           = errors.New("index out of range")
	ErrValueNotIn                = errors.New("value not in set")
	ErrNoMatchesForQuery         = errors.New("nothing matched query")
	ErrFalse                     = errors.New("evaluated to false")
)

Functions

func Any

func Any(e Runner) *anyFunc

func Contains

func Contains(runner Runner) *containsFunc

func Default

func Default(i interface{}) *otherwiseFunc

Default used with .Find() as a PathOpt this will will fallback / default to the provided value regardless of future nagivations, it suppresses most errors / Invalidators.

func Equals

func Equals(e Runner) *equalsFunc

func Every

func Every(e Runner) *everyFunc

func ExtractPath

func ExtractPath(pather Pathor) string

ExtractPath retrieves the path use because I didn't export it.

func Filter

func Filter(expression Runner) *filterFunc

func In

func In(e Runner) *inFunc

func Index

func Index(i interface{}) *indexFunc

func IsZero

func IsZero(e Runner) *isZeroFunc

func Map

func Map(expression Runner) *mapFunc

func Match

func Match(e ...Runner) *matchFunc

func Not

func Not(e Runner) *notFunc

func PathBuilder

func PathBuilder(path string, r Pathor, cp CustomPath) string

func ToBool

func ToBool(expression Runner) *toBoolFunc

func Truthy

func Truthy(expression Runner) *truthyFunc

Types

type Constantor

type Constantor struct {
	// contains filtered or unexported fields
}

Constantor This object represents a non-navigable constant. It can be used as an argument applied on the appropriate location in a .Find() chain and it will be the fallback value if no value is found. It can be constructed with either lookup.NewConstantor or lookup.Default()

func Array

func Array(c ...interface{}) *Constantor

func Constant

func Constant(c interface{}) *Constantor

func False

func False(path string) *Constantor

func NewConstantor

func NewConstantor(path string, c interface{}) *Constantor

NewConstantor constructs a non-navigable constant.

func True

func True(path string) *Constantor

func (*Constantor) Find

func (r *Constantor) Find(path string, opts ...Runner) Pathor

Find returns a new Constinator with the same object but with an updated path if required.

func (*Constantor) Raw

func (r *Constantor) Raw() interface{}

Raw returns the contained object / reference.

func (*Constantor) Run

func (c *Constantor) Run(scope *Scope) Pathor

func (*Constantor) Type

func (r *Constantor) Type() reflect.Type

Type extracts the reflect.Type from the stored object

func (*Constantor) Value

func (r *Constantor) Value() reflect.Value

Value returns the reflect.Value

type CustomPath

type CustomPath interface {
	Path(previousPath string, findPath string) string
}

type Finder

type Finder interface {
	// Find preforms a path navigation. 'Path' is either a map key, array/slice index, or struct function/field.
	// This function is fixed and will probably not change in the future
	// So usage is supposed to be changed, anything which implements this function should return null-safe values. (ie non
	// nul.)
	// Usage: `lookup.Reflector(MyObjcet).Find("Quotes").Find("12").Find("Qty").Raw()
	Find(path string, opts ...Runner) Pathor
}

type HasPath

type HasPath interface {
	Path() string
}

HasPath is an interface used to determine if a Pathor has a Path() function

type Interface

type Interface interface {
	// Find the next component.. Must return an Interface OR another type of Pathor.
	Get(path string) (interface{}, error)
	// The raw type
	Raw() interface{}
}

Interface an interface you can implement to avoid using Reflector or to put your own selection logic such as if you were to run this over another data structure.

type Interfaceor

type Interfaceor struct {
	// contains filtered or unexported fields
}

Interfaceor the warping element for the Interface component to make it adhere to the Pathor interface

func (*Interfaceor) Find

func (i *Interfaceor) Find(path string, opts ...Runner) Pathor

func (*Interfaceor) Path

func (i *Interfaceor) Path() string

func (*Interfaceor) Raw

func (i *Interfaceor) Raw() interface{}

func (*Interfaceor) Type

func (i *Interfaceor) Type() reflect.Type

func (*Interfaceor) Value

func (i *Interfaceor) Value() reflect.Value

type Invalidor

type Invalidor struct {
	// contains filtered or unexported fields
}

Invalidor indicates an invalid state this can be because of an error, or an invalid path. It contains an error and adheres to errors and errors.Unwrap using fmt.Errors(".. %w..") It is designed to be continued to be used without returning a null value when you reach an error and also provide the path and error combo for debugging. It is fully adherent to a Pathor object

func NewInvalidor

func NewInvalidor(path string, err error) *Invalidor

NewInvalidor creates an invalidator, there shouldn't be any real reason to do this but you have an option to. See documentation for Invalidor for details

func (*Invalidor) Error

func (i *Invalidor) Error() string

Error implements the error interface

func (*Invalidor) Evaluate

func (i *Invalidor) Evaluate(scope *Scope, position Pathor) (Pathor, error)

Evaluate implements EvaluateNoArgs

func (*Invalidor) Find

func (i *Invalidor) Find(path string, opts ...Runner) Pathor

Find returns a new Invalidator with the same object but with an updated path if required. -- The path changing component might be removed - or become toggleable in an option.

func (*Invalidor) Path

func (i *Invalidor) Path() string

func (*Invalidor) Raw

func (i *Invalidor) Raw() interface{}

Raw returns NULL

func (*Invalidor) Type

func (i *Invalidor) Type() reflect.Type

Type returns NULL

func (*Invalidor) Unwrap

func (i *Invalidor) Unwrap() error

Unwrap implements the Unwrap error interface

func (*Invalidor) Value

func (i *Invalidor) Value() reflect.Value

Raw returns a zero/invalid reflect.Value

type Pathor

type Pathor interface {
	// Finder preforms a path navigation. 'Path' is either a map key, array/slice index, or struct function/field.
	// This function is fixed and will probably not change in the future
	// So usage is supposed to be changed, anything which implements this function should return null-safe values. (ie non
	// nul.)
	// Usage: `lookup.Reflector(MyObjcet).Find("Quotes").Find("12").Find("Qty").Raw()
	Finder
	// Value returns the reflect.Value or an invalid reflect.Value. This could be restricted to lookup.Reflector and others where appropriate
	Value() reflect.Value
	// Raw returns the raw contents / result of the lookup. This won't change
	Raw() interface{}
	// Type returns the reflect.Type or nil. This could be restricted to lookup.Reflector and others where appropriate
	Type() reflect.Type
}

Pathor interface

func NewInterfaceor

func NewInterfaceor(i Interface) Pathor

NewInterfaceor see Interface and Interfaceor for details.

func Reflect

func Reflect(i interface{}) Pathor

Reflect creates a Pathor that uses reflect to navigate the object. This so far is the only way to navigate arbitrary go objects, so use this.

type Reflector

type Reflector struct {
	// contains filtered or unexported fields
}

Reflector is a Pathor which uses reflection for navigation of the the objects, it supports a wide range of elements

func (*Reflector) Find

func (r *Reflector) Find(path string, opts ...Runner) Pathor

Find finds the best match for the "Path" argument in the contained object and then returns a Pathor for that location Match nothing was found it will return an Invalidor, or if a Constant has bee provided as an argument (such as through `Default()` it will default to that in most cases. Find is designed to return null safe results.

func (*Reflector) Path

func (r *Reflector) Path() string

func (*Reflector) Raw

func (r *Reflector) Raw() interface{}

Raw returns the contained object / reference.

func (*Reflector) Type

func (r *Reflector) Type() reflect.Type

Type extracts the reflect.Type from the stored object

func (*Reflector) Value

func (r *Reflector) Value() reflect.Value

Value returns the reflect.Value

type Relator

type Relator struct {
	// contains filtered or unexported fields
}

Relator allows you to do an Evaluate from a relative location

func Find

func Find(path string, opts ...Runner) *Relator

func NewRelator

func NewRelator() *Relator

func Parent

func Parent(path string, opts ...Runner) *Relator

func Result

func Result(paths ...string) *Relator

func This

func This(paths ...string) *Relator

func (*Relator) Copy

func (r *Relator) Copy() *Relator

Copy produces a copy of the Relator

func (*Relator) Find

func (r *Relator) Find(path string, opts ...Runner) *Relator

Find stores a find request to be used in the relative location. Please note this doesn't alloc a new Relator use Copy for that.

func (*Relator) Run

func (r *Relator) Run(scope *Scope) Pathor

type Runner

type Runner interface {
	Run(scope *Scope) Pathor
}

type Scope

type Scope struct {
	Current Pathor
	Parent  *Scope

	Position Pathor
	// contains filtered or unexported fields
}

func NewScope

func NewScope(parent Pathor, position Pathor) *Scope

func (*Scope) Copy

func (s *Scope) Copy() *Scope

func (*Scope) Nest

func (s *Scope) Nest(new Pathor) *Scope

func (*Scope) Next

func (s *Scope) Next(position Pathor) *Scope

func (*Scope) Path

func (s *Scope) Path() string

func (*Scope) Value

func (s *Scope) Value() reflect.Value

type Valuor

type Valuor struct {
	Pathor
}

func ValueOf

func ValueOf(pathor Pathor) *Valuor

func (*Valuor) Run

func (v *Valuor) Run(scope *Scope) Pathor

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL