github.com/instill-ai/component@v0.16.0-beta/pkg/jsonref/ref.go (about)

     1  // The following code is based on lestrrat's work, available at https://github.com/lestrrat-go/jsref.
     2  
     3  package jsonref
     4  
     5  import (
     6  	"encoding/json"
     7  	"net/url"
     8  	"reflect"
     9  
    10  	"github.com/lestrrat-go/jspointer"
    11  	"github.com/lestrrat-go/pdebug"
    12  	"github.com/lestrrat-go/structinfo"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  const ref = "$ref"
    17  
    18  var refrv = reflect.ValueOf(ref)
    19  
    20  var DefaultMaxRecursions = 10
    21  
    22  // New creates a new Resolver
    23  func New() *Resolver {
    24  	return &Resolver{MaxRecursions: DefaultMaxRecursions}
    25  }
    26  
    27  // AddProvider adds a new Provider to be searched for in case
    28  // a JSON pointer with more than just the URI fragment is given.
    29  func (r *Resolver) AddProvider(p Provider) error {
    30  	r.providers = append(r.providers, p)
    31  	return nil
    32  }
    33  
    34  type resolveCtx struct {
    35  	rlevel    int         // recurse level
    36  	maxrlevel int         // max recurse level
    37  	object    interface{} // the main object that was passed to `Resolve()`
    38  	recursive bool        // should traverseExpandRefRecursive or not
    39  	seen      []string    // loop detection
    40  }
    41  
    42  // Resolve takes a target `v`, and a JSON pointer `spec`.
    43  // spec is expected to be in the form of
    44  //
    45  //	[scheme://[userinfo@]host/path[?query]]#fragment
    46  //	[scheme:opaque[?query]]#fragment
    47  //
    48  // where everything except for `#fragment` is optional.
    49  // If the fragment is empty, an error is returned.
    50  //
    51  // If `spec` is the empty string, `v` is returned
    52  // This method handles recursive JSON references.
    53  //
    54  // If `WithRecursiveResolution` option is given and its value is true,
    55  // an attempt to resolve all references within the resulting object
    56  // is made by traversing the structure recursively. Default is false
    57  func (r *Resolver) Resolve(v interface{}, ptr string, options ...Option) (ret interface{}, err error) {
    58  	if pdebug.Enabled {
    59  		g := pdebug.Marker("Resolver.Resolve(%s)", ptr).BindError(&err)
    60  		defer g.End()
    61  	}
    62  	var recursiveResolution bool
    63  	for _, opt := range options {
    64  		switch opt.Ident() {
    65  		case identRecursiveResolution{}:
    66  			recursiveResolution = opt.Value().(bool)
    67  		}
    68  	}
    69  
    70  	ctx := resolveCtx{
    71  		rlevel:    0,
    72  		maxrlevel: r.MaxRecursions,
    73  		object:    v,
    74  		recursive: recursiveResolution,
    75  		seen:      []string{},
    76  	}
    77  
    78  	// First, expand the target as much as we can
    79  	v, err = expandRefRecursive(&ctx, r, v)
    80  	if err != nil {
    81  		return nil, errors.Wrap(err, "recursive search failed")
    82  	}
    83  
    84  	result, err := evalptr(&ctx, r, v, ptr)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	if recursiveResolution {
    90  		rv, err := traverseExpandRefRecursive(&ctx, r, reflect.ValueOf(result))
    91  		if err != nil {
    92  			return nil, errors.Wrap(err, `failed to resolve result`)
    93  		}
    94  		result = rv.Interface()
    95  	}
    96  
    97  	return result, nil
    98  }
    99  
   100  func setPtrOrInterface(container, value reflect.Value) bool {
   101  	switch container.Kind() {
   102  	case reflect.Ptr:
   103  		if !value.CanAddr() {
   104  			return false
   105  		}
   106  		container.Set(value.Addr())
   107  	case reflect.Interface:
   108  		container.Set(value)
   109  	default:
   110  		return false
   111  	}
   112  	return true
   113  }
   114  
   115  func traverseExpandRefRecursive(ctx *resolveCtx, r *Resolver, rv reflect.Value) (reflect.Value, error) {
   116  	if pdebug.Enabled {
   117  		g := pdebug.Marker("traverseExpandRefRecursive")
   118  		defer g.End()
   119  	}
   120  
   121  	switch rv.Kind() {
   122  	case reflect.Ptr, reflect.Interface:
   123  		rv = rv.Elem()
   124  	}
   125  
   126  	switch rv.Kind() {
   127  	case reflect.Array, reflect.Slice:
   128  		for i := 0; i < rv.Len(); i++ {
   129  			elem := rv.Index(i)
   130  			var elemcontainer reflect.Value
   131  			switch elem.Kind() {
   132  			case reflect.Ptr, reflect.Interface:
   133  				elemcontainer = elem
   134  				elem = elem.Elem()
   135  			}
   136  
   137  			// Need to check for elem being Valid, otherwise the
   138  			// subsequent call to Interface() will fail
   139  			if !elem.IsValid() {
   140  				continue
   141  			}
   142  
   143  			if elemcontainer.IsValid() {
   144  				if !elemcontainer.CanSet() {
   145  					continue
   146  				}
   147  			}
   148  			newv, err := expandRefRecursive(ctx, r, elem.Interface())
   149  			if err != nil {
   150  				return zeroval, errors.Wrap(err, `failed to expand array/slice element`)
   151  			}
   152  			newrv, err := traverseExpandRefRecursive(ctx, r, reflect.ValueOf(newv))
   153  			if err != nil {
   154  				return zeroval, errors.Wrap(err, `failed to recurse into array/slice element`)
   155  			}
   156  
   157  			if elemcontainer.IsValid() {
   158  				setPtrOrInterface(elemcontainer, newrv)
   159  			} else {
   160  				elem.Set(newrv)
   161  			}
   162  		}
   163  	case reflect.Map:
   164  		// No refs found in the map keys, but there could be more
   165  		// in the values
   166  		if _, err := findRef(rv.Interface()); err != nil {
   167  			for _, key := range rv.MapKeys() {
   168  				value, err := traverseExpandRefRecursive(ctx, r, rv.MapIndex(key))
   169  				if err != nil {
   170  					return zeroval, errors.Wrap(err, `failed to traverse map value`)
   171  				}
   172  				rv.SetMapIndex(key, value)
   173  			}
   174  			return rv, nil
   175  		}
   176  		newv, err := expandRefRecursive(ctx, r, rv.Interface())
   177  		if err != nil {
   178  			return zeroval, errors.Wrap(err, `failed to expand map element`)
   179  		}
   180  		return traverseExpandRefRecursive(ctx, r, reflect.ValueOf(newv))
   181  	case reflect.Struct:
   182  		// No refs found in the map keys, but there could be more
   183  		// in the values
   184  		if _, err := findRef(rv.Interface()); err != nil {
   185  			for i := 0; i < rv.NumField(); i++ {
   186  				field := rv.Field(i)
   187  				value, err := traverseExpandRefRecursive(ctx, r, field)
   188  				if err != nil {
   189  					return zeroval, errors.Wrap(err, `failed to traverse struct field value`)
   190  				}
   191  				field.Set(value)
   192  			}
   193  			return rv, nil
   194  		}
   195  		newv, err := expandRefRecursive(ctx, r, rv.Interface())
   196  		if err != nil {
   197  			return zeroval, errors.Wrap(err, `failed to expand struct element`)
   198  		}
   199  		return traverseExpandRefRecursive(ctx, r, reflect.ValueOf(newv))
   200  	}
   201  	return rv, nil
   202  }
   203  
   204  // expands $ref with in v, until all $refs are expanded.
   205  // note: DOES NOT recurse down into structures
   206  func expandRefRecursive(ctx *resolveCtx, r *Resolver, v interface{}) (ret interface{}, err error) {
   207  	if pdebug.Enabled {
   208  		g := pdebug.Marker("expandRefRecursive")
   209  		defer g.End()
   210  	}
   211  	for {
   212  		ref, err := findRef(v)
   213  		if err != nil {
   214  			if pdebug.Enabled {
   215  				pdebug.Printf("No refs found. bailing out of loop")
   216  			}
   217  			break
   218  		}
   219  
   220  		if pdebug.Enabled {
   221  			pdebug.Printf("Found ref '%s'", ref)
   222  		}
   223  
   224  		newv, err := expandRef(ctx, r, v, ref)
   225  		if err != nil {
   226  			if pdebug.Enabled {
   227  				pdebug.Printf("Failed to expand ref '%s': %s", ref, err)
   228  			}
   229  			return nil, errors.Wrap(err, "failed to expand ref")
   230  		}
   231  		b, err := json.Marshal(newv)
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  		var i interface{}
   236  		err = json.Unmarshal(b, &i)
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  
   241  		for key, value := range v.(map[string]interface{}) {
   242  			if key != refrv.String() {
   243  				i.(map[string]interface{})[key] = value
   244  			}
   245  		}
   246  		v = i
   247  	}
   248  
   249  	return v, nil
   250  }
   251  
   252  func expandRef(ctx *resolveCtx, r *Resolver, v interface{}, ref string) (ret interface{}, err error) {
   253  	if pdebug.Enabled {
   254  		g := pdebug.Marker("expandRef %s", ref)
   255  		defer g.End()
   256  	}
   257  	ctx.rlevel++
   258  	if ctx.rlevel > ctx.maxrlevel {
   259  		return nil, ErrMaxRecursion
   260  	}
   261  
   262  	defer func() { ctx.rlevel-- }()
   263  
   264  	for _, s := range ctx.seen {
   265  		if s == ref {
   266  			if pdebug.Enabled {
   267  				pdebug.Printf("reference loop detected %s", ref)
   268  			}
   269  			return nil, ErrReferenceLoop
   270  		}
   271  	}
   272  
   273  	u, err := url.Parse(ref)
   274  	if err != nil {
   275  		return nil, errors.Wrap(err, "failed to parse ref as URL")
   276  	}
   277  
   278  	ptr := "#" + u.Fragment
   279  	if u.Host == "" && u.Path == "" {
   280  		if pdebug.Enabled {
   281  			pdebug.Printf("ptr doesn't contain any host/path part, apply json pointer directly to object")
   282  			// pdebug.Printf("  %v", ctx.object)
   283  		}
   284  		return evalptr(ctx, r, ctx.object, ptr)
   285  	}
   286  
   287  	u.Fragment = ""
   288  	for _, p := range r.providers {
   289  		pv, err := p.Get(u)
   290  		if err == nil {
   291  			if pdebug.Enabled {
   292  				pdebug.Printf("Found object matching %s", u)
   293  			}
   294  			newseen := append([]string{}, ctx.seen...)
   295  			newseen = append(newseen, ref)
   296  			ctx2 := &resolveCtx{
   297  				rlevel:    ctx.rlevel,
   298  				maxrlevel: ctx.maxrlevel,
   299  				object:    pv,
   300  				recursive: ctx.recursive,
   301  				seen:      newseen,
   302  			}
   303  			pv, err := evalptr(ctx2, r, pv, ptr)
   304  			if err != nil {
   305  				return nil, errors.Wrap(err, "failed on ptr")
   306  			}
   307  			if !ctx.recursive {
   308  				return pv, nil
   309  			}
   310  
   311  			pv, err = expandRefRecursive(ctx2, r, pv)
   312  			if err != nil {
   313  				return nil, errors.Wrap(err, "failed to expand external reference")
   314  			}
   315  			rv, err := traverseExpandRefRecursive(ctx2, r, reflect.ValueOf(pv))
   316  			if err != nil {
   317  				return nil, errors.Wrap(err, "failed to traverse external reference")
   318  			}
   319  			return rv.Interface(), nil
   320  		}
   321  	}
   322  
   323  	return nil, errors.New("element pointed by $ref '" + ref + "' not found")
   324  }
   325  
   326  func findRef(v interface{}) (ref string, err error) {
   327  	if pdebug.Enabled {
   328  		g := pdebug.Marker("findRef").BindError(&err)
   329  		defer g.End()
   330  	}
   331  
   332  	rv := reflect.ValueOf(v)
   333  	switch rv.Kind() {
   334  	case reflect.Interface, reflect.Ptr:
   335  		rv = rv.Elem()
   336  	}
   337  
   338  	if pdebug.Enabled {
   339  		pdebug.Printf("object is a '%s'", rv.Kind())
   340  	}
   341  
   342  	// Find if we have a "$ref" element
   343  	var refv reflect.Value
   344  	switch rv.Kind() {
   345  	case reflect.Map:
   346  		refv = rv.MapIndex(refrv)
   347  	case reflect.Struct:
   348  		if fn := structinfo.StructFieldFromJSONName(rv, ref); fn != "" {
   349  			refv = rv.FieldByName(fn)
   350  		}
   351  	default:
   352  		return "", errors.New("element is not a map-like container")
   353  	}
   354  
   355  	if !refv.IsValid() {
   356  		return "", errors.New("$ref element not found")
   357  	}
   358  
   359  	switch refv.Kind() {
   360  	case reflect.Interface, reflect.Ptr:
   361  		refv = refv.Elem()
   362  	}
   363  
   364  	switch refv.Kind() {
   365  	case reflect.String:
   366  		// Empty string isn't a valid pointer
   367  		if refv.Len() <= 0 {
   368  			return "", errors.New("$ref element not found (empty)")
   369  		}
   370  		if refv.String() == "#" {
   371  			return "", errors.New("$ref to '#' skipped")
   372  		}
   373  		if pdebug.Enabled {
   374  			pdebug.Printf("Found ref '%s'", refv)
   375  		}
   376  		return refv.String(), nil
   377  	case reflect.Invalid:
   378  		return "", errors.New("$ref element not found")
   379  	default:
   380  		if pdebug.Enabled {
   381  			pdebug.Printf("'$ref' was found, but its kind is %s", refv.Kind())
   382  		}
   383  	}
   384  
   385  	return "", errors.New("$ref element must be a string")
   386  }
   387  
   388  func evalptr(ctx *resolveCtx, r *Resolver, v interface{}, ptrspec string) (ret interface{}, err error) {
   389  	if pdebug.Enabled {
   390  		g := pdebug.Marker("evalptr(%s)", ptrspec).BindError(&err)
   391  		defer g.End()
   392  	}
   393  
   394  	// If the reference is empty, return v
   395  	if ptrspec == "" || ptrspec == "#" {
   396  		if pdebug.Enabled {
   397  			pdebug.Printf("Empty pointer, return v itself")
   398  		}
   399  		return v, nil
   400  	}
   401  
   402  	// Parse the spec.
   403  	u, err := url.Parse(ptrspec)
   404  	if err != nil {
   405  		return nil, errors.Wrap(err, "failed to parse reference spec")
   406  	}
   407  
   408  	ptr := u.Fragment
   409  
   410  	// We are evaluating the pointer part. That means if the
   411  	// Fragment portion is not set, there's no point in evaluating
   412  	if ptr == "" {
   413  		return nil, errors.Wrap(err, "empty json pointer")
   414  	}
   415  
   416  	p, err := jspointer.New(ptr)
   417  	if err != nil {
   418  		return nil, errors.Wrap(err, "failed create a new JSON pointer")
   419  	}
   420  	x, err := p.Get(v)
   421  	if err != nil {
   422  		return nil, errors.Wrap(err, "failed to fetch value")
   423  	}
   424  
   425  	if pdebug.Enabled {
   426  		pdebug.Printf("Evaulated JSON pointer, now checking if we can expand further")
   427  	}
   428  	// If this result contains more refs, expand that
   429  	return expandRefRecursive(ctx, r, x)
   430  }