Serge Bazanski | cc25bdf | 2018-10-25 14:02:58 +0200 | [diff] [blame] | 1 | // Copyright 2015 go-swagger maintainers |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | package loads |
| 16 | |
| 17 | import ( |
| 18 | "bytes" |
| 19 | "encoding/json" |
| 20 | "fmt" |
| 21 | "net/url" |
| 22 | |
| 23 | "github.com/go-openapi/analysis" |
| 24 | "github.com/go-openapi/spec" |
| 25 | "github.com/go-openapi/swag" |
| 26 | ) |
| 27 | |
| 28 | // JSONDoc loads a json document from either a file or a remote url |
| 29 | func JSONDoc(path string) (json.RawMessage, error) { |
| 30 | data, err := swag.LoadFromFileOrHTTP(path) |
| 31 | if err != nil { |
| 32 | return nil, err |
| 33 | } |
| 34 | return json.RawMessage(data), nil |
| 35 | } |
| 36 | |
| 37 | // DocLoader represents a doc loader type |
| 38 | type DocLoader func(string) (json.RawMessage, error) |
| 39 | |
| 40 | // DocMatcher represents a predicate to check if a loader matches |
| 41 | type DocMatcher func(string) bool |
| 42 | |
| 43 | var ( |
| 44 | loaders *loader |
| 45 | defaultLoader *loader |
| 46 | ) |
| 47 | |
| 48 | func init() { |
| 49 | defaultLoader = &loader{Match: func(_ string) bool { return true }, Fn: JSONDoc} |
| 50 | loaders = defaultLoader |
| 51 | spec.PathLoader = loaders.Fn |
| 52 | AddLoader(swag.YAMLMatcher, swag.YAMLDoc) |
| 53 | } |
| 54 | |
| 55 | // AddLoader for a document |
| 56 | func AddLoader(predicate DocMatcher, load DocLoader) { |
| 57 | prev := loaders |
| 58 | loaders = &loader{ |
| 59 | Match: predicate, |
| 60 | Fn: load, |
| 61 | Next: prev, |
| 62 | } |
| 63 | spec.PathLoader = loaders.Fn |
| 64 | } |
| 65 | |
| 66 | type loader struct { |
| 67 | Fn DocLoader |
| 68 | Match DocMatcher |
| 69 | Next *loader |
| 70 | } |
| 71 | |
| 72 | // JSONSpec loads a spec from a json document |
| 73 | func JSONSpec(path string) (*Document, error) { |
| 74 | data, err := JSONDoc(path) |
| 75 | if err != nil { |
| 76 | return nil, err |
| 77 | } |
| 78 | // convert to json |
| 79 | return Analyzed(json.RawMessage(data), "") |
| 80 | } |
| 81 | |
| 82 | // Document represents a swagger spec document |
| 83 | type Document struct { |
| 84 | // specAnalyzer |
| 85 | Analyzer *analysis.Spec |
| 86 | spec *spec.Swagger |
| 87 | specFilePath string |
| 88 | origSpec *spec.Swagger |
| 89 | schema *spec.Schema |
| 90 | raw json.RawMessage |
| 91 | } |
| 92 | |
| 93 | // Embedded returns a Document based on embedded specs. No analysis is required |
| 94 | func Embedded(orig, flat json.RawMessage) (*Document, error) { |
| 95 | var origSpec, flatSpec spec.Swagger |
| 96 | if err := json.Unmarshal(orig, &origSpec); err != nil { |
| 97 | return nil, err |
| 98 | } |
| 99 | if err := json.Unmarshal(flat, &flatSpec); err != nil { |
| 100 | return nil, err |
| 101 | } |
| 102 | return &Document{ |
| 103 | raw: orig, |
| 104 | origSpec: &origSpec, |
| 105 | spec: &flatSpec, |
| 106 | }, nil |
| 107 | } |
| 108 | |
| 109 | // Spec loads a new spec document |
| 110 | func Spec(path string) (*Document, error) { |
| 111 | specURL, err := url.Parse(path) |
| 112 | if err != nil { |
| 113 | return nil, err |
| 114 | } |
| 115 | var lastErr error |
| 116 | for l := loaders.Next; l != nil; l = l.Next { |
| 117 | if loaders.Match(specURL.Path) { |
| 118 | b, err2 := loaders.Fn(path) |
| 119 | if err2 != nil { |
| 120 | lastErr = err2 |
| 121 | continue |
| 122 | } |
| 123 | doc, err := Analyzed(b, "") |
| 124 | if err != nil { |
| 125 | return nil, err |
| 126 | } |
| 127 | if doc != nil { |
| 128 | doc.specFilePath = path |
| 129 | } |
| 130 | return doc, nil |
| 131 | } |
| 132 | } |
| 133 | if lastErr != nil { |
| 134 | return nil, lastErr |
| 135 | } |
| 136 | b, err := defaultLoader.Fn(path) |
| 137 | if err != nil { |
| 138 | return nil, err |
| 139 | } |
| 140 | |
| 141 | document, err := Analyzed(b, "") |
| 142 | if document != nil { |
| 143 | document.specFilePath = path |
| 144 | } |
| 145 | |
| 146 | return document, err |
| 147 | } |
| 148 | |
| 149 | // Analyzed creates a new analyzed spec document |
| 150 | func Analyzed(data json.RawMessage, version string) (*Document, error) { |
| 151 | if version == "" { |
| 152 | version = "2.0" |
| 153 | } |
| 154 | if version != "2.0" { |
| 155 | return nil, fmt.Errorf("spec version %q is not supported", version) |
| 156 | } |
| 157 | |
| 158 | raw := data |
| 159 | trimmed := bytes.TrimSpace(data) |
| 160 | if len(trimmed) > 0 { |
| 161 | if trimmed[0] != '{' && trimmed[0] != '[' { |
| 162 | yml, err := swag.BytesToYAMLDoc(trimmed) |
| 163 | if err != nil { |
| 164 | return nil, fmt.Errorf("analyzed: %v", err) |
| 165 | } |
| 166 | d, err := swag.YAMLToJSON(yml) |
| 167 | if err != nil { |
| 168 | return nil, fmt.Errorf("analyzed: %v", err) |
| 169 | } |
| 170 | raw = d |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | swspec := new(spec.Swagger) |
| 175 | if err := json.Unmarshal(raw, swspec); err != nil { |
| 176 | return nil, err |
| 177 | } |
| 178 | |
| 179 | origsqspec := new(spec.Swagger) |
| 180 | if err := json.Unmarshal(raw, origsqspec); err != nil { |
| 181 | return nil, err |
| 182 | } |
| 183 | |
| 184 | d := &Document{ |
| 185 | Analyzer: analysis.New(swspec), |
| 186 | schema: spec.MustLoadSwagger20Schema(), |
| 187 | spec: swspec, |
| 188 | raw: raw, |
| 189 | origSpec: origsqspec, |
| 190 | } |
| 191 | return d, nil |
| 192 | } |
| 193 | |
| 194 | // Expanded expands the ref fields in the spec document and returns a new spec document |
| 195 | func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) { |
| 196 | swspec := new(spec.Swagger) |
| 197 | if err := json.Unmarshal(d.raw, swspec); err != nil { |
| 198 | return nil, err |
| 199 | } |
| 200 | |
| 201 | var expandOptions *spec.ExpandOptions |
| 202 | if len(options) > 0 { |
| 203 | expandOptions = options[0] |
| 204 | } else { |
| 205 | expandOptions = &spec.ExpandOptions{ |
| 206 | RelativeBase: d.specFilePath, |
| 207 | } |
| 208 | } |
| 209 | |
| 210 | if err := spec.ExpandSpec(swspec, expandOptions); err != nil { |
| 211 | return nil, err |
| 212 | } |
| 213 | |
| 214 | dd := &Document{ |
| 215 | Analyzer: analysis.New(swspec), |
| 216 | spec: swspec, |
| 217 | specFilePath: d.specFilePath, |
| 218 | schema: spec.MustLoadSwagger20Schema(), |
| 219 | raw: d.raw, |
| 220 | origSpec: d.origSpec, |
| 221 | } |
| 222 | return dd, nil |
| 223 | } |
| 224 | |
| 225 | // BasePath the base path for this spec |
| 226 | func (d *Document) BasePath() string { |
| 227 | return d.spec.BasePath |
| 228 | } |
| 229 | |
| 230 | // Version returns the version of this spec |
| 231 | func (d *Document) Version() string { |
| 232 | return d.spec.Swagger |
| 233 | } |
| 234 | |
| 235 | // Schema returns the swagger 2.0 schema |
| 236 | func (d *Document) Schema() *spec.Schema { |
| 237 | return d.schema |
| 238 | } |
| 239 | |
| 240 | // Spec returns the swagger spec object model |
| 241 | func (d *Document) Spec() *spec.Swagger { |
| 242 | return d.spec |
| 243 | } |
| 244 | |
| 245 | // Host returns the host for the API |
| 246 | func (d *Document) Host() string { |
| 247 | return d.spec.Host |
| 248 | } |
| 249 | |
| 250 | // Raw returns the raw swagger spec as json bytes |
| 251 | func (d *Document) Raw() json.RawMessage { |
| 252 | return d.raw |
| 253 | } |
| 254 | |
| 255 | func (d *Document) OrigSpec() *spec.Swagger { |
| 256 | return d.origSpec |
| 257 | } |
| 258 | |
| 259 | // ResetDefinitions gives a shallow copy with the models reset |
| 260 | func (d *Document) ResetDefinitions() *Document { |
| 261 | defs := make(map[string]spec.Schema, len(d.origSpec.Definitions)) |
| 262 | for k, v := range d.origSpec.Definitions { |
| 263 | defs[k] = v |
| 264 | } |
| 265 | |
| 266 | d.spec.Definitions = defs |
| 267 | return d |
| 268 | } |
| 269 | |
| 270 | // Pristine creates a new pristine document instance based on the input data |
| 271 | func (d *Document) Pristine() *Document { |
| 272 | dd, _ := Analyzed(d.Raw(), d.Version()) |
| 273 | return dd |
| 274 | } |
| 275 | |
| 276 | // SpecFilePath returns the file path of the spec if one is defined |
| 277 | func (d *Document) SpecFilePath() string { |
| 278 | return d.specFilePath |
| 279 | } |