github.com/jasonfriedland/openapi2proto@v0.2.1/openapi/external.go (about)

     1  package openapi
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  	"path"
    10  	"path/filepath"
    11  	"reflect"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/dolmen-go/jsonptr"
    16  	"github.com/pkg/errors"
    17  	yaml "gopkg.in/yaml.v2"
    18  )
    19  
    20  var interfaceType = reflect.TypeOf((*interface{})(nil)).Elem()
    21  var stringType = reflect.TypeOf("")
    22  var stringInterfaceMapType = reflect.MapOf(stringType, interfaceType)
    23  
    24  // we may receive maps for arbitrary key types, as various *.Marshal
    25  // methods may treat things like
    26  //
    27  // {
    28  //   200: { ... }
    29  // }
    30  //
    31  // as a map with an integer key. However, for all of our purposes,
    32  // we need a string key.
    33  //
    34  // This function provides the conversion routine for such cases
    35  func stringify(v interface{}) string {
    36  	switch v := v.(type) {
    37  	case string:
    38  		return v
    39  	case int:
    40  		return strconv.FormatInt(int64(v), 10)
    41  	case int64:
    42  		return strconv.FormatInt(int64(v), 10)
    43  	case int32:
    44  		return strconv.FormatInt(int64(v), 10)
    45  	case int16:
    46  		return strconv.FormatInt(int64(v), 10)
    47  	case int8:
    48  		return strconv.FormatInt(int64(v), 10)
    49  	case uint:
    50  		return strconv.FormatUint(uint64(v), 10)
    51  	case uint64:
    52  		return strconv.FormatUint(uint64(v), 10)
    53  	case uint32:
    54  		return strconv.FormatUint(uint64(v), 10)
    55  	case uint16:
    56  		return strconv.FormatUint(uint64(v), 10)
    57  	case uint8:
    58  		return strconv.FormatUint(uint64(v), 10)
    59  	case float32:
    60  		return strconv.FormatFloat(float64(v), 'f', -1, 64)
    61  	case float64:
    62  		return strconv.FormatFloat(float64(v), 'f', -1, 64)
    63  	case bool:
    64  		return strconv.FormatBool(v)
    65  	}
    66  
    67  	return `(invalid)`
    68  }
    69  
    70  // YAML serializers are really, really, really annoying in that
    71  // it decodes maps into map[interface{}]interface{} instead
    72  // of map[string]interfaace{}
    73  //
    74  // Keys behind the interface{} could be strings, ints, etc, so
    75  // we convert them into map types that we can actually handle,
    76  // namely map[string]interface{}
    77  func restoreSanity(rv reflect.Value) reflect.Value {
    78  	rv, _ = restoreSanityInternal(rv)
    79  	return rv
    80  }
    81  
    82  // this function is separated out from the main restoreSanity
    83  // function, because in certain cases, we should be checking
    84  // checking if the value has been updated.
    85  //
    86  // e.g. when we are dealing with elements in an array, we
    87  // do not want to swap values unless the value has been
    88  // changed, as `reflect` operations are pretty costly anyways.
    89  //
    90  // the second return value indicates if the operation changed
    91  // the rv value, and if you should use it, which is only
    92  // applicable while traversing the nodes.
    93  func restoreSanityInternal(rv reflect.Value) (reflect.Value, bool) {
    94  	if rv.Kind() == reflect.Interface {
    95  		return restoreSanityInternal(rv.Elem())
    96  	}
    97  
    98  	switch rv.Kind() {
    99  	case reflect.Map:
   100  		var count int // keep track of how many "restorations" have been applied
   101  
   102  		var dst = rv
   103  
   104  		// the keys MUST Be strings.
   105  		isStringKey := rv.Type().Key().Kind() == reflect.String
   106  		if !isStringKey {
   107  			dst = reflect.MakeMap(stringInterfaceMapType)
   108  			count++ // if we got here, it's "restored" regardless of the keys being transformed
   109  		}
   110  
   111  		for _, key := range rv.MapKeys() {
   112  			newValue, restored := restoreSanityInternal(rv.MapIndex(key))
   113  			if restored {
   114  				count++
   115  			}
   116  
   117  			// Keys need special treatment becase we may be re-using the
   118  			// original map. in that case we can simply re-use the key
   119  			var newKey reflect.Value
   120  			if isStringKey {
   121  				newKey = key
   122  			} else {
   123  				newKey = reflect.ValueOf(stringify(key.Elem().Interface()))
   124  			}
   125  
   126  			dst.SetMapIndex(newKey, newValue)
   127  		}
   128  		return dst, count > 0
   129  	case reflect.Slice, reflect.Array:
   130  		var count int
   131  		for i := 0; i < rv.Len(); i++ {
   132  			newValue, restored := restoreSanityInternal(rv.Index(i))
   133  			if restored {
   134  				rv.Index(i).Set(newValue)
   135  				count++
   136  			}
   137  		}
   138  		return rv, count > 0
   139  	default:
   140  		return rv, false
   141  	}
   142  }
   143  
   144  var zeroval reflect.Value
   145  var refKey = reflect.ValueOf(`$ref`)
   146  
   147  func parseRef(s string) (string, string, error) {
   148  	u, err := url.Parse(s)
   149  	if err != nil {
   150  		return "", "", errors.Wrapf(err, `failed to parse URL %s`, s)
   151  	}
   152  
   153  	frag := u.Fragment
   154  	u.Fragment = ""
   155  	return u.String(), frag, nil
   156  }
   157  
   158  func isExternal(s string) bool {
   159  	if strings.HasPrefix(s, `google/protobuf/`) {
   160  		return false
   161  	}
   162  	return strings.IndexByte(s, '#') != 0
   163  }
   164  
   165  func newResolver() *resolver {
   166  	return &resolver{}
   167  }
   168  
   169  func (r *resolver) Resolve(v interface{}, options ...Option) (interface{}, error) {
   170  	var dir string
   171  	for _, o := range options {
   172  		switch o.Name() {
   173  		case optkeyDir:
   174  			dir = o.Value().(string)
   175  		}
   176  	}
   177  
   178  	c := resolveCtx{
   179  		dir:                dir,
   180  		externalReferences: map[string]interface{}{},
   181  		cache:              map[string]interface{}{},
   182  	}
   183  
   184  	rv, err := c.resolve(restoreSanity(reflect.ValueOf(v)))
   185  	if err != nil {
   186  		return nil, errors.Wrap(err, `failed to resolve object`)
   187  	}
   188  
   189  	return restoreSanity(rv).Interface(), nil
   190  }
   191  
   192  // note, we must use a composite type with only map[string]interface{},
   193  // []interface{} and interface{} as its building blocks
   194  func (c *resolveCtx) resolve(rv reflect.Value) (reflect.Value, error) {
   195  	if rv.Kind() == reflect.Interface {
   196  		return c.resolve(rv.Elem())
   197  	}
   198  
   199  	switch rv.Kind() {
   200  	case reflect.Slice, reflect.Array:
   201  		for i := 0; i < rv.Len(); i++ {
   202  			newV, err := c.resolve(rv.Index(i))
   203  			if err != nil {
   204  				return zeroval, errors.Wrapf(err, `failed to resolve element %d`, i)
   205  			}
   206  			rv.Index(i).Set(newV)
   207  		}
   208  	case reflect.Map:
   209  		// if it's a map, see if we have a "$ref" key
   210  		if refValue := rv.MapIndex(refKey); refValue != zeroval {
   211  			if refValue.Kind() != reflect.Interface {
   212  				return zeroval, errors.Errorf("'$ref' key contains non-interface{} element (%s)", refValue.Type())
   213  			}
   214  			refValue = refValue.Elem()
   215  
   216  			if refValue.Kind() != reflect.String {
   217  				return zeroval, errors.Errorf("'$ref' key contains non-string element (%s)", refValue.Type())
   218  			}
   219  
   220  			ref := refValue.String()
   221  			if isExternal(ref) {
   222  				refURL, refFragment, err := parseRef(ref)
   223  				if err != nil {
   224  					return zeroval, errors.Wrap(err, `failed to parse reference`)
   225  				}
   226  
   227  				// if we have already loaded this, don't make another
   228  				// roundtrip to the remote server
   229  				resolved, ok := c.cache[refURL]
   230  				if !ok {
   231  					var err error
   232  					resolved, err = c.loadExternal(refURL)
   233  					if err != nil {
   234  						return zeroval, errors.Wrapf(err, `failed to resolve external reference %s`, ref)
   235  					}
   236  					// remember that we have resolved this document
   237  					c.cache[refURL] = resolved
   238  				}
   239  
   240  				docFragment, err := jsonptr.Get(restoreSanity(reflect.ValueOf(resolved)).Interface(), refFragment)
   241  				if err != nil {
   242  					return zeroval, errors.Wrapf(err, `failed to resolve document fragment %s`, refFragment)
   243  				}
   244  
   245  				// recurse into docFragment
   246  				return c.resolve(reflect.ValueOf(docFragment))
   247  			}
   248  			return rv, nil
   249  		}
   250  
   251  		// otherwise, traverse the map
   252  		for _, key := range rv.MapKeys() {
   253  			newV, err := c.resolve(rv.MapIndex(key))
   254  			if err != nil {
   255  				return zeroval, errors.Wrapf(err, `failed to resolve map element for %s`, key)
   256  			}
   257  			rv.SetMapIndex(key, newV)
   258  		}
   259  		return rv, nil
   260  	}
   261  	return rv, nil
   262  }
   263  
   264  func (c *resolveCtx) normalizePath(s string) string {
   265  	if c.dir == "" {
   266  		return s
   267  	}
   268  	return filepath.Join(c.dir, s)
   269  }
   270  
   271  func (c *resolveCtx) loadExternal(s string) (interface{}, error) {
   272  	u, err := url.Parse(s)
   273  	if err != nil {
   274  		return nil, errors.Wrapf(err, `failed to parse reference %s`, s)
   275  	}
   276  
   277  	var src io.Reader
   278  	switch u.Scheme {
   279  	case "":
   280  		f, err := os.Open(c.normalizePath(u.Path))
   281  		if err != nil {
   282  			return nil, errors.Wrapf(err, `failed to read local file %s`, u.Path)
   283  		}
   284  		defer f.Close()
   285  		src = f
   286  	case "http", "https":
   287  		res, err := http.Get(u.String())
   288  		if err != nil {
   289  			return nil, errors.Wrapf(err, `failed to fetch remote file %s`, u.String())
   290  		}
   291  		defer res.Body.Close()
   292  
   293  		if res.StatusCode != http.StatusOK {
   294  			return nil, errors.Wrapf(err, `failed to fetch remote file %s`, u.String())
   295  		}
   296  
   297  		src = res.Body
   298  	default:
   299  		return nil, errors.Errorf(`cannot handle reference %s`, s)
   300  	}
   301  
   302  	// now guess from the file nam if this is a YAML or JSON
   303  	var v interface{}
   304  	switch strings.ToLower(path.Ext(u.Path)) {
   305  	case ".yaml", ".yml":
   306  		if err := yaml.NewDecoder(src).Decode(&v); err != nil {
   307  			return nil, errors.Wrapf(err, `failed to decode reference %s`, s)
   308  		}
   309  	default:
   310  		if err := json.NewDecoder(src).Decode(&v); err != nil {
   311  			return nil, errors.Wrapf(err, `failed to decode reference %s`, s)
   312  		}
   313  	}
   314  
   315  	return v, nil
   316  }