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.
 
 
 

441 lines
11 KiB

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