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 }