blob: 649ca06e666ed8a2505c516ecb38fc39b1b5063c [file] [log] [blame]
Serge Bazanskicc25bdf2018-10-25 14:02:58 +02001// 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
15package loads
16
17import (
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
29func 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
38type DocLoader func(string) (json.RawMessage, error)
39
40// DocMatcher represents a predicate to check if a loader matches
41type DocMatcher func(string) bool
42
43var (
44 loaders *loader
45 defaultLoader *loader
46)
47
48func 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
56func 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
66type loader struct {
67 Fn DocLoader
68 Match DocMatcher
69 Next *loader
70}
71
72// JSONSpec loads a spec from a json document
73func 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
83type 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
94func 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
110func 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
150func 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
195func (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
226func (d *Document) BasePath() string {
227 return d.spec.BasePath
228}
229
230// Version returns the version of this spec
231func (d *Document) Version() string {
232 return d.spec.Swagger
233}
234
235// Schema returns the swagger 2.0 schema
236func (d *Document) Schema() *spec.Schema {
237 return d.schema
238}
239
240// Spec returns the swagger spec object model
241func (d *Document) Spec() *spec.Swagger {
242 return d.spec
243}
244
245// Host returns the host for the API
246func (d *Document) Host() string {
247 return d.spec.Host
248}
249
250// Raw returns the raw swagger spec as json bytes
251func (d *Document) Raw() json.RawMessage {
252 return d.raw
253}
254
255func (d *Document) OrigSpec() *spec.Swagger {
256 return d.origSpec
257}
258
259// ResetDefinitions gives a shallow copy with the models reset
260func (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
271func (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
277func (d *Document) SpecFilePath() string {
278 return d.specFilePath
279}