reflect.go 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. // Package jsonschema uses reflection to generate JSON Schemas from Go types [1].
  2. //
  3. // If json tags are present on struct fields, they will be used to infer
  4. // property names and if a property is required (omitempty is present).
  5. //
  6. // [1] http://json-schema.org/latest/json-schema-validation.html
  7. package jsonschema
  8. import (
  9. "bytes"
  10. "encoding/json"
  11. "net"
  12. "net/url"
  13. "reflect"
  14. "strconv"
  15. "strings"
  16. "time"
  17. )
  18. // customSchemaImpl is used to detect if the type provides it's own
  19. // custom Schema Type definition to use instead. Very useful for situations
  20. // where there are custom JSON Marshal and Unmarshal methods.
  21. type customSchemaImpl interface {
  22. JSONSchema() *Schema
  23. }
  24. // Function to be run after the schema has been generated.
  25. // this will let you modify a schema afterwards
  26. type extendSchemaImpl interface {
  27. JSONSchemaExtend(*Schema)
  28. }
  29. // If the object to be reflected defines a `JSONSchemaAlias` method, its type will
  30. // be used instead of the original type.
  31. type aliasSchemaImpl interface {
  32. JSONSchemaAlias() any
  33. }
  34. // If an object to be reflected defines a `JSONSchemaPropertyAlias` method,
  35. // it will be called for each property to determine if another object
  36. // should be used for the contents.
  37. type propertyAliasSchemaImpl interface {
  38. JSONSchemaProperty(prop string) any
  39. }
  40. var customAliasSchema = reflect.TypeOf((*aliasSchemaImpl)(nil)).Elem()
  41. var customPropertyAliasSchema = reflect.TypeOf((*propertyAliasSchemaImpl)(nil)).Elem()
  42. var customType = reflect.TypeOf((*customSchemaImpl)(nil)).Elem()
  43. var extendType = reflect.TypeOf((*extendSchemaImpl)(nil)).Elem()
  44. // customSchemaGetFieldDocString
  45. type customSchemaGetFieldDocString interface {
  46. GetFieldDocString(fieldName string) string
  47. }
  48. type customGetFieldDocString func(fieldName string) string
  49. var customStructGetFieldDocString = reflect.TypeOf((*customSchemaGetFieldDocString)(nil)).Elem()
  50. // Reflect reflects to Schema from a value using the default Reflector
  51. func Reflect(v any) *Schema {
  52. return ReflectFromType(reflect.TypeOf(v))
  53. }
  54. // ReflectFromType generates root schema using the default Reflector
  55. func ReflectFromType(t reflect.Type) *Schema {
  56. r := &Reflector{}
  57. return r.ReflectFromType(t)
  58. }
  59. // A Reflector reflects values into a Schema.
  60. type Reflector struct {
  61. // BaseSchemaID defines the URI that will be used as a base to determine Schema
  62. // IDs for models. For example, a base Schema ID of `https://invopop.com/schemas`
  63. // when defined with a struct called `User{}`, will result in a schema with an
  64. // ID set to `https://invopop.com/schemas/user`.
  65. //
  66. // If no `BaseSchemaID` is provided, we'll take the type's complete package path
  67. // and use that as a base instead. Set `Anonymous` to try if you do not want to
  68. // include a schema ID.
  69. BaseSchemaID ID
  70. // Anonymous when true will hide the auto-generated Schema ID and provide what is
  71. // known as an "anonymous schema". As a rule, this is not recommended.
  72. Anonymous bool
  73. // AssignAnchor when true will use the original struct's name as an anchor inside
  74. // every definition, including the root schema. These can be useful for having a
  75. // reference to the original struct's name in CamelCase instead of the snake-case used
  76. // by default for URI compatibility.
  77. //
  78. // Anchors do not appear to be widely used out in the wild, so at this time the
  79. // anchors themselves will not be used inside generated schema.
  80. AssignAnchor bool
  81. // AllowAdditionalProperties will cause the Reflector to generate a schema
  82. // without additionalProperties set to 'false' for all struct types. This means
  83. // the presence of additional keys in JSON objects will not cause validation
  84. // to fail. Note said additional keys will simply be dropped when the
  85. // validated JSON is unmarshaled.
  86. AllowAdditionalProperties bool
  87. // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema
  88. // that requires any key tagged with `jsonschema:required`, overriding the
  89. // default of requiring any key *not* tagged with `json:,omitempty`.
  90. RequiredFromJSONSchemaTags bool
  91. // Do not reference definitions. This will remove the top-level $defs map and
  92. // instead cause the entire structure of types to be output in one tree. The
  93. // list of type definitions (`$defs`) will not be included.
  94. DoNotReference bool
  95. // ExpandedStruct when true will include the reflected type's definition in the
  96. // root as opposed to a definition with a reference.
  97. ExpandedStruct bool
  98. // FieldNameTag will change the tag used to get field names. json tags are used by default.
  99. FieldNameTag string
  100. // IgnoredTypes defines a slice of types that should be ignored in the schema,
  101. // switching to just allowing additional properties instead.
  102. IgnoredTypes []any
  103. // Lookup allows a function to be defined that will provide a custom mapping of
  104. // types to Schema IDs. This allows existing schema documents to be referenced
  105. // by their ID instead of being embedded into the current schema definitions.
  106. // Reflected types will never be pointers, only underlying elements.
  107. Lookup func(reflect.Type) ID
  108. // Mapper is a function that can be used to map custom Go types to jsonschema schemas.
  109. Mapper func(reflect.Type) *Schema
  110. // Namer allows customizing of type names. The default is to use the type's name
  111. // provided by the reflect package.
  112. Namer func(reflect.Type) string
  113. // KeyNamer allows customizing of key names.
  114. // The default is to use the key's name as is, or the json tag if present.
  115. // If a json tag is present, KeyNamer will receive the tag's name as an argument, not the original key name.
  116. KeyNamer func(string) string
  117. // AdditionalFields allows adding structfields for a given type
  118. AdditionalFields func(reflect.Type) []reflect.StructField
  119. // LookupComment allows customizing comment lookup. Given a reflect.Type and optionally
  120. // a field name, it should return the comment string associated with this type or field.
  121. //
  122. // If the field name is empty, it should return the type's comment; otherwise, the field's
  123. // comment should be returned. If no comment is found, an empty string should be returned.
  124. //
  125. // When set, this function is called before the below CommentMap lookup mechanism. However,
  126. // if it returns an empty string, the CommentMap is still consulted.
  127. LookupComment func(reflect.Type, string) string
  128. // CommentMap is a dictionary of fully qualified go types and fields to comment
  129. // strings that will be used if a description has not already been provided in
  130. // the tags. Types and fields are added to the package path using "." as a
  131. // separator.
  132. //
  133. // Type descriptions should be defined like:
  134. //
  135. // map[string]string{"github.com/invopop/jsonschema.Reflector": "A Reflector reflects values into a Schema."}
  136. //
  137. // And Fields defined as:
  138. //
  139. // map[string]string{"github.com/invopop/jsonschema.Reflector.DoNotReference": "Do not reference definitions."}
  140. //
  141. // See also: AddGoComments, LookupComment
  142. CommentMap map[string]string
  143. }
  144. // Reflect reflects to Schema from a value.
  145. func (r *Reflector) Reflect(v any) *Schema {
  146. return r.ReflectFromType(reflect.TypeOf(v))
  147. }
  148. // ReflectFromType generates root schema
  149. func (r *Reflector) ReflectFromType(t reflect.Type) *Schema {
  150. if t.Kind() == reflect.Ptr {
  151. t = t.Elem() // re-assign from pointer
  152. }
  153. name := r.typeName(t)
  154. s := new(Schema)
  155. definitions := Definitions{}
  156. s.Definitions = definitions
  157. bs := r.reflectTypeToSchemaWithID(definitions, t)
  158. if r.ExpandedStruct {
  159. *s = *definitions[name]
  160. delete(definitions, name)
  161. } else {
  162. *s = *bs
  163. }
  164. // Attempt to set the schema ID
  165. if !r.Anonymous && s.ID == EmptyID {
  166. baseSchemaID := r.BaseSchemaID
  167. if baseSchemaID == EmptyID {
  168. id := ID("https://" + t.PkgPath())
  169. if err := id.Validate(); err == nil {
  170. // it's okay to silently ignore URL errors
  171. baseSchemaID = id
  172. }
  173. }
  174. if baseSchemaID != EmptyID {
  175. s.ID = baseSchemaID.Add(ToSnakeCase(name))
  176. }
  177. }
  178. s.Version = Version
  179. if !r.DoNotReference {
  180. s.Definitions = definitions
  181. }
  182. return s
  183. }
  184. // Available Go defined types for JSON Schema Validation.
  185. // RFC draft-wright-json-schema-validation-00, section 7.3
  186. var (
  187. timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1
  188. ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5
  189. uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6
  190. )
  191. // Byte slices will be encoded as base64
  192. var byteSliceType = reflect.TypeOf([]byte(nil))
  193. // Except for json.RawMessage
  194. var rawMessageType = reflect.TypeOf(json.RawMessage{})
  195. // Go code generated from protobuf enum types should fulfil this interface.
  196. type protoEnum interface {
  197. EnumDescriptor() ([]byte, []int)
  198. }
  199. var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem()
  200. // SetBaseSchemaID is a helper use to be able to set the reflectors base
  201. // schema ID from a string as opposed to then ID instance.
  202. func (r *Reflector) SetBaseSchemaID(id string) {
  203. r.BaseSchemaID = ID(id)
  204. }
  205. func (r *Reflector) refOrReflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema {
  206. id := r.lookupID(t)
  207. if id != EmptyID {
  208. return &Schema{
  209. Ref: id.String(),
  210. }
  211. }
  212. // Already added to definitions?
  213. if def := r.refDefinition(definitions, t); def != nil {
  214. return def
  215. }
  216. return r.reflectTypeToSchemaWithID(definitions, t)
  217. }
  218. func (r *Reflector) reflectTypeToSchemaWithID(defs Definitions, t reflect.Type) *Schema {
  219. s := r.reflectTypeToSchema(defs, t)
  220. if s != nil {
  221. if r.Lookup != nil {
  222. id := r.Lookup(t)
  223. if id != EmptyID {
  224. s.ID = id
  225. }
  226. }
  227. }
  228. return s
  229. }
  230. func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Schema {
  231. // only try to reflect non-pointers
  232. if t.Kind() == reflect.Ptr {
  233. return r.refOrReflectTypeToSchema(definitions, t.Elem())
  234. }
  235. // Check if the there is an alias method that provides an object
  236. // that we should use instead of this one.
  237. if t.Implements(customAliasSchema) {
  238. v := reflect.New(t)
  239. o := v.Interface().(aliasSchemaImpl)
  240. t = reflect.TypeOf(o.JSONSchemaAlias())
  241. return r.refOrReflectTypeToSchema(definitions, t)
  242. }
  243. // Do any pre-definitions exist?
  244. if r.Mapper != nil {
  245. if t := r.Mapper(t); t != nil {
  246. return t
  247. }
  248. }
  249. if rt := r.reflectCustomSchema(definitions, t); rt != nil {
  250. return rt
  251. }
  252. // Prepare a base to which details can be added
  253. st := new(Schema)
  254. // jsonpb will marshal protobuf enum options as either strings or integers.
  255. // It will unmarshal either.
  256. if t.Implements(protoEnumType) {
  257. st.OneOf = []*Schema{
  258. {Type: "string"},
  259. {Type: "integer"},
  260. }
  261. return st
  262. }
  263. // Defined format types for JSON Schema Validation
  264. // RFC draft-wright-json-schema-validation-00, section 7.3
  265. // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7
  266. if t == ipType {
  267. // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5
  268. st.Type = "string"
  269. st.Format = "ipv4"
  270. return st
  271. }
  272. switch t.Kind() {
  273. case reflect.Struct:
  274. r.reflectStruct(definitions, t, st)
  275. case reflect.Slice, reflect.Array:
  276. r.reflectSliceOrArray(definitions, t, st)
  277. case reflect.Map:
  278. r.reflectMap(definitions, t, st)
  279. case reflect.Interface:
  280. // empty
  281. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  282. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  283. st.Type = "integer"
  284. case reflect.Float32, reflect.Float64:
  285. st.Type = "number"
  286. case reflect.Bool:
  287. st.Type = "boolean"
  288. case reflect.String:
  289. st.Type = "string"
  290. default:
  291. panic("unsupported type " + t.String())
  292. }
  293. r.reflectSchemaExtend(definitions, t, st)
  294. // Always try to reference the definition which may have just been created
  295. if def := r.refDefinition(definitions, t); def != nil {
  296. return def
  297. }
  298. return st
  299. }
  300. func (r *Reflector) reflectCustomSchema(definitions Definitions, t reflect.Type) *Schema {
  301. if t.Kind() == reflect.Ptr {
  302. return r.reflectCustomSchema(definitions, t.Elem())
  303. }
  304. if t.Implements(customType) {
  305. v := reflect.New(t)
  306. o := v.Interface().(customSchemaImpl)
  307. st := o.JSONSchema()
  308. r.addDefinition(definitions, t, st)
  309. if ref := r.refDefinition(definitions, t); ref != nil {
  310. return ref
  311. }
  312. return st
  313. }
  314. return nil
  315. }
  316. func (r *Reflector) reflectSchemaExtend(definitions Definitions, t reflect.Type, s *Schema) *Schema {
  317. if t.Implements(extendType) {
  318. v := reflect.New(t)
  319. o := v.Interface().(extendSchemaImpl)
  320. o.JSONSchemaExtend(s)
  321. if ref := r.refDefinition(definitions, t); ref != nil {
  322. return ref
  323. }
  324. }
  325. return s
  326. }
  327. func (r *Reflector) reflectSliceOrArray(definitions Definitions, t reflect.Type, st *Schema) {
  328. if t == rawMessageType {
  329. return
  330. }
  331. r.addDefinition(definitions, t, st)
  332. if st.Description == "" {
  333. st.Description = r.lookupComment(t, "")
  334. }
  335. if t.Kind() == reflect.Array {
  336. l := uint64(t.Len())
  337. st.MinItems = &l
  338. st.MaxItems = &l
  339. }
  340. if t.Kind() == reflect.Slice && t.Elem() == byteSliceType.Elem() {
  341. st.Type = "string"
  342. // NOTE: ContentMediaType is not set here
  343. st.ContentEncoding = "base64"
  344. } else {
  345. st.Type = "array"
  346. st.Items = r.refOrReflectTypeToSchema(definitions, t.Elem())
  347. }
  348. }
  349. func (r *Reflector) reflectMap(definitions Definitions, t reflect.Type, st *Schema) {
  350. r.addDefinition(definitions, t, st)
  351. st.Type = "object"
  352. if st.Description == "" {
  353. st.Description = r.lookupComment(t, "")
  354. }
  355. switch t.Key().Kind() {
  356. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
  357. st.PatternProperties = map[string]*Schema{
  358. "^[0-9]+$": r.refOrReflectTypeToSchema(definitions, t.Elem()),
  359. }
  360. st.AdditionalProperties = FalseSchema
  361. return
  362. }
  363. if t.Elem().Kind() != reflect.Interface {
  364. st.AdditionalProperties = r.refOrReflectTypeToSchema(definitions, t.Elem())
  365. }
  366. }
  367. // Reflects a struct to a JSON Schema type.
  368. func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type, s *Schema) {
  369. // Handle special types
  370. switch t {
  371. case timeType: // date-time RFC section 7.3.1
  372. s.Type = "string"
  373. s.Format = "date-time"
  374. return
  375. case uriType: // uri RFC section 7.3.6
  376. s.Type = "string"
  377. s.Format = "uri"
  378. return
  379. }
  380. r.addDefinition(definitions, t, s)
  381. s.Type = "object"
  382. s.Properties = NewProperties()
  383. s.Description = r.lookupComment(t, "")
  384. if r.AssignAnchor {
  385. s.Anchor = t.Name()
  386. }
  387. if !r.AllowAdditionalProperties && s.AdditionalProperties == nil {
  388. s.AdditionalProperties = FalseSchema
  389. }
  390. ignored := false
  391. for _, it := range r.IgnoredTypes {
  392. if reflect.TypeOf(it) == t {
  393. ignored = true
  394. break
  395. }
  396. }
  397. if !ignored {
  398. r.reflectStructFields(s, definitions, t)
  399. }
  400. }
  401. func (r *Reflector) reflectStructFields(st *Schema, definitions Definitions, t reflect.Type) {
  402. if t.Kind() == reflect.Ptr {
  403. t = t.Elem()
  404. }
  405. if t.Kind() != reflect.Struct {
  406. return
  407. }
  408. var getFieldDocString customGetFieldDocString
  409. if t.Implements(customStructGetFieldDocString) {
  410. v := reflect.New(t)
  411. o := v.Interface().(customSchemaGetFieldDocString)
  412. getFieldDocString = o.GetFieldDocString
  413. }
  414. customPropertyMethod := func(string) any {
  415. return nil
  416. }
  417. if t.Implements(customPropertyAliasSchema) {
  418. v := reflect.New(t)
  419. o := v.Interface().(propertyAliasSchemaImpl)
  420. customPropertyMethod = o.JSONSchemaProperty
  421. }
  422. handleField := func(f reflect.StructField) {
  423. name, shouldEmbed, required, nullable := r.reflectFieldName(f)
  424. // if anonymous and exported type should be processed recursively
  425. // current type should inherit properties of anonymous one
  426. if name == "" {
  427. if shouldEmbed {
  428. r.reflectStructFields(st, definitions, f.Type)
  429. }
  430. return
  431. }
  432. // If a JSONSchemaAlias(prop string) method is defined, attempt to use
  433. // the provided object's type instead of the field's type.
  434. var property *Schema
  435. if alias := customPropertyMethod(name); alias != nil {
  436. property = r.refOrReflectTypeToSchema(definitions, reflect.TypeOf(alias))
  437. } else {
  438. property = r.refOrReflectTypeToSchema(definitions, f.Type)
  439. }
  440. property.structKeywordsFromTags(f, st, name)
  441. if property.Description == "" {
  442. property.Description = r.lookupComment(t, f.Name)
  443. }
  444. if getFieldDocString != nil {
  445. property.Description = getFieldDocString(f.Name)
  446. }
  447. if nullable {
  448. property = &Schema{
  449. OneOf: []*Schema{
  450. property,
  451. {
  452. Type: "null",
  453. },
  454. },
  455. }
  456. }
  457. st.Properties.Set(name, property)
  458. if required {
  459. st.Required = appendUniqueString(st.Required, name)
  460. }
  461. }
  462. for i := 0; i < t.NumField(); i++ {
  463. f := t.Field(i)
  464. handleField(f)
  465. }
  466. if r.AdditionalFields != nil {
  467. if af := r.AdditionalFields(t); af != nil {
  468. for _, sf := range af {
  469. handleField(sf)
  470. }
  471. }
  472. }
  473. }
  474. func appendUniqueString(base []string, value string) []string {
  475. for _, v := range base {
  476. if v == value {
  477. return base
  478. }
  479. }
  480. return append(base, value)
  481. }
  482. // addDefinition will append the provided schema. If needed, an ID and anchor will also be added.
  483. func (r *Reflector) addDefinition(definitions Definitions, t reflect.Type, s *Schema) {
  484. name := r.typeName(t)
  485. if name == "" {
  486. return
  487. }
  488. definitions[name] = s
  489. }
  490. // refDefinition will provide a schema with a reference to an existing definition.
  491. func (r *Reflector) refDefinition(definitions Definitions, t reflect.Type) *Schema {
  492. if r.DoNotReference {
  493. return nil
  494. }
  495. name := r.typeName(t)
  496. if name == "" {
  497. return nil
  498. }
  499. if _, ok := definitions[name]; !ok {
  500. return nil
  501. }
  502. return &Schema{
  503. Ref: "#/$defs/" + name,
  504. }
  505. }
  506. func (r *Reflector) lookupID(t reflect.Type) ID {
  507. if r.Lookup != nil {
  508. if t.Kind() == reflect.Ptr {
  509. t = t.Elem()
  510. }
  511. return r.Lookup(t)
  512. }
  513. return EmptyID
  514. }
  515. func (t *Schema) structKeywordsFromTags(f reflect.StructField, parent *Schema, propertyName string) {
  516. t.Description = f.Tag.Get("jsonschema_description")
  517. tags := splitOnUnescapedCommas(f.Tag.Get("jsonschema"))
  518. tags = t.genericKeywords(tags, parent, propertyName)
  519. switch t.Type {
  520. case "string":
  521. t.stringKeywords(tags)
  522. case "number":
  523. t.numericalKeywords(tags)
  524. case "integer":
  525. t.numericalKeywords(tags)
  526. case "array":
  527. t.arrayKeywords(tags)
  528. case "boolean":
  529. t.booleanKeywords(tags)
  530. }
  531. extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",")
  532. t.extraKeywords(extras)
  533. }
  534. // read struct tags for generic keywords
  535. func (t *Schema) genericKeywords(tags []string, parent *Schema, propertyName string) []string { //nolint:gocyclo
  536. unprocessed := make([]string, 0, len(tags))
  537. for _, tag := range tags {
  538. nameValue := strings.SplitN(tag, "=", 2)
  539. if len(nameValue) == 2 {
  540. name, val := nameValue[0], nameValue[1]
  541. switch name {
  542. case "title":
  543. t.Title = val
  544. case "description":
  545. t.Description = val
  546. case "type":
  547. t.Type = val
  548. case "anchor":
  549. t.Anchor = val
  550. case "oneof_required":
  551. var typeFound *Schema
  552. for i := range parent.OneOf {
  553. if parent.OneOf[i].Title == nameValue[1] {
  554. typeFound = parent.OneOf[i]
  555. }
  556. }
  557. if typeFound == nil {
  558. typeFound = &Schema{
  559. Title: nameValue[1],
  560. Required: []string{},
  561. }
  562. parent.OneOf = append(parent.OneOf, typeFound)
  563. }
  564. typeFound.Required = append(typeFound.Required, propertyName)
  565. case "anyof_required":
  566. var typeFound *Schema
  567. for i := range parent.AnyOf {
  568. if parent.AnyOf[i].Title == nameValue[1] {
  569. typeFound = parent.AnyOf[i]
  570. }
  571. }
  572. if typeFound == nil {
  573. typeFound = &Schema{
  574. Title: nameValue[1],
  575. Required: []string{},
  576. }
  577. parent.AnyOf = append(parent.AnyOf, typeFound)
  578. }
  579. typeFound.Required = append(typeFound.Required, propertyName)
  580. case "oneof_ref":
  581. subSchema := t
  582. if t.Items != nil {
  583. subSchema = t.Items
  584. }
  585. if subSchema.OneOf == nil {
  586. subSchema.OneOf = make([]*Schema, 0, 1)
  587. }
  588. subSchema.Ref = ""
  589. refs := strings.Split(nameValue[1], ";")
  590. for _, r := range refs {
  591. subSchema.OneOf = append(subSchema.OneOf, &Schema{
  592. Ref: r,
  593. })
  594. }
  595. case "oneof_type":
  596. if t.OneOf == nil {
  597. t.OneOf = make([]*Schema, 0, 1)
  598. }
  599. t.Type = ""
  600. types := strings.Split(nameValue[1], ";")
  601. for _, ty := range types {
  602. t.OneOf = append(t.OneOf, &Schema{
  603. Type: ty,
  604. })
  605. }
  606. case "anyof_ref":
  607. subSchema := t
  608. if t.Items != nil {
  609. subSchema = t.Items
  610. }
  611. if subSchema.AnyOf == nil {
  612. subSchema.AnyOf = make([]*Schema, 0, 1)
  613. }
  614. subSchema.Ref = ""
  615. refs := strings.Split(nameValue[1], ";")
  616. for _, r := range refs {
  617. subSchema.AnyOf = append(subSchema.AnyOf, &Schema{
  618. Ref: r,
  619. })
  620. }
  621. case "anyof_type":
  622. if t.AnyOf == nil {
  623. t.AnyOf = make([]*Schema, 0, 1)
  624. }
  625. t.Type = ""
  626. types := strings.Split(nameValue[1], ";")
  627. for _, ty := range types {
  628. t.AnyOf = append(t.AnyOf, &Schema{
  629. Type: ty,
  630. })
  631. }
  632. default:
  633. unprocessed = append(unprocessed, tag)
  634. }
  635. }
  636. }
  637. return unprocessed
  638. }
  639. // read struct tags for boolean type keywords
  640. func (t *Schema) booleanKeywords(tags []string) {
  641. for _, tag := range tags {
  642. nameValue := strings.Split(tag, "=")
  643. if len(nameValue) != 2 {
  644. continue
  645. }
  646. name, val := nameValue[0], nameValue[1]
  647. if name == "default" {
  648. if val == "true" {
  649. t.Default = true
  650. } else if val == "false" {
  651. t.Default = false
  652. }
  653. }
  654. }
  655. }
  656. // read struct tags for string type keywords
  657. func (t *Schema) stringKeywords(tags []string) {
  658. for _, tag := range tags {
  659. nameValue := strings.SplitN(tag, "=", 2)
  660. if len(nameValue) == 2 {
  661. name, val := nameValue[0], nameValue[1]
  662. switch name {
  663. case "minLength":
  664. t.MinLength = parseUint(val)
  665. case "maxLength":
  666. t.MaxLength = parseUint(val)
  667. case "pattern":
  668. t.Pattern = val
  669. case "format":
  670. t.Format = val
  671. case "readOnly":
  672. i, _ := strconv.ParseBool(val)
  673. t.ReadOnly = i
  674. case "writeOnly":
  675. i, _ := strconv.ParseBool(val)
  676. t.WriteOnly = i
  677. case "default":
  678. t.Default = val
  679. case "example":
  680. t.Examples = append(t.Examples, val)
  681. case "enum":
  682. t.Enum = append(t.Enum, val)
  683. }
  684. }
  685. }
  686. }
  687. // read struct tags for numerical type keywords
  688. func (t *Schema) numericalKeywords(tags []string) {
  689. for _, tag := range tags {
  690. nameValue := strings.Split(tag, "=")
  691. if len(nameValue) == 2 {
  692. name, val := nameValue[0], nameValue[1]
  693. switch name {
  694. case "multipleOf":
  695. t.MultipleOf, _ = toJSONNumber(val)
  696. case "minimum":
  697. t.Minimum, _ = toJSONNumber(val)
  698. case "maximum":
  699. t.Maximum, _ = toJSONNumber(val)
  700. case "exclusiveMaximum":
  701. t.ExclusiveMaximum, _ = toJSONNumber(val)
  702. case "exclusiveMinimum":
  703. t.ExclusiveMinimum, _ = toJSONNumber(val)
  704. case "default":
  705. if num, ok := toJSONNumber(val); ok {
  706. t.Default = num
  707. }
  708. case "example":
  709. if num, ok := toJSONNumber(val); ok {
  710. t.Examples = append(t.Examples, num)
  711. }
  712. case "enum":
  713. if num, ok := toJSONNumber(val); ok {
  714. t.Enum = append(t.Enum, num)
  715. }
  716. }
  717. }
  718. }
  719. }
  720. // read struct tags for object type keywords
  721. // func (t *Type) objectKeywords(tags []string) {
  722. // for _, tag := range tags{
  723. // nameValue := strings.Split(tag, "=")
  724. // name, val := nameValue[0], nameValue[1]
  725. // switch name{
  726. // case "dependencies":
  727. // t.Dependencies = val
  728. // break;
  729. // case "patternProperties":
  730. // t.PatternProperties = val
  731. // break;
  732. // }
  733. // }
  734. // }
  735. // read struct tags for array type keywords
  736. func (t *Schema) arrayKeywords(tags []string) {
  737. var defaultValues []any
  738. unprocessed := make([]string, 0, len(tags))
  739. for _, tag := range tags {
  740. nameValue := strings.Split(tag, "=")
  741. if len(nameValue) == 2 {
  742. name, val := nameValue[0], nameValue[1]
  743. switch name {
  744. case "minItems":
  745. t.MinItems = parseUint(val)
  746. case "maxItems":
  747. t.MaxItems = parseUint(val)
  748. case "uniqueItems":
  749. t.UniqueItems = true
  750. case "default":
  751. defaultValues = append(defaultValues, val)
  752. case "format":
  753. t.Items.Format = val
  754. case "pattern":
  755. t.Items.Pattern = val
  756. default:
  757. unprocessed = append(unprocessed, tag) // left for further processing by underlying type
  758. }
  759. }
  760. }
  761. if len(defaultValues) > 0 {
  762. t.Default = defaultValues
  763. }
  764. if len(unprocessed) == 0 {
  765. // we don't have anything else to process
  766. return
  767. }
  768. switch t.Items.Type {
  769. case "string":
  770. t.Items.stringKeywords(unprocessed)
  771. case "number":
  772. t.Items.numericalKeywords(unprocessed)
  773. case "integer":
  774. t.Items.numericalKeywords(unprocessed)
  775. case "array":
  776. // explicitly don't support traversal for the [][]..., as it's unclear where the array tags belong
  777. case "boolean":
  778. t.Items.booleanKeywords(unprocessed)
  779. }
  780. }
  781. func (t *Schema) extraKeywords(tags []string) {
  782. for _, tag := range tags {
  783. nameValue := strings.SplitN(tag, "=", 2)
  784. if len(nameValue) == 2 {
  785. t.setExtra(nameValue[0], nameValue[1])
  786. }
  787. }
  788. }
  789. func (t *Schema) setExtra(key, val string) {
  790. if t.Extras == nil {
  791. t.Extras = map[string]any{}
  792. }
  793. if existingVal, ok := t.Extras[key]; ok {
  794. switch existingVal := existingVal.(type) {
  795. case string:
  796. t.Extras[key] = []string{existingVal, val}
  797. case []string:
  798. t.Extras[key] = append(existingVal, val)
  799. case int:
  800. t.Extras[key], _ = strconv.Atoi(val)
  801. case bool:
  802. t.Extras[key] = (val == "true" || val == "t")
  803. }
  804. } else {
  805. switch key {
  806. case "minimum":
  807. t.Extras[key], _ = strconv.Atoi(val)
  808. default:
  809. var x any
  810. if val == "true" {
  811. x = true
  812. } else if val == "false" {
  813. x = false
  814. } else {
  815. x = val
  816. }
  817. t.Extras[key] = x
  818. }
  819. }
  820. }
  821. func requiredFromJSONTags(tags []string, val *bool) {
  822. if ignoredByJSONTags(tags) {
  823. return
  824. }
  825. for _, tag := range tags[1:] {
  826. if tag == "omitempty" {
  827. *val = false
  828. return
  829. }
  830. }
  831. *val = true
  832. }
  833. func requiredFromJSONSchemaTags(tags []string, val *bool) {
  834. if ignoredByJSONSchemaTags(tags) {
  835. return
  836. }
  837. for _, tag := range tags {
  838. if tag == "required" {
  839. *val = true
  840. }
  841. }
  842. }
  843. func nullableFromJSONSchemaTags(tags []string) bool {
  844. if ignoredByJSONSchemaTags(tags) {
  845. return false
  846. }
  847. for _, tag := range tags {
  848. if tag == "nullable" {
  849. return true
  850. }
  851. }
  852. return false
  853. }
  854. func ignoredByJSONTags(tags []string) bool {
  855. return tags[0] == "-"
  856. }
  857. func ignoredByJSONSchemaTags(tags []string) bool {
  858. return tags[0] == "-"
  859. }
  860. func inlinedByJSONTags(tags []string) bool {
  861. for _, tag := range tags[1:] {
  862. if tag == "inline" {
  863. return true
  864. }
  865. }
  866. return false
  867. }
  868. // toJSONNumber converts string to *json.Number.
  869. // It'll aso return whether the number is valid.
  870. func toJSONNumber(s string) (json.Number, bool) {
  871. num := json.Number(s)
  872. if _, err := num.Int64(); err == nil {
  873. return num, true
  874. }
  875. if _, err := num.Float64(); err == nil {
  876. return num, true
  877. }
  878. return json.Number(""), false
  879. }
  880. func parseUint(num string) *uint64 {
  881. val, err := strconv.ParseUint(num, 10, 64)
  882. if err != nil {
  883. return nil
  884. }
  885. return &val
  886. }
  887. func (r *Reflector) fieldNameTag() string {
  888. if r.FieldNameTag != "" {
  889. return r.FieldNameTag
  890. }
  891. return "json"
  892. }
  893. func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool, bool) {
  894. jsonTagString := f.Tag.Get(r.fieldNameTag())
  895. jsonTags := strings.Split(jsonTagString, ",")
  896. if ignoredByJSONTags(jsonTags) {
  897. return "", false, false, false
  898. }
  899. schemaTags := strings.Split(f.Tag.Get("jsonschema"), ",")
  900. if ignoredByJSONSchemaTags(schemaTags) {
  901. return "", false, false, false
  902. }
  903. var required bool
  904. if !r.RequiredFromJSONSchemaTags {
  905. requiredFromJSONTags(jsonTags, &required)
  906. }
  907. requiredFromJSONSchemaTags(schemaTags, &required)
  908. nullable := nullableFromJSONSchemaTags(schemaTags)
  909. if f.Anonymous && jsonTags[0] == "" {
  910. // As per JSON Marshal rules, anonymous structs are inherited
  911. if f.Type.Kind() == reflect.Struct {
  912. return "", true, false, false
  913. }
  914. // As per JSON Marshal rules, anonymous pointer to structs are inherited
  915. if f.Type.Kind() == reflect.Ptr && f.Type.Elem().Kind() == reflect.Struct {
  916. return "", true, false, false
  917. }
  918. }
  919. // As per JSON Marshal rules, inline nested structs that have `inline` tag.
  920. if inlinedByJSONTags(jsonTags) {
  921. return "", true, false, false
  922. }
  923. // Try to determine the name from the different combos
  924. name := f.Name
  925. if jsonTags[0] != "" {
  926. name = jsonTags[0]
  927. }
  928. if !f.Anonymous && f.PkgPath != "" {
  929. // field not anonymous and not export has no export name
  930. name = ""
  931. } else if r.KeyNamer != nil {
  932. name = r.KeyNamer(name)
  933. }
  934. return name, false, required, nullable
  935. }
  936. // UnmarshalJSON is used to parse a schema object or boolean.
  937. func (t *Schema) UnmarshalJSON(data []byte) error {
  938. if bytes.Equal(data, []byte("true")) {
  939. *t = *TrueSchema
  940. return nil
  941. } else if bytes.Equal(data, []byte("false")) {
  942. *t = *FalseSchema
  943. return nil
  944. }
  945. type SchemaAlt Schema
  946. aux := &struct {
  947. *SchemaAlt
  948. }{
  949. SchemaAlt: (*SchemaAlt)(t),
  950. }
  951. return json.Unmarshal(data, aux)
  952. }
  953. // MarshalJSON is used to serialize a schema object or boolean.
  954. func (t *Schema) MarshalJSON() ([]byte, error) {
  955. if t.boolean != nil {
  956. if *t.boolean {
  957. return []byte("true"), nil
  958. }
  959. return []byte("false"), nil
  960. }
  961. if reflect.DeepEqual(&Schema{}, t) {
  962. // Don't bother returning empty schemas
  963. return []byte("true"), nil
  964. }
  965. type SchemaAlt Schema
  966. b, err := json.Marshal((*SchemaAlt)(t))
  967. if err != nil {
  968. return nil, err
  969. }
  970. if len(t.Extras) == 0 {
  971. return b, nil
  972. }
  973. m, err := json.Marshal(t.Extras)
  974. if err != nil {
  975. return nil, err
  976. }
  977. if len(b) == 2 {
  978. return m, nil
  979. }
  980. b[len(b)-1] = ','
  981. return append(b, m[1:]...), nil
  982. }
  983. func (r *Reflector) typeName(t reflect.Type) string {
  984. if r.Namer != nil {
  985. if name := r.Namer(t); name != "" {
  986. return name
  987. }
  988. }
  989. return t.Name()
  990. }
  991. // Split on commas that are not preceded by `\`.
  992. // This way, we prevent splitting regexes
  993. func splitOnUnescapedCommas(tagString string) []string {
  994. ret := make([]string, 0)
  995. separated := strings.Split(tagString, ",")
  996. ret = append(ret, separated[0])
  997. i := 0
  998. for _, nextTag := range separated[1:] {
  999. if len(ret[i]) == 0 {
  1000. ret = append(ret, nextTag)
  1001. i++
  1002. continue
  1003. }
  1004. if ret[i][len(ret[i])-1] == '\\' {
  1005. ret[i] = ret[i][:len(ret[i])-1] + "," + nextTag
  1006. } else {
  1007. ret = append(ret, nextTag)
  1008. i++
  1009. }
  1010. }
  1011. return ret
  1012. }
  1013. func fullyQualifiedTypeName(t reflect.Type) string {
  1014. return t.PkgPath() + "." + t.Name()
  1015. }