main.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846
  1. // Copyright 2017 Google Inc. All Rights Reserved.
  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. // schema-generator is a support tool that generates the OpenAPI v3 JSON schema.
  15. // Yes, it's gross, but the OpenAPI 3.0 spec, which defines REST APIs with a
  16. // rigorous JSON schema, is itself defined with a Markdown file. Ironic?
  17. package main
  18. import (
  19. "encoding/json"
  20. "fmt"
  21. "io/ioutil"
  22. "log"
  23. "os"
  24. "regexp"
  25. "sort"
  26. "strings"
  27. "unicode"
  28. "unicode/utf8"
  29. "github.com/googleapis/gnostic/jsonschema"
  30. )
  31. // convert the first character of a string to lower case
  32. func lowerFirst(s string) string {
  33. if s == "" {
  34. return ""
  35. }
  36. r, n := utf8.DecodeRuneInString(s)
  37. return string(unicode.ToLower(r)) + s[n:]
  38. }
  39. // Section models a section of the OpenAPI specification text document.
  40. type Section struct {
  41. Level int
  42. Text string
  43. Title string
  44. Children []*Section
  45. }
  46. // ReadSection reads a section of the OpenAPI Specification, recursively dividing it into subsections
  47. func ReadSection(text string, level int) (section *Section) {
  48. titlePattern := regexp.MustCompile("^" + strings.Repeat("#", level) + " .*$")
  49. subtitlePattern := regexp.MustCompile("^" + strings.Repeat("#", level+1) + " .*$")
  50. section = &Section{Level: level, Text: text}
  51. lines := strings.Split(string(text), "\n")
  52. subsection := ""
  53. for i, line := range lines {
  54. if i == 0 && titlePattern.Match([]byte(line)) {
  55. section.Title = line
  56. } else if subtitlePattern.Match([]byte(line)) {
  57. // we've found a subsection title.
  58. // if there's a subsection that we've already been reading, save it
  59. if len(subsection) != 0 {
  60. child := ReadSection(subsection, level+1)
  61. section.Children = append(section.Children, child)
  62. }
  63. // start a new subsection
  64. subsection = line + "\n"
  65. } else {
  66. // add to the subsection we've been reading
  67. subsection += line + "\n"
  68. }
  69. }
  70. // if this section has subsections, save the last one
  71. if len(section.Children) > 0 {
  72. child := ReadSection(subsection, level+1)
  73. section.Children = append(section.Children, child)
  74. }
  75. return
  76. }
  77. // Display recursively displays a section of the specification.
  78. func (s *Section) Display(section string) {
  79. if len(s.Children) == 0 {
  80. //fmt.Printf("%s\n", s.Text)
  81. } else {
  82. for i, child := range s.Children {
  83. var subsection string
  84. if section == "" {
  85. subsection = fmt.Sprintf("%d", i)
  86. } else {
  87. subsection = fmt.Sprintf("%s.%d", section, i)
  88. }
  89. fmt.Printf("%-12s %s\n", subsection, child.NiceTitle())
  90. child.Display(subsection)
  91. }
  92. }
  93. }
  94. // remove a link from a string, leaving only the text that follows it
  95. // if there is no link, just return the string
  96. func stripLink(input string) (output string) {
  97. stringPattern := regexp.MustCompile("^(.*)$")
  98. stringWithLinkPattern := regexp.MustCompile("^<a .*</a>(.*)$")
  99. if matches := stringWithLinkPattern.FindSubmatch([]byte(input)); matches != nil {
  100. return string(matches[1])
  101. } else if matches := stringPattern.FindSubmatch([]byte(input)); matches != nil {
  102. return string(matches[1])
  103. } else {
  104. return input
  105. }
  106. }
  107. // NiceTitle returns a nice-to-display title for a section by removing the opening "###" and any links.
  108. func (s *Section) NiceTitle() string {
  109. titlePattern := regexp.MustCompile("^#+ (.*)$")
  110. titleWithLinkPattern := regexp.MustCompile("^#+ <a .*</a>(.*)$")
  111. if matches := titleWithLinkPattern.FindSubmatch([]byte(s.Title)); matches != nil {
  112. return string(matches[1])
  113. } else if matches := titlePattern.FindSubmatch([]byte(s.Title)); matches != nil {
  114. return string(matches[1])
  115. } else {
  116. return ""
  117. }
  118. }
  119. // replace markdown links with their link text (removing the URL part)
  120. func removeMarkdownLinks(input string) (output string) {
  121. markdownLink := regexp.MustCompile("\\[([^\\]\\[]*)\\]\\(([^\\)]*)\\)") // matches [link title](link url)
  122. output = string(markdownLink.ReplaceAll([]byte(input), []byte("$1")))
  123. return
  124. }
  125. // extract the fixed fields from a table in a section
  126. func parseFixedFields(input string, schemaObject *SchemaObject) {
  127. lines := strings.Split(input, "\n")
  128. for _, line := range lines {
  129. // replace escaped bars with "OR", assuming these are used to describe union types
  130. line = strings.Replace(line, " \\| ", " OR ", -1)
  131. // split the table on the remaining bars
  132. parts := strings.Split(line, "|")
  133. if len(parts) > 1 {
  134. fieldName := strings.Trim(stripLink(parts[0]), " ")
  135. if fieldName != "Field Name" && fieldName != "---" {
  136. if len(parts) == 3 || len(parts) == 4 {
  137. // this is what we expect
  138. } else {
  139. log.Printf("ERROR: %+v", parts)
  140. }
  141. typeName := parts[1]
  142. typeName = strings.Replace(typeName, "{expression}", "Expression", -1)
  143. typeName = strings.Trim(typeName, " ")
  144. typeName = strings.Replace(typeName, "`", "", -1)
  145. typeName = removeMarkdownLinks(typeName)
  146. typeName = strings.Replace(typeName, " ", "", -1)
  147. typeName = strings.Replace(typeName, "Object", "", -1)
  148. isArray := false
  149. if typeName[0] == '[' && typeName[len(typeName)-1] == ']' {
  150. typeName = typeName[1 : len(typeName)-1]
  151. isArray = true
  152. }
  153. isMap := false
  154. mapPattern := regexp.MustCompile("^Mapstring,\\[(.*)\\]$")
  155. if matches := mapPattern.FindSubmatch([]byte(typeName)); matches != nil {
  156. typeName = string(matches[1])
  157. isMap = true
  158. } else {
  159. // match map[string,<typename>]
  160. mapPattern2 := regexp.MustCompile("^Map\\[string,(.+)\\]$")
  161. if matches := mapPattern2.FindSubmatch([]byte(typeName)); matches != nil {
  162. typeName = string(matches[1])
  163. isMap = true
  164. }
  165. }
  166. description := strings.Trim(parts[len(parts)-1], " ")
  167. description = removeMarkdownLinks(description)
  168. description = strings.Replace(description, "\n", " ", -1)
  169. requiredLabel1 := "**Required.** "
  170. requiredLabel2 := "**REQUIRED**."
  171. if strings.Contains(description, requiredLabel1) ||
  172. strings.Contains(description, requiredLabel2) {
  173. // only include required values if their "Validity" is "Any" or if no validity is specified
  174. valid := true
  175. if len(parts) == 4 {
  176. validity := parts[2]
  177. if strings.Contains(validity, "Any") {
  178. valid = true
  179. } else {
  180. valid = false
  181. }
  182. }
  183. if valid {
  184. schemaObject.RequiredFields = append(schemaObject.RequiredFields, fieldName)
  185. }
  186. description = strings.Replace(description, requiredLabel1, "", -1)
  187. description = strings.Replace(description, requiredLabel2, "", -1)
  188. }
  189. schemaField := SchemaObjectField{
  190. Name: fieldName,
  191. Type: typeName,
  192. IsArray: isArray,
  193. IsMap: isMap,
  194. Description: description,
  195. }
  196. schemaObject.FixedFields = append(schemaObject.FixedFields, schemaField)
  197. }
  198. }
  199. }
  200. }
  201. // extract the patterned fields from a table in a section
  202. func parsePatternedFields(input string, schemaObject *SchemaObject) {
  203. lines := strings.Split(input, "\n")
  204. for _, line := range lines {
  205. line = strings.Replace(line, " \\| ", " OR ", -1)
  206. parts := strings.Split(line, "|")
  207. if len(parts) > 1 {
  208. fieldName := strings.Trim(stripLink(parts[0]), " ")
  209. fieldName = removeMarkdownLinks(fieldName)
  210. if fieldName == "HTTP Status Code" {
  211. fieldName = "^([0-9X]{3})$"
  212. }
  213. if fieldName != "Field Pattern" && fieldName != "---" {
  214. typeName := parts[1]
  215. typeName = strings.Trim(typeName, " ")
  216. typeName = strings.Replace(typeName, "`", "", -1)
  217. typeName = removeMarkdownLinks(typeName)
  218. typeName = strings.Replace(typeName, " ", "", -1)
  219. typeName = strings.Replace(typeName, "Object", "", -1)
  220. typeName = strings.Replace(typeName, "{expression}", "Expression", -1)
  221. isArray := false
  222. if typeName[0] == '[' && typeName[len(typeName)-1] == ']' {
  223. typeName = typeName[1 : len(typeName)-1]
  224. isArray = true
  225. }
  226. isMap := false
  227. mapPattern := regexp.MustCompile("^Mapstring,\\[(.*)\\]$")
  228. if matches := mapPattern.FindSubmatch([]byte(typeName)); matches != nil {
  229. typeName = string(matches[1])
  230. isMap = true
  231. }
  232. description := strings.Trim(parts[len(parts)-1], " ")
  233. description = removeMarkdownLinks(description)
  234. description = strings.Replace(description, "\n", " ", -1)
  235. schemaField := SchemaObjectField{
  236. Name: fieldName,
  237. Type: typeName,
  238. IsArray: isArray,
  239. IsMap: isMap,
  240. Description: description,
  241. }
  242. schemaObject.PatternedFields = append(schemaObject.PatternedFields, schemaField)
  243. }
  244. }
  245. }
  246. }
  247. // SchemaObjectField describes a field of a schema.
  248. type SchemaObjectField struct {
  249. Name string `json:"name"`
  250. Type string `json:"type"`
  251. IsArray bool `json:"is_array"`
  252. IsMap bool `json:"is_map"`
  253. Description string `json:"description"`
  254. }
  255. // SchemaObject describes a schema.
  256. type SchemaObject struct {
  257. Name string `json:"name"`
  258. ID string `json:"id"`
  259. Description string `json:"description"`
  260. Extendable bool `json:"extendable"`
  261. RequiredFields []string `json:"required"`
  262. FixedFields []SchemaObjectField `json:"fixed"`
  263. PatternedFields []SchemaObjectField `json:"patterned"`
  264. }
  265. // SchemaModel is a collection of schemas.
  266. type SchemaModel struct {
  267. Objects []SchemaObject
  268. }
  269. func (m *SchemaModel) objectWithID(id string) *SchemaObject {
  270. for _, object := range m.Objects {
  271. if object.ID == id {
  272. return &object
  273. }
  274. }
  275. return nil
  276. }
  277. // NewSchemaModel returns a new SchemaModel.
  278. func NewSchemaModel(filename string) (schemaModel *SchemaModel, err error) {
  279. b, err := ioutil.ReadFile("3.0.md")
  280. if err != nil {
  281. return nil, err
  282. }
  283. // divide the specification into sections
  284. document := ReadSection(string(b), 1)
  285. document.Display("")
  286. // read object names and their details
  287. specification := document.Children[4] // fragile! the section title is "Specification"
  288. schema := specification.Children[7] // fragile! the section title is "Schema"
  289. anchor := regexp.MustCompile("^#### <a name=\"(.*)Object\"")
  290. schemaObjects := make([]SchemaObject, 0)
  291. for _, section := range schema.Children {
  292. if matches := anchor.FindSubmatch([]byte(section.Title)); matches != nil {
  293. id := string(matches[1])
  294. schemaObject := SchemaObject{
  295. Name: section.NiceTitle(),
  296. ID: id,
  297. RequiredFields: nil,
  298. }
  299. if len(section.Children) > 0 {
  300. description := section.Children[0].Text
  301. description = removeMarkdownLinks(description)
  302. description = strings.Trim(description, " \t\n")
  303. description = strings.Replace(description, "\n", " ", -1)
  304. schemaObject.Description = description
  305. }
  306. // is the object extendable?
  307. if strings.Contains(section.Text, "Specification Extensions") {
  308. schemaObject.Extendable = true
  309. }
  310. // look for fixed fields
  311. for _, child := range section.Children {
  312. if child.NiceTitle() == "Fixed Fields" {
  313. parseFixedFields(child.Text, &schemaObject)
  314. }
  315. }
  316. // look for patterned fields
  317. for _, child := range section.Children {
  318. if child.NiceTitle() == "Patterned Fields" {
  319. parsePatternedFields(child.Text, &schemaObject)
  320. }
  321. }
  322. schemaObjects = append(schemaObjects, schemaObject)
  323. }
  324. }
  325. return &SchemaModel{Objects: schemaObjects}, nil
  326. }
  327. // UnionType represents a union of two types.
  328. type UnionType struct {
  329. Name string
  330. ObjectType1 string
  331. ObjectType2 string
  332. }
  333. var unionTypes map[string]*UnionType
  334. func noteUnionType(typeName, objectType1, objectType2 string) {
  335. if unionTypes == nil {
  336. unionTypes = make(map[string]*UnionType, 0)
  337. }
  338. unionTypes[typeName] = &UnionType{
  339. Name: typeName,
  340. ObjectType1: objectType1,
  341. ObjectType2: objectType2,
  342. }
  343. }
  344. // MapType represents a map of a specified type (with string keys).
  345. type MapType struct {
  346. Name string
  347. ObjectType string
  348. }
  349. var mapTypes map[string]*MapType
  350. func noteMapType(typeName, objectType string) {
  351. if mapTypes == nil {
  352. mapTypes = make(map[string]*MapType, 0)
  353. }
  354. mapTypes[typeName] = &MapType{
  355. Name: typeName,
  356. ObjectType: objectType,
  357. }
  358. }
  359. func definitionNameForType(typeName string) string {
  360. name := typeName
  361. switch typeName {
  362. case "OAuthFlows":
  363. name = "oauthFlows"
  364. case "OAuthFlow":
  365. name = "oauthFlow"
  366. case "XML":
  367. name = "xml"
  368. case "ExternalDocumentation":
  369. name = "externalDocs"
  370. default:
  371. // does the name contain an "OR"
  372. if parts := strings.Split(typeName, "OR"); len(parts) > 1 {
  373. name = lowerFirst(parts[0]) + "Or" + parts[1]
  374. noteUnionType(name, parts[0], parts[1])
  375. } else {
  376. name = lowerFirst(typeName)
  377. }
  378. }
  379. return "#/definitions/" + name
  380. }
  381. func pluralize(name string) string {
  382. if name == "any" {
  383. return "anys"
  384. }
  385. switch name[len(name)-1] {
  386. case 'y':
  387. name = name[0:len(name)-1] + "ies"
  388. case 's':
  389. name = name + "Map"
  390. default:
  391. name = name + "s"
  392. }
  393. return name
  394. }
  395. func definitionNameForMapOfType(typeName string) string {
  396. // pluralize the type name to get the name of an object representing a map of them
  397. var elementTypeName string
  398. var mapTypeName string
  399. if parts := strings.Split(typeName, "OR"); len(parts) > 1 {
  400. elementTypeName = lowerFirst(parts[0]) + "Or" + parts[1]
  401. noteUnionType(elementTypeName, parts[0], parts[1])
  402. mapTypeName = pluralize(lowerFirst(parts[0])) + "Or" + pluralize(parts[1])
  403. } else {
  404. elementTypeName = lowerFirst(typeName)
  405. mapTypeName = pluralize(elementTypeName)
  406. }
  407. noteMapType(mapTypeName, elementTypeName)
  408. return "#/definitions/" + mapTypeName
  409. }
  410. func updateSchemaFieldWithModelField(schemaField *jsonschema.Schema, modelField *SchemaObjectField) {
  411. // fmt.Printf("IN %s:%+v\n", name, schemaField)
  412. // update the attributes of the schema field
  413. if modelField.IsArray {
  414. // is array
  415. itemSchema := &jsonschema.Schema{}
  416. switch modelField.Type {
  417. case "string":
  418. itemSchema.Type = jsonschema.NewStringOrStringArrayWithString("string")
  419. case "boolean":
  420. itemSchema.Type = jsonschema.NewStringOrStringArrayWithString("boolean")
  421. case "primitive":
  422. itemSchema.Ref = stringptr(definitionNameForType("Primitive"))
  423. default:
  424. itemSchema.Ref = stringptr(definitionNameForType(modelField.Type))
  425. }
  426. schemaField.Items = jsonschema.NewSchemaOrSchemaArrayWithSchema(itemSchema)
  427. schemaField.Type = jsonschema.NewStringOrStringArrayWithString("array")
  428. boolValue := true // not sure about this
  429. schemaField.UniqueItems = &boolValue
  430. } else if modelField.IsMap {
  431. schemaField.Ref = stringptr(definitionNameForMapOfType(modelField.Type))
  432. } else {
  433. // is scalar
  434. switch modelField.Type {
  435. case "string":
  436. schemaField.Type = jsonschema.NewStringOrStringArrayWithString("string")
  437. case "boolean":
  438. schemaField.Type = jsonschema.NewStringOrStringArrayWithString("boolean")
  439. case "primitive":
  440. schemaField.Ref = stringptr(definitionNameForType("Primitive"))
  441. default:
  442. schemaField.Ref = stringptr(definitionNameForType(modelField.Type))
  443. }
  444. }
  445. }
  446. func buildSchemaWithModel(modelObject *SchemaObject) (schema *jsonschema.Schema) {
  447. schema = &jsonschema.Schema{}
  448. schema.Type = jsonschema.NewStringOrStringArrayWithString("object")
  449. if modelObject.RequiredFields != nil && len(modelObject.RequiredFields) > 0 {
  450. // copy array
  451. arrayCopy := modelObject.RequiredFields
  452. schema.Required = &arrayCopy
  453. }
  454. schema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(false)
  455. schema.Description = stringptr(modelObject.Description)
  456. // handle fixed fields
  457. if modelObject.FixedFields != nil {
  458. newNamedSchemas := make([]*jsonschema.NamedSchema, 0)
  459. for _, modelField := range modelObject.FixedFields {
  460. schemaField := schema.PropertyWithName(modelField.Name)
  461. if schemaField == nil {
  462. // create and add the schema field
  463. schemaField = &jsonschema.Schema{}
  464. namedSchema := &jsonschema.NamedSchema{Name: modelField.Name, Value: schemaField}
  465. newNamedSchemas = append(newNamedSchemas, namedSchema)
  466. }
  467. updateSchemaFieldWithModelField(schemaField, &modelField)
  468. }
  469. for _, pair := range newNamedSchemas {
  470. if schema.Properties == nil {
  471. properties := make([]*jsonschema.NamedSchema, 0)
  472. schema.Properties = &properties
  473. }
  474. *(schema.Properties) = append(*(schema.Properties), pair)
  475. }
  476. } else {
  477. if schema.Properties != nil {
  478. fmt.Printf("SCHEMA SHOULD NOT HAVE PROPERTIES %s\n", modelObject.ID)
  479. }
  480. }
  481. // handle patterned fields
  482. if modelObject.PatternedFields != nil {
  483. newNamedSchemas := make([]*jsonschema.NamedSchema, 0)
  484. for _, modelField := range modelObject.PatternedFields {
  485. schemaField := schema.PatternPropertyWithName(modelField.Name)
  486. if schemaField == nil {
  487. // create and add the schema field
  488. schemaField = &jsonschema.Schema{}
  489. // Component names should match "^[a-zA-Z0-9\.\-_]+$"
  490. // See https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#componentsObject
  491. nameRegex := "^[a-zA-Z0-9\\\\.\\\\-_]+$"
  492. if modelObject.Name == "Scopes Object" {
  493. nameRegex = "^"
  494. } else if modelObject.Name == "Headers Object" {
  495. nameRegex = "^[a-zA-Z0-9!#\\-\\$%&'\\*\\+\\\\\\.\\^_`\\|~]+"
  496. }
  497. propertyName := strings.Replace(modelField.Name, "{name}", nameRegex, -1)
  498. // The field name MUST begin with a slash, see https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#paths-object
  499. // JSON Schema for OpenAPI v2 uses "^/" as regex for paths, see https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/schemas/v2.0/schema.json#L173
  500. propertyName = strings.Replace(propertyName, "/{path}", "^/", -1)
  501. // Replace human-friendly (and regex-confusing) description with a blank pattern
  502. propertyName = strings.Replace(propertyName, "{expression}", "^", -1)
  503. propertyName = strings.Replace(propertyName, "{property}", "^", -1)
  504. namedSchema := &jsonschema.NamedSchema{Name: propertyName, Value: schemaField}
  505. newNamedSchemas = append(newNamedSchemas, namedSchema)
  506. }
  507. updateSchemaFieldWithModelField(schemaField, &modelField)
  508. }
  509. for _, pair := range newNamedSchemas {
  510. if schema.PatternProperties == nil {
  511. properties := make([]*jsonschema.NamedSchema, 0)
  512. schema.PatternProperties = &properties
  513. }
  514. *(schema.PatternProperties) = append(*(schema.PatternProperties), pair)
  515. }
  516. } else {
  517. if schema.PatternProperties != nil && !modelObject.Extendable {
  518. fmt.Printf("SCHEMA SHOULD NOT HAVE PATTERN PROPERTIES %s\n", modelObject.ID)
  519. }
  520. }
  521. if modelObject.Extendable {
  522. schemaField := schema.PatternPropertyWithName("^x-")
  523. if schemaField != nil {
  524. schemaField.Ref = stringptr("#/definitions/specificationExtension")
  525. } else {
  526. schemaField = &jsonschema.Schema{}
  527. schemaField.Ref = stringptr("#/definitions/specificationExtension")
  528. namedSchema := &jsonschema.NamedSchema{Name: "^x-", Value: schemaField}
  529. if schema.PatternProperties == nil {
  530. properties := make([]*jsonschema.NamedSchema, 0)
  531. schema.PatternProperties = &properties
  532. }
  533. *(schema.PatternProperties) = append(*(schema.PatternProperties), namedSchema)
  534. }
  535. } else {
  536. schemaField := schema.PatternPropertyWithName("^x-")
  537. if schemaField != nil {
  538. fmt.Printf("INVALID EXTENSION SUPPORT %s:%s\n", modelObject.ID, "^x-")
  539. }
  540. }
  541. return schema
  542. }
  543. // return a pointer to a copy of a passed-in string
  544. func stringptr(input string) (output *string) {
  545. return &input
  546. }
  547. func int64ptr(input int64) (output *int64) {
  548. return &input
  549. }
  550. func arrayOfSchema() *jsonschema.Schema {
  551. return &jsonschema.Schema{
  552. Type: jsonschema.NewStringOrStringArrayWithString("array"),
  553. MinItems: int64ptr(1),
  554. Items: jsonschema.NewSchemaOrSchemaArrayWithSchema(&jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")}),
  555. }
  556. }
  557. func main() {
  558. // read and parse the text specification into a model structure
  559. model, err := NewSchemaModel("3.0.md")
  560. if err != nil {
  561. panic(err)
  562. }
  563. // write the model as JSON (for debugging)
  564. modelJSON, _ := json.MarshalIndent(model, "", " ")
  565. err = ioutil.WriteFile("model.json", modelJSON, 0644)
  566. if err != nil {
  567. panic(err)
  568. }
  569. // build the top-level schema using the "OAS" model
  570. oasModel := model.objectWithID("oas")
  571. if oasModel == nil {
  572. log.Printf("Unable to find OAS model. Has the source document structure changed?")
  573. os.Exit(-1)
  574. }
  575. schema := buildSchemaWithModel(oasModel)
  576. // manually set a few fields
  577. schema.Title = stringptr("A JSON Schema for OpenAPI 3.0.")
  578. schema.ID = stringptr("http://openapis.org/v3/schema.json#")
  579. schema.Schema = stringptr("http://json-schema.org/draft-04/schema#")
  580. // loop over all models and create the corresponding schema objects
  581. definitions := make([]*jsonschema.NamedSchema, 0)
  582. schema.Definitions = &definitions
  583. for _, modelObject := range model.Objects {
  584. if modelObject.ID == "oas" {
  585. continue
  586. }
  587. definitionSchema := buildSchemaWithModel(&modelObject)
  588. name := modelObject.ID
  589. if name == "externalDocumentation" {
  590. name = "externalDocs"
  591. }
  592. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(name, definitionSchema))
  593. }
  594. // copy the properties of headerObject from parameterObject
  595. headerObject := schema.DefinitionWithName("header")
  596. parameterObject := schema.DefinitionWithName("parameter")
  597. if parameterObject != nil {
  598. newArray := make([]*jsonschema.NamedSchema, 0)
  599. for _, property := range *(parameterObject.Properties) {
  600. // we need to remove a few properties...
  601. if property.Name != "name" && property.Name != "in" {
  602. newArray = append(newArray, property)
  603. }
  604. }
  605. headerObject.Properties = &newArray
  606. // "So a shorthand for copying array arr would be tmp := append([]int{}, arr...)"
  607. ppArray := make([]*jsonschema.NamedSchema, 0)
  608. ppArray = append(ppArray, *(parameterObject.PatternProperties)...)
  609. headerObject.PatternProperties = &ppArray
  610. }
  611. // generate implied union types
  612. unionTypeKeys := make([]string, 0, len(unionTypes))
  613. for key := range unionTypes {
  614. unionTypeKeys = append(unionTypeKeys, key)
  615. }
  616. sort.Strings(unionTypeKeys)
  617. for _, unionTypeKey := range unionTypeKeys {
  618. unionType := unionTypes[unionTypeKey]
  619. objectSchema := schema.DefinitionWithName(unionType.Name)
  620. if objectSchema == nil {
  621. objectSchema = &jsonschema.Schema{}
  622. oneOf := make([]*jsonschema.Schema, 0)
  623. oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/" + lowerFirst(unionType.ObjectType1))})
  624. oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/" + lowerFirst(unionType.ObjectType2))})
  625. objectSchema.OneOf = &oneOf
  626. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(unionType.Name, objectSchema))
  627. }
  628. }
  629. // generate implied map types
  630. mapTypeKeys := make([]string, 0, len(mapTypes))
  631. for key := range mapTypes {
  632. mapTypeKeys = append(mapTypeKeys, key)
  633. }
  634. sort.Strings(mapTypeKeys)
  635. for _, mapTypeKey := range mapTypeKeys {
  636. mapType := mapTypes[mapTypeKey]
  637. objectSchema := schema.DefinitionWithName(mapType.Name)
  638. if objectSchema == nil {
  639. objectSchema = &jsonschema.Schema{}
  640. objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object")
  641. additionalPropertiesSchema := &jsonschema.Schema{}
  642. if mapType.ObjectType == "string" {
  643. additionalPropertiesSchema.Type = jsonschema.NewStringOrStringArrayWithString("string")
  644. } else {
  645. additionalPropertiesSchema.Ref = stringptr("#/definitions/" + lowerFirst(mapType.ObjectType))
  646. }
  647. objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithSchema(additionalPropertiesSchema)
  648. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(mapType.Name, objectSchema))
  649. }
  650. }
  651. // add schema objects for "object", "any", and "expression"
  652. if true {
  653. objectSchema := &jsonschema.Schema{}
  654. objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object")
  655. objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true)
  656. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("object", objectSchema))
  657. }
  658. if true {
  659. objectSchema := &jsonschema.Schema{}
  660. objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true)
  661. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("any", objectSchema))
  662. }
  663. if true {
  664. objectSchema := &jsonschema.Schema{}
  665. objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object")
  666. objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true)
  667. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("expression", objectSchema))
  668. }
  669. // add schema objects for "specificationExtension"
  670. if true {
  671. objectSchema := &jsonschema.Schema{}
  672. objectSchema.Description = stringptr("Any property starting with x- is valid.")
  673. oneOf := make([]*jsonschema.Schema, 0)
  674. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("null")})
  675. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")})
  676. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")})
  677. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  678. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("object")})
  679. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("array")})
  680. objectSchema.OneOf = &oneOf
  681. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("specificationExtension", objectSchema))
  682. }
  683. // add schema objects for "defaultType"
  684. if true {
  685. objectSchema := &jsonschema.Schema{}
  686. oneOf := make([]*jsonschema.Schema, 0)
  687. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("null")})
  688. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("array")})
  689. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("object")})
  690. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")})
  691. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")})
  692. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  693. objectSchema.OneOf = &oneOf
  694. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("defaultType", objectSchema))
  695. }
  696. // add schema objects for "primitive"
  697. if false { // we don't seem to need these for 3.0 RC2
  698. objectSchema := &jsonschema.Schema{}
  699. oneOf := make([]*jsonschema.Schema, 0)
  700. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")})
  701. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")})
  702. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  703. objectSchema.OneOf = &oneOf
  704. *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("primitive", objectSchema))
  705. }
  706. // force a few more things into the "schema" schema
  707. schemaObject := schema.DefinitionWithName("schema")
  708. schemaObject.CopyOfficialSchemaProperties(
  709. []string{
  710. "title",
  711. "multipleOf",
  712. "maximum",
  713. "exclusiveMaximum",
  714. "minimum",
  715. "exclusiveMinimum",
  716. "maxLength",
  717. "minLength",
  718. "pattern",
  719. "maxItems",
  720. "minItems",
  721. "uniqueItems",
  722. "maxProperties",
  723. "minProperties",
  724. "required",
  725. "enum",
  726. })
  727. schemaObject.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(false)
  728. schemaObject.AddProperty("type", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  729. schemaObject.AddProperty("allOf", arrayOfSchema())
  730. schemaObject.AddProperty("oneOf", arrayOfSchema())
  731. schemaObject.AddProperty("anyOf", arrayOfSchema())
  732. schemaObject.AddProperty("not", &jsonschema.Schema{Ref: stringptr("#/definitions/schema")})
  733. anyOf := make([]*jsonschema.Schema, 0)
  734. anyOf = append(anyOf, &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")})
  735. anyOf = append(anyOf, arrayOfSchema())
  736. schemaObject.AddProperty("items",
  737. &jsonschema.Schema{AnyOf: &anyOf})
  738. schemaObject.AddProperty("properties", &jsonschema.Schema{
  739. Type: jsonschema.NewStringOrStringArrayWithString("object"),
  740. AdditionalProperties: jsonschema.NewSchemaOrBooleanWithSchema(
  741. &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")})})
  742. if true { // add additionalProperties schema object
  743. oneOf := make([]*jsonschema.Schema, 0)
  744. oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")})
  745. oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")})
  746. schemaObject.AddProperty("additionalProperties", &jsonschema.Schema{OneOf: &oneOf})
  747. }
  748. schemaObject.AddProperty("default", &jsonschema.Schema{Ref: stringptr("#/definitions/defaultType")})
  749. schemaObject.AddProperty("description", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  750. schemaObject.AddProperty("format", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")})
  751. // fix the content object
  752. contentObject := schema.DefinitionWithName("content")
  753. if contentObject != nil {
  754. pairs := make([]*jsonschema.NamedSchema, 0)
  755. contentObject.PatternProperties = &pairs
  756. namedSchema := &jsonschema.NamedSchema{Name: "^", Value: &jsonschema.Schema{Ref: stringptr("#/definitions/mediaType")}}
  757. *(contentObject.PatternProperties) = append(*(contentObject.PatternProperties), namedSchema)
  758. }
  759. // write the updated schema
  760. output := schema.JSONString()
  761. err = ioutil.WriteFile("schema.json", []byte(output), 0644)
  762. if err != nil {
  763. panic(err)
  764. }
  765. }