You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

spec.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  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. package loads
  15. import (
  16. "bytes"
  17. "encoding/gob"
  18. "encoding/json"
  19. "fmt"
  20. "net/url"
  21. "github.com/go-openapi/analysis"
  22. "github.com/go-openapi/spec"
  23. "github.com/go-openapi/swag"
  24. )
  25. // JSONDoc loads a json document from either a file or a remote url
  26. func JSONDoc(path string) (json.RawMessage, error) {
  27. data, err := swag.LoadFromFileOrHTTP(path)
  28. if err != nil {
  29. return nil, err
  30. }
  31. return json.RawMessage(data), nil
  32. }
  33. // DocLoader represents a doc loader type
  34. type DocLoader func(string) (json.RawMessage, error)
  35. // DocMatcher represents a predicate to check if a loader matches
  36. type DocMatcher func(string) bool
  37. var (
  38. loaders *loader
  39. defaultLoader *loader
  40. )
  41. func init() {
  42. defaultLoader = &loader{Match: func(_ string) bool { return true }, Fn: JSONDoc}
  43. loaders = defaultLoader
  44. spec.PathLoader = loaders.Fn
  45. AddLoader(swag.YAMLMatcher, swag.YAMLDoc)
  46. gob.Register(map[string]interface{}{})
  47. gob.Register([]interface{}{})
  48. //gob.Register(spec.Refable{})
  49. }
  50. // AddLoader for a document
  51. func AddLoader(predicate DocMatcher, load DocLoader) {
  52. prev := loaders
  53. loaders = &loader{
  54. Match: predicate,
  55. Fn: load,
  56. Next: prev,
  57. }
  58. spec.PathLoader = loaders.Fn
  59. }
  60. type loader struct {
  61. Fn DocLoader
  62. Match DocMatcher
  63. Next *loader
  64. }
  65. // JSONSpec loads a spec from a json document
  66. func JSONSpec(path string) (*Document, error) {
  67. data, err := JSONDoc(path)
  68. if err != nil {
  69. return nil, err
  70. }
  71. // convert to json
  72. return Analyzed(data, "")
  73. }
  74. // Document represents a swagger spec document
  75. type Document struct {
  76. // specAnalyzer
  77. Analyzer *analysis.Spec
  78. spec *spec.Swagger
  79. specFilePath string
  80. origSpec *spec.Swagger
  81. schema *spec.Schema
  82. raw json.RawMessage
  83. }
  84. // Embedded returns a Document based on embedded specs. No analysis is required
  85. func Embedded(orig, flat json.RawMessage) (*Document, error) {
  86. var origSpec, flatSpec spec.Swagger
  87. if err := json.Unmarshal(orig, &origSpec); err != nil {
  88. return nil, err
  89. }
  90. if err := json.Unmarshal(flat, &flatSpec); err != nil {
  91. return nil, err
  92. }
  93. return &Document{
  94. raw: orig,
  95. origSpec: &origSpec,
  96. spec: &flatSpec,
  97. }, nil
  98. }
  99. // Spec loads a new spec document
  100. func Spec(path string) (*Document, error) {
  101. specURL, err := url.Parse(path)
  102. if err != nil {
  103. return nil, err
  104. }
  105. var lastErr error
  106. for l := loaders.Next; l != nil; l = l.Next {
  107. if loaders.Match(specURL.Path) {
  108. b, err2 := loaders.Fn(path)
  109. if err2 != nil {
  110. lastErr = err2
  111. continue
  112. }
  113. doc, err3 := Analyzed(b, "")
  114. if err3 != nil {
  115. return nil, err3
  116. }
  117. if doc != nil {
  118. doc.specFilePath = path
  119. }
  120. return doc, nil
  121. }
  122. }
  123. if lastErr != nil {
  124. return nil, lastErr
  125. }
  126. b, err := defaultLoader.Fn(path)
  127. if err != nil {
  128. return nil, err
  129. }
  130. document, err := Analyzed(b, "")
  131. if document != nil {
  132. document.specFilePath = path
  133. }
  134. return document, err
  135. }
  136. // Analyzed creates a new analyzed spec document
  137. func Analyzed(data json.RawMessage, version string) (*Document, error) {
  138. if version == "" {
  139. version = "2.0"
  140. }
  141. if version != "2.0" {
  142. return nil, fmt.Errorf("spec version %q is not supported", version)
  143. }
  144. raw := data
  145. trimmed := bytes.TrimSpace(data)
  146. if len(trimmed) > 0 {
  147. if trimmed[0] != '{' && trimmed[0] != '[' {
  148. yml, err := swag.BytesToYAMLDoc(trimmed)
  149. if err != nil {
  150. return nil, fmt.Errorf("analyzed: %v", err)
  151. }
  152. d, err := swag.YAMLToJSON(yml)
  153. if err != nil {
  154. return nil, fmt.Errorf("analyzed: %v", err)
  155. }
  156. raw = d
  157. }
  158. }
  159. swspec := new(spec.Swagger)
  160. if err := json.Unmarshal(raw, swspec); err != nil {
  161. return nil, err
  162. }
  163. origsqspec, err := cloneSpec(swspec)
  164. if err != nil {
  165. return nil, err
  166. }
  167. d := &Document{
  168. Analyzer: analysis.New(swspec),
  169. schema: spec.MustLoadSwagger20Schema(),
  170. spec: swspec,
  171. raw: raw,
  172. origSpec: origsqspec,
  173. }
  174. return d, nil
  175. }
  176. // Expanded expands the ref fields in the spec document and returns a new spec document
  177. func (d *Document) Expanded(options ...*spec.ExpandOptions) (*Document, error) {
  178. swspec := new(spec.Swagger)
  179. if err := json.Unmarshal(d.raw, swspec); err != nil {
  180. return nil, err
  181. }
  182. var expandOptions *spec.ExpandOptions
  183. if len(options) > 0 {
  184. expandOptions = options[0]
  185. } else {
  186. expandOptions = &spec.ExpandOptions{
  187. RelativeBase: d.specFilePath,
  188. }
  189. }
  190. if err := spec.ExpandSpec(swspec, expandOptions); err != nil {
  191. return nil, err
  192. }
  193. dd := &Document{
  194. Analyzer: analysis.New(swspec),
  195. spec: swspec,
  196. specFilePath: d.specFilePath,
  197. schema: spec.MustLoadSwagger20Schema(),
  198. raw: d.raw,
  199. origSpec: d.origSpec,
  200. }
  201. return dd, nil
  202. }
  203. // BasePath the base path for this spec
  204. func (d *Document) BasePath() string {
  205. return d.spec.BasePath
  206. }
  207. // Version returns the version of this spec
  208. func (d *Document) Version() string {
  209. return d.spec.Swagger
  210. }
  211. // Schema returns the swagger 2.0 schema
  212. func (d *Document) Schema() *spec.Schema {
  213. return d.schema
  214. }
  215. // Spec returns the swagger spec object model
  216. func (d *Document) Spec() *spec.Swagger {
  217. return d.spec
  218. }
  219. // Host returns the host for the API
  220. func (d *Document) Host() string {
  221. return d.spec.Host
  222. }
  223. // Raw returns the raw swagger spec as json bytes
  224. func (d *Document) Raw() json.RawMessage {
  225. return d.raw
  226. }
  227. // OrigSpec yields the original spec
  228. func (d *Document) OrigSpec() *spec.Swagger {
  229. return d.origSpec
  230. }
  231. // ResetDefinitions gives a shallow copy with the models reset
  232. func (d *Document) ResetDefinitions() *Document {
  233. defs := make(map[string]spec.Schema, len(d.origSpec.Definitions))
  234. for k, v := range d.origSpec.Definitions {
  235. defs[k] = v
  236. }
  237. d.spec.Definitions = defs
  238. return d
  239. }
  240. // Pristine creates a new pristine document instance based on the input data
  241. func (d *Document) Pristine() *Document {
  242. dd, _ := Analyzed(d.Raw(), d.Version())
  243. return dd
  244. }
  245. // SpecFilePath returns the file path of the spec if one is defined
  246. func (d *Document) SpecFilePath() string {
  247. return d.specFilePath
  248. }
  249. func cloneSpec(src *spec.Swagger) (*spec.Swagger, error) {
  250. var b bytes.Buffer
  251. if err := gob.NewEncoder(&b).Encode(src); err != nil {
  252. return nil, err
  253. }
  254. var dst spec.Swagger
  255. if err := gob.NewDecoder(&b).Decode(&dst); err != nil {
  256. return nil, err
  257. }
  258. return &dst, nil
  259. }