|
- // Copyright 2016 Google Inc. All rights reserved.
- // Use of this source code is governed by a BSD-style
- // license that can be found in the LICENSE file.
-
- // Package disco represents Google API discovery documents.
- package disco
-
- import (
- "encoding/json"
- "fmt"
- "reflect"
- "sort"
- "strings"
- )
-
- // A Document is an API discovery document.
- type Document struct {
- ID string `json:"id"`
- Name string `json:"name"`
- Version string `json:"version"`
- Title string `json:"title"`
- RootURL string `json:"rootUrl"`
- ServicePath string `json:"servicePath"`
- BasePath string `json:"basePath"`
- DocumentationLink string `json:"documentationLink"`
- Auth Auth `json:"auth"`
- Features []string `json:"features"`
- Methods MethodList `json:"methods"`
- Schemas map[string]*Schema `json:"schemas"`
- Resources ResourceList `json:"resources"`
- }
-
- // init performs additional initialization and checks that
- // were not done during unmarshaling.
- func (d *Document) init() error {
- schemasByID := map[string]*Schema{}
- for _, s := range d.Schemas {
- schemasByID[s.ID] = s
- }
- for name, s := range d.Schemas {
- if s.Ref != "" {
- return fmt.Errorf("top level schema %q is a reference", name)
- }
- s.Name = name
- if err := s.init(schemasByID); err != nil {
- return err
- }
- }
- for _, m := range d.Methods {
- if err := m.init(schemasByID); err != nil {
- return err
- }
- }
- for _, r := range d.Resources {
- if err := r.init("", schemasByID); err != nil {
- return err
- }
- }
- return nil
- }
-
- // NewDocument unmarshals the bytes into a Document.
- // It also validates the document to make sure it is error-free.
- func NewDocument(bytes []byte) (*Document, error) {
- // The discovery service returns JSON with this format if there's an error, e.g.
- // the document isn't found.
- var errDoc struct {
- Error struct {
- Code int
- Message string
- Status string
- }
- }
- if err := json.Unmarshal(bytes, &errDoc); err == nil && errDoc.Error.Code != 0 {
- return nil, fmt.Errorf("bad discovery doc: %+v", errDoc.Error)
- }
-
- var doc Document
- if err := json.Unmarshal(bytes, &doc); err != nil {
- return nil, err
- }
- if err := doc.init(); err != nil {
- return nil, err
- }
- return &doc, nil
- }
-
- // Auth represents the auth section of a discovery document.
- // Only OAuth2 information is retained.
- type Auth struct {
- OAuth2Scopes []Scope
- }
-
- // A Scope is an OAuth2 scope.
- type Scope struct {
- URL string
- Description string
- }
-
- // UnmarshalJSON implements the json.Unmarshaler interface.
- func (a *Auth) UnmarshalJSON(data []byte) error {
- // Pull out the oauth2 scopes and turn them into nice structs.
- // Ignore other auth information.
- var m struct {
- OAuth2 struct {
- Scopes map[string]struct {
- Description string
- }
- }
- }
- if err := json.Unmarshal(data, &m); err != nil {
- return err
- }
- // Sort keys to provide a deterministic ordering, mainly for testing.
- for _, k := range sortedKeys(m.OAuth2.Scopes) {
- a.OAuth2Scopes = append(a.OAuth2Scopes, Scope{
- URL: k,
- Description: m.OAuth2.Scopes[k].Description,
- })
- }
- return nil
- }
-
- // A Schema holds a JSON Schema as defined by
- // https://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1.
- // We only support the subset of JSON Schema needed for Google API generation.
- type Schema struct {
- ID string // union types not supported
- Type string // union types not supported
- Format string
- Description string
- Properties PropertyList
- ItemSchema *Schema `json:"items"` // array of schemas not supported
- AdditionalProperties *Schema // boolean not supported
- Ref string `json:"$ref"`
- Default string
- Pattern string
- Enums []string `json:"enum"`
- // Google extensions to JSON Schema
- EnumDescriptions []string
- Variant *Variant
-
- RefSchema *Schema `json:"-"` // Schema referred to by $ref
- Name string `json:"-"` // Schema name, if top level
- Kind Kind `json:"-"`
- }
-
- type Variant struct {
- Discriminant string
- Map []*VariantMapItem
- }
-
- type VariantMapItem struct {
- TypeValue string `json:"type_value"`
- Ref string `json:"$ref"`
- }
-
- func (s *Schema) init(topLevelSchemas map[string]*Schema) error {
- if s == nil {
- return nil
- }
- var err error
- if s.Ref != "" {
- if s.RefSchema, err = resolveRef(s.Ref, topLevelSchemas); err != nil {
- return err
- }
- }
- s.Kind, err = s.initKind()
- if err != nil {
- return err
- }
- if s.Kind == ArrayKind && s.ItemSchema == nil {
- return fmt.Errorf("schema %+v: array does not have items", s)
- }
- if s.Kind != ArrayKind && s.ItemSchema != nil {
- return fmt.Errorf("schema %+v: non-array has items", s)
- }
- if err := s.AdditionalProperties.init(topLevelSchemas); err != nil {
- return err
- }
- if err := s.ItemSchema.init(topLevelSchemas); err != nil {
- return err
- }
- for _, p := range s.Properties {
- if err := p.Schema.init(topLevelSchemas); err != nil {
- return err
- }
- }
- return nil
- }
-
- func resolveRef(ref string, topLevelSchemas map[string]*Schema) (*Schema, error) {
- rs, ok := topLevelSchemas[ref]
- if !ok {
- return nil, fmt.Errorf("could not resolve schema reference %q", ref)
- }
- return rs, nil
- }
-
- func (s *Schema) initKind() (Kind, error) {
- if s.Ref != "" {
- return ReferenceKind, nil
- }
- switch s.Type {
- case "string", "number", "integer", "boolean", "any":
- return SimpleKind, nil
- case "object":
- if s.AdditionalProperties != nil {
- if s.AdditionalProperties.Type == "any" {
- return AnyStructKind, nil
- }
- return MapKind, nil
- }
- return StructKind, nil
- case "array":
- return ArrayKind, nil
- default:
- return 0, fmt.Errorf("unknown type %q for schema %q", s.Type, s.ID)
- }
- }
-
- // ElementSchema returns the schema for the element type of s. For maps,
- // this is the schema of the map values. For arrays, it is the schema
- // of the array item type.
- //
- // ElementSchema panics if called on a schema that is not of kind map or array.
- func (s *Schema) ElementSchema() *Schema {
- switch s.Kind {
- case MapKind:
- return s.AdditionalProperties
- case ArrayKind:
- return s.ItemSchema
- default:
- panic("ElementSchema called on schema of type " + s.Type)
- }
- }
-
- // IsIntAsString reports whether the schema represents an integer value
- // formatted as a string.
- func (s *Schema) IsIntAsString() bool {
- return s.Type == "string" && strings.Contains(s.Format, "int")
- }
-
- // Kind classifies a Schema.
- type Kind int
-
- const (
- // SimpleKind is the category for any JSON Schema that maps to a
- // primitive Go type: strings, numbers, booleans, and "any" (since it
- // maps to interface{}).
- SimpleKind Kind = iota
-
- // StructKind is the category for a JSON Schema that declares a JSON
- // object without any additional (arbitrary) properties.
- StructKind
-
- // MapKind is the category for a JSON Schema that declares a JSON
- // object with additional (arbitrary) properties that have a non-"any"
- // schema type.
- MapKind
-
- // AnyStructKind is the category for a JSON Schema that declares a
- // JSON object with additional (arbitrary) properties that can be any
- // type.
- AnyStructKind
-
- // ArrayKind is the category for a JSON Schema that declares an
- // "array" type.
- ArrayKind
-
- // ReferenceKind is the category for a JSON Schema that is a reference
- // to another JSON Schema. During code generation, these references
- // are resolved using the API.schemas map.
- // See https://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.28
- // for more details on the format.
- ReferenceKind
- )
-
- type Property struct {
- Name string
- Schema *Schema
- }
-
- type PropertyList []*Property
-
- func (pl *PropertyList) UnmarshalJSON(data []byte) error {
- // In the discovery doc, properties are a map. Convert to a list.
- var m map[string]*Schema
- if err := json.Unmarshal(data, &m); err != nil {
- return err
- }
- for _, k := range sortedKeys(m) {
- *pl = append(*pl, &Property{
- Name: k,
- Schema: m[k],
- })
- }
- return nil
- }
-
- type ResourceList []*Resource
-
- func (rl *ResourceList) UnmarshalJSON(data []byte) error {
- // In the discovery doc, resources are a map. Convert to a list.
- var m map[string]*Resource
- if err := json.Unmarshal(data, &m); err != nil {
- return err
- }
- for _, k := range sortedKeys(m) {
- r := m[k]
- r.Name = k
- *rl = append(*rl, r)
- }
- return nil
- }
-
- // A Resource holds information about a Google API Resource.
- type Resource struct {
- Name string
- FullName string // {parent.FullName}.{Name}
- Methods MethodList
- Resources ResourceList
- }
-
- func (r *Resource) init(parentFullName string, topLevelSchemas map[string]*Schema) error {
- r.FullName = fmt.Sprintf("%s.%s", parentFullName, r.Name)
- for _, m := range r.Methods {
- if err := m.init(topLevelSchemas); err != nil {
- return err
- }
- }
- for _, r2 := range r.Resources {
- if err := r2.init(r.FullName, topLevelSchemas); err != nil {
- return err
- }
- }
- return nil
- }
-
- type MethodList []*Method
-
- func (ml *MethodList) UnmarshalJSON(data []byte) error {
- // In the discovery doc, resources are a map. Convert to a list.
- var m map[string]*Method
- if err := json.Unmarshal(data, &m); err != nil {
- return err
- }
- for _, k := range sortedKeys(m) {
- meth := m[k]
- meth.Name = k
- *ml = append(*ml, meth)
- }
- return nil
- }
-
- // A Method holds information about a resource method.
- type Method struct {
- Name string
- ID string
- Path string
- HTTPMethod string
- Description string
- Parameters ParameterList
- ParameterOrder []string
- Request *Schema
- Response *Schema
- Scopes []string
- MediaUpload *MediaUpload
- SupportsMediaDownload bool
-
- JSONMap map[string]interface{} `json:"-"`
- }
-
- type MediaUpload struct {
- Accept []string
- MaxSize string
- Protocols map[string]Protocol
- }
-
- type Protocol struct {
- Multipart bool
- Path string
- }
-
- func (m *Method) init(topLevelSchemas map[string]*Schema) error {
- if err := m.Request.init(topLevelSchemas); err != nil {
- return err
- }
- if err := m.Response.init(topLevelSchemas); err != nil {
- return err
- }
- return nil
- }
-
- func (m *Method) UnmarshalJSON(data []byte) error {
- type T Method // avoid a recursive call to UnmarshalJSON
- if err := json.Unmarshal(data, (*T)(m)); err != nil {
- return err
- }
- // Keep the unmarshalled map around, because the generator
- // outputs it as a comment after the method body.
- // TODO(jba): make this unnecessary.
- return json.Unmarshal(data, &m.JSONMap)
- }
-
- type ParameterList []*Parameter
-
- func (pl *ParameterList) UnmarshalJSON(data []byte) error {
- // In the discovery doc, resources are a map. Convert to a list.
- var m map[string]*Parameter
- if err := json.Unmarshal(data, &m); err != nil {
- return err
- }
- for _, k := range sortedKeys(m) {
- p := m[k]
- p.Name = k
- *pl = append(*pl, p)
- }
- return nil
- }
-
- // A Parameter holds information about a method parameter.
- type Parameter struct {
- Name string
- Schema
- Required bool
- Repeated bool
- Location string
- }
-
- // sortedKeys returns the keys of m, which must be a map[string]T, in sorted order.
- func sortedKeys(m interface{}) []string {
- vkeys := reflect.ValueOf(m).MapKeys()
- var keys []string
- for _, vk := range vkeys {
- keys = append(keys, vk.Interface().(string))
- }
- sort.Strings(keys)
- return keys
- }
|