...

Source file src/google.golang.org/api/google-api-go-generator/internal/disco/disco.go

Documentation: google.golang.org/api/google-api-go-generator/internal/disco

     1  // Copyright 2016 Google LLC
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package disco represents Google API discovery documents.
     6  package disco
     7  
     8  import (
     9  	"encoding/json"
    10  	"fmt"
    11  	"reflect"
    12  	"sort"
    13  	"strings"
    14  )
    15  
    16  // A Document is an API discovery document.
    17  type Document struct {
    18  	ID                string             `json:"id"`
    19  	Name              string             `json:"name"`
    20  	Version           string             `json:"version"`
    21  	Title             string             `json:"title"`
    22  	RootURL           string             `json:"rootUrl"`
    23  	MTLSRootURL       string             `json:"mtlsRootUrl"`
    24  	ServicePath       string             `json:"servicePath"`
    25  	BasePath          string             `json:"basePath"`
    26  	DocumentationLink string             `json:"documentationLink"`
    27  	Auth              Auth               `json:"auth"`
    28  	Features          []string           `json:"features"`
    29  	Methods           MethodList         `json:"methods"`
    30  	Schemas           map[string]*Schema `json:"schemas"`
    31  	Resources         ResourceList       `json:"resources"`
    32  }
    33  
    34  // init performs additional initialization and checks that
    35  // were not done during unmarshaling.
    36  func (d *Document) init() error {
    37  	schemasByID := map[string]*Schema{}
    38  	for _, s := range d.Schemas {
    39  		schemasByID[s.ID] = s
    40  	}
    41  	for name, s := range d.Schemas {
    42  		if s.Ref != "" {
    43  			return fmt.Errorf("top level schema %q is a reference", name)
    44  		}
    45  		s.Name = name
    46  		if err := s.init(schemasByID); err != nil {
    47  			return err
    48  		}
    49  	}
    50  	for _, m := range d.Methods {
    51  		if err := m.init(schemasByID); err != nil {
    52  			return err
    53  		}
    54  	}
    55  	for _, r := range d.Resources {
    56  		if err := r.init("", schemasByID); err != nil {
    57  			return err
    58  		}
    59  	}
    60  	return nil
    61  }
    62  
    63  // NewDocument unmarshals the bytes into a Document.
    64  // It also validates the document to make sure it is error-free.
    65  func NewDocument(bytes []byte) (*Document, error) {
    66  	// The discovery service returns JSON with this format if there's an error, e.g.
    67  	// the document isn't found.
    68  	var errDoc struct {
    69  		Error struct {
    70  			Code    int
    71  			Message string
    72  			Status  string
    73  		}
    74  	}
    75  	if err := json.Unmarshal(bytes, &errDoc); err == nil && errDoc.Error.Code != 0 {
    76  		return nil, fmt.Errorf("bad discovery doc: %+v", errDoc.Error)
    77  	}
    78  
    79  	var doc Document
    80  	if err := json.Unmarshal(bytes, &doc); err != nil {
    81  		return nil, err
    82  	}
    83  	if err := doc.init(); err != nil {
    84  		return nil, err
    85  	}
    86  	return &doc, nil
    87  }
    88  
    89  // Auth represents the auth section of a discovery document.
    90  // Only OAuth2 information is retained.
    91  type Auth struct {
    92  	OAuth2Scopes []Scope
    93  }
    94  
    95  // A Scope is an OAuth2 scope.
    96  type Scope struct {
    97  	ID          string
    98  	Description string
    99  }
   100  
   101  // UnmarshalJSON implements the json.Unmarshaler interface.
   102  func (a *Auth) UnmarshalJSON(data []byte) error {
   103  	// Pull out the oauth2 scopes and turn them into nice structs.
   104  	// Ignore other auth information.
   105  	var m struct {
   106  		OAuth2 struct {
   107  			Scopes map[string]struct {
   108  				Description string
   109  			}
   110  		}
   111  	}
   112  	if err := json.Unmarshal(data, &m); err != nil {
   113  		return err
   114  	}
   115  	// Sort keys to provide a deterministic ordering, mainly for testing.
   116  	for _, k := range sortedKeys(m.OAuth2.Scopes) {
   117  		a.OAuth2Scopes = append(a.OAuth2Scopes, Scope{
   118  			ID:          k,
   119  			Description: m.OAuth2.Scopes[k].Description,
   120  		})
   121  	}
   122  	return nil
   123  }
   124  
   125  // A Schema holds a JSON Schema as defined by
   126  // https://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1.
   127  // We only support the subset of JSON Schema needed for Google API generation.
   128  type Schema struct {
   129  	ID                   string // union types not supported
   130  	Type                 string // union types not supported
   131  	Format               string
   132  	Description          string
   133  	Properties           PropertyList
   134  	ItemSchema           *Schema `json:"items"` // array of schemas not supported
   135  	AdditionalProperties *Schema // boolean not supported
   136  	Ref                  string  `json:"$ref"`
   137  	Default              string
   138  	Pattern              string
   139  	Enums                []string `json:"enum"`
   140  	// Google extensions to JSON Schema
   141  	EnumDescriptions []string
   142  	Variant          *Variant
   143  
   144  	RefSchema *Schema `json:"-"` // Schema referred to by $ref
   145  	Name      string  `json:"-"` // Schema name, if top level
   146  	Kind      Kind    `json:"-"`
   147  }
   148  
   149  type Variant struct {
   150  	Discriminant string
   151  	Map          []*VariantMapItem
   152  }
   153  
   154  type VariantMapItem struct {
   155  	TypeValue string `json:"type_value"`
   156  	Ref       string `json:"$ref"`
   157  }
   158  
   159  func (s *Schema) init(topLevelSchemas map[string]*Schema) error {
   160  	if s == nil {
   161  		return nil
   162  	}
   163  	var err error
   164  	if s.Ref != "" {
   165  		if s.RefSchema, err = resolveRef(s.Ref, topLevelSchemas); err != nil {
   166  			return err
   167  		}
   168  	}
   169  	s.Kind, err = s.initKind()
   170  	if err != nil {
   171  		return err
   172  	}
   173  	if s.Kind == ArrayKind && s.ItemSchema == nil {
   174  		return fmt.Errorf("schema %+v: array does not have items", s)
   175  	}
   176  	if s.Kind != ArrayKind && s.ItemSchema != nil {
   177  		return fmt.Errorf("schema %+v: non-array has items", s)
   178  	}
   179  	if err := s.AdditionalProperties.init(topLevelSchemas); err != nil {
   180  		return err
   181  	}
   182  	if err := s.ItemSchema.init(topLevelSchemas); err != nil {
   183  		return err
   184  	}
   185  	for _, p := range s.Properties {
   186  		if err := p.Schema.init(topLevelSchemas); err != nil {
   187  			return err
   188  		}
   189  	}
   190  	return nil
   191  }
   192  
   193  func resolveRef(ref string, topLevelSchemas map[string]*Schema) (*Schema, error) {
   194  	rs, ok := topLevelSchemas[ref]
   195  	if !ok {
   196  		return nil, fmt.Errorf("could not resolve schema reference %q", ref)
   197  	}
   198  	return rs, nil
   199  }
   200  
   201  func (s *Schema) initKind() (Kind, error) {
   202  	if s.Ref != "" {
   203  		return ReferenceKind, nil
   204  	}
   205  	switch s.Type {
   206  	case "string", "number", "integer", "boolean", "any":
   207  		return SimpleKind, nil
   208  	case "object":
   209  		if s.AdditionalProperties != nil {
   210  			if s.AdditionalProperties.Type == "any" {
   211  				return AnyStructKind, nil
   212  			}
   213  			return MapKind, nil
   214  		}
   215  		return StructKind, nil
   216  	case "array":
   217  		return ArrayKind, nil
   218  	default:
   219  		return 0, fmt.Errorf("unknown type %q for schema %q", s.Type, s.ID)
   220  	}
   221  }
   222  
   223  // ElementSchema returns the schema for the element type of s. For maps,
   224  // this is the schema of the map values. For arrays, it is the schema
   225  // of the array item type.
   226  //
   227  // ElementSchema panics if called on a schema that is not of kind map or array.
   228  func (s *Schema) ElementSchema() *Schema {
   229  	switch s.Kind {
   230  	case MapKind:
   231  		return s.AdditionalProperties
   232  	case ArrayKind:
   233  		return s.ItemSchema
   234  	default:
   235  		panic("ElementSchema called on schema of type " + s.Type)
   236  	}
   237  }
   238  
   239  // IsIntAsString reports whether the schema represents an integer value
   240  // formatted as a string.
   241  func (s *Schema) IsIntAsString() bool {
   242  	return s.Type == "string" && strings.Contains(s.Format, "int")
   243  }
   244  
   245  // Kind classifies a Schema.
   246  type Kind int
   247  
   248  const (
   249  	// SimpleKind is the category for any JSON Schema that maps to a
   250  	// primitive Go type: strings, numbers, booleans, and "any" (since it
   251  	// maps to interface{}).
   252  	SimpleKind Kind = iota
   253  
   254  	// StructKind is the category for a JSON Schema that declares a JSON
   255  	// object without any additional (arbitrary) properties.
   256  	StructKind
   257  
   258  	// MapKind is the category for a JSON Schema that declares a JSON
   259  	// object with additional (arbitrary) properties that have a non-"any"
   260  	// schema type.
   261  	MapKind
   262  
   263  	// AnyStructKind is the category for a JSON Schema that declares a
   264  	// JSON object with additional (arbitrary) properties that can be any
   265  	// type.
   266  	AnyStructKind
   267  
   268  	// ArrayKind is the category for a JSON Schema that declares an
   269  	// "array" type.
   270  	ArrayKind
   271  
   272  	// ReferenceKind is the category for a JSON Schema that is a reference
   273  	// to another JSON Schema.  During code generation, these references
   274  	// are resolved using the API.schemas map.
   275  	// See https://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.28
   276  	// for more details on the format.
   277  	ReferenceKind
   278  )
   279  
   280  type Property struct {
   281  	Name   string
   282  	Schema *Schema
   283  }
   284  
   285  type PropertyList []*Property
   286  
   287  func (pl *PropertyList) UnmarshalJSON(data []byte) error {
   288  	// In the discovery doc, properties are a map. Convert to a list.
   289  	var m map[string]*Schema
   290  	if err := json.Unmarshal(data, &m); err != nil {
   291  		return err
   292  	}
   293  	for _, k := range sortedKeys(m) {
   294  		*pl = append(*pl, &Property{
   295  			Name:   k,
   296  			Schema: m[k],
   297  		})
   298  	}
   299  	return nil
   300  }
   301  
   302  type ResourceList []*Resource
   303  
   304  func (rl *ResourceList) UnmarshalJSON(data []byte) error {
   305  	// In the discovery doc, resources are a map. Convert to a list.
   306  	var m map[string]*Resource
   307  	if err := json.Unmarshal(data, &m); err != nil {
   308  		return err
   309  	}
   310  	for _, k := range sortedKeys(m) {
   311  		r := m[k]
   312  		r.Name = k
   313  		*rl = append(*rl, r)
   314  	}
   315  	return nil
   316  }
   317  
   318  // A Resource holds information about a Google API Resource.
   319  type Resource struct {
   320  	Name      string
   321  	FullName  string // {parent.FullName}.{Name}
   322  	Methods   MethodList
   323  	Resources ResourceList
   324  }
   325  
   326  func (r *Resource) init(parentFullName string, topLevelSchemas map[string]*Schema) error {
   327  	r.FullName = fmt.Sprintf("%s.%s", parentFullName, r.Name)
   328  	for _, m := range r.Methods {
   329  		if err := m.init(topLevelSchemas); err != nil {
   330  			return err
   331  		}
   332  	}
   333  	for _, r2 := range r.Resources {
   334  		if err := r2.init(r.FullName, topLevelSchemas); err != nil {
   335  			return err
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  type MethodList []*Method
   342  
   343  func (ml *MethodList) UnmarshalJSON(data []byte) error {
   344  	// In the discovery doc, resources are a map. Convert to a list.
   345  	var m map[string]*Method
   346  	if err := json.Unmarshal(data, &m); err != nil {
   347  		return err
   348  	}
   349  	for _, k := range sortedKeys(m) {
   350  		meth := m[k]
   351  		meth.Name = k
   352  		*ml = append(*ml, meth)
   353  	}
   354  	return nil
   355  }
   356  
   357  // A Method holds information about a resource method.
   358  type Method struct {
   359  	Name                  string
   360  	ID                    string
   361  	Path                  string
   362  	HTTPMethod            string
   363  	Description           string
   364  	Parameters            ParameterList
   365  	ParameterOrder        []string
   366  	Request               *Schema
   367  	Response              *Schema
   368  	Scopes                []string
   369  	MediaUpload           *MediaUpload
   370  	SupportsMediaDownload bool
   371  	APIVersion            string
   372  
   373  	JSONMap map[string]interface{} `json:"-"`
   374  }
   375  
   376  type MediaUpload struct {
   377  	Accept    []string
   378  	MaxSize   string
   379  	Protocols map[string]Protocol
   380  }
   381  
   382  type Protocol struct {
   383  	Multipart bool
   384  	Path      string
   385  }
   386  
   387  func (m *Method) init(topLevelSchemas map[string]*Schema) error {
   388  	if err := m.Request.init(topLevelSchemas); err != nil {
   389  		return err
   390  	}
   391  	if err := m.Response.init(topLevelSchemas); err != nil {
   392  		return err
   393  	}
   394  	return nil
   395  }
   396  
   397  func (m *Method) UnmarshalJSON(data []byte) error {
   398  	type T Method // avoid a recursive call to UnmarshalJSON
   399  	if err := json.Unmarshal(data, (*T)(m)); err != nil {
   400  		return err
   401  	}
   402  	// Keep the unmarshalled map around, because the generator
   403  	// outputs it as a comment after the method body.
   404  	// TODO(jba): make this unnecessary.
   405  	return json.Unmarshal(data, &m.JSONMap)
   406  }
   407  
   408  type ParameterList []*Parameter
   409  
   410  func (pl *ParameterList) UnmarshalJSON(data []byte) error {
   411  	// In the discovery doc, resources are a map. Convert to a list.
   412  	var m map[string]*Parameter
   413  	if err := json.Unmarshal(data, &m); err != nil {
   414  		return err
   415  	}
   416  	for _, k := range sortedKeys(m) {
   417  		p := m[k]
   418  		p.Name = k
   419  		*pl = append(*pl, p)
   420  	}
   421  	return nil
   422  }
   423  
   424  // A Parameter holds information about a method parameter.
   425  type Parameter struct {
   426  	Name string
   427  	Schema
   428  	Required bool
   429  	Repeated bool
   430  	Location string
   431  }
   432  
   433  // sortedKeys returns the keys of m, which must be a map[string]T, in sorted order.
   434  func sortedKeys(m interface{}) []string {
   435  	vkeys := reflect.ValueOf(m).MapKeys()
   436  	var keys []string
   437  	for _, vk := range vkeys {
   438  		keys = append(keys, vk.Interface().(string))
   439  	}
   440  	sort.Strings(keys)
   441  	return keys
   442  }
   443  

View as plain text