...

Source file src/github.com/go-openapi/loads/spec.go

Documentation: github.com/go-openapi/loads

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

View as plain text