swagger_yard 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e69ba8fc51e7c39ca9291c1e2917f28b5ed794fe
4
- data.tar.gz: f00801cc7268c6faf6d1fdc22c8d9b14ccf55756
2
+ SHA256:
3
+ metadata.gz: 92fee53b6f39a4a44e733619571d5c051054df3e7721058b50d6f18654898d73
4
+ data.tar.gz: 93e2c4acd9fb8c523a39c916257e41a5c6426e5033439f814223d91397317606
5
5
  SHA512:
6
- metadata.gz: baa76a8eccd08176c057fb6d6969001899350d7f0ce8b5cf25ab00cd29d42bf81e554440e5517a1dd517c1bb4952f8deb6143127442e208f91c181825fec6faa
7
- data.tar.gz: 7ddf6347f3a340ab921f0f576146fcd3709f8adbcbe049319ab63137fc156779998369f68b1612b97138434fccf9ae8198dde34b19e6463bc713df9d6038d375
6
+ metadata.gz: 23e2f8c39bdaf2a8009dc7f78b4a8f589da25737fa4cb3e77ab89395abe84459a52106d85aa026b533bbefdfe646cfa9c7fc7d9ede512be5edd4e242ada164ba
7
+ data.tar.gz: b893eddf59099d0fc2cba25486d5e7b2073b2386d3e0805addf7f7d2b280ccdd165038cd5323ab5315ecd8ac7a5ad07b0b6541471c6c14f7ff5890309de313cf
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # SwaggerYard [![Build Status](https://travis-ci.org/livingsocial/swagger_yard.svg?branch=master)](https://travis-ci.org/livingsocial/swagger_yard) #
1
+ # SwaggerYard [![Build Status](https://github.com/livingsocial/swagger_yard/actions/workflows/rspec.yml/badge.svg)](https://github.com/livingsocial/swagger_yard/actions/workflows/rspec.yml)
2
2
 
3
3
  SwaggerYard is a gem to convert custom YARD tags in comments into Swagger 2.0 or OpenAPI 3.0.0 specs.
4
4
 
@@ -41,6 +41,9 @@ Then start to annotate controllers and models as described below.
41
41
  To generate a Swagger or OpenAPI specification, use one of the `SwaggerYard::Swagger` or `SwaggerYard::OpenAPI` classes as follows in a script or Rake task (or use [swagger_yard-rails](/livingsocial/swagger_yard-rails)):
42
42
 
43
43
  ```
44
+ # Register the yard tags
45
+ SwaggerYard.register_custom_yard_tags!
46
+
44
47
  spec = SwaggerYard::OpenAPI.new
45
48
  # Generate YAML
46
49
  File.open("openapi.yml", "w") { |f| f << YAML.dump(spec.to_h) }
@@ -107,7 +110,7 @@ end
107
110
 
108
111
  ### Model ###
109
112
 
110
- Each model to be exposed in the specification must have a `@model` tag. Model properties are specified with `@property` tags.
113
+ Each model to be exposed in the specification must have a `@model` tag. Model properties are specified with `@property` tags. JSON Schema `additionalProperties` can be specified with `@additional_properties <type>` where `<type>` is any type defined elsewhere, or simply `false` to denote a closed model (`additionalProperties: false`).
111
114
 
112
115
  ```ruby
113
116
  #
@@ -117,6 +120,7 @@ Each model to be exposed in the specification must have a `@model` tag. Model pr
117
120
  # @property name [Array<string>] the names for the pet
118
121
  # @property age [integer] the age of the pet
119
122
  # @property relatives(required) [Array<Pet>] other Pets in its family
123
+ # @additional_properties string
120
124
  #
121
125
  class Pet
122
126
  end
@@ -172,10 +176,13 @@ Types of things (parameters or responses of an operation, properties of a model)
172
176
  - Basic types (integer, boolean, string, object, number, date, time, date-time, uuid, etc.) should be lowercased.
173
177
  - An array of models or basic types is specified with `[array<...>]`.
174
178
  - An enum of allowed string values is specified with `[enum<one,two,three>]`.
179
+ - An enum of allowed values that are defined in the application `[enum<{CURRENCIES}>]`.
175
180
  - An object definition can include the property definitions of its fields, and / or of an additional property for any remaining allowed fields. E.g., `[object<name: string, age: integer, string >]`
176
181
  - Structured data like objects, arrays, pairs, etc., definitions can also be nested; E.g., `[object<pairs:array<object<right:integer,left:integer>>>]`
177
182
  - JSON-Schema `format` attributes can be specified for basic types using `<...>`. For example, `[integer<int64>]` produces JSON `{ "type": "integer", "format": "int64" }`.
178
183
  - Regex pattern constraints can be specified for strings using `[regex<PATTERN>]`. For example, `[regex<^.{3}$>]` produces JSON `{ "type": "string", "pattern": "^.{3}$" }`.
184
+ - A union of two or more sub-types is expressed as `(A | B)` (parentheses required). This translates to `oneOf:` in JSON Schema.
185
+ - An intersection of two or more sub-types is expressed as `(A & B)` (parentheses required). This translates to `allOf:` in JSON Schema.
179
186
 
180
187
  ### Options ###
181
188
 
@@ -258,6 +265,24 @@ class Person
258
265
  end
259
266
  ```
260
267
 
268
+ ### Standalone Model ###
269
+
270
+ Types can be specified without being associated to an existing model with the `@!model` directive. It is useful when documenting a create and an update of the same class:
271
+
272
+ ```ruby
273
+ # @!model CreatePet
274
+ # @property id(required) [integer]
275
+ # @property name(required) [string]
276
+ #
277
+ # @!model UpdatePet
278
+ # @property id(required) [integer]
279
+ # @property name [string]
280
+ ```
281
+
282
+ It can also be needed when the body parameter of a path is not totally matching a model.
283
+
284
+ Note that a model name must be given to the directive.
285
+
261
286
 
262
287
  ### External Schema ###
263
288
 
@@ -0,0 +1,32 @@
1
+ module SwaggerYard::Directives
2
+
3
+ # A directive used to create a model tag with a dummy class.
4
+ # based on https://github.com/lsegal/yard/blob/master/lib/yard/tags/directives.rb#L361
5
+ class ParamClassDirective < YARD::Tags::Directive
6
+
7
+ def call; end
8
+
9
+ def after_parse
10
+ return unless handler
11
+
12
+ create_object
13
+ end
14
+
15
+ def create_object
16
+ name = tag.name
17
+ obj = YARD::CodeObjects::ClassObject.new(handler.namespace, tag.name)
18
+ handler.register_file_info(obj)
19
+ handler.register_source(obj)
20
+ handler.register_group(obj)
21
+ obj.docstring = YARD::Docstring.new!(parser.text,
22
+ parser.tags,
23
+ obj,
24
+ nil,
25
+ parser.reference)
26
+ obj.add_tag(YARD::Tags::Tag.new(:model, name))
27
+ parser.object = obj
28
+ parser.post_process
29
+ obj
30
+ end
31
+ end
32
+ end
@@ -5,7 +5,8 @@ module SwaggerYard
5
5
  #
6
6
  class Model
7
7
  include Example
8
- attr_reader :id, :discriminator, :inherits, :description, :properties
8
+ attr_reader :id, :discriminator, :inherits,
9
+ :description, :properties, :additional_properties
9
10
 
10
11
  def self.from_yard_object(yard_object)
11
12
  new.tap do |model|
@@ -41,8 +42,12 @@ module SwaggerYard
41
42
  properties.detect {|prop| prop.name == key }
42
43
  end
43
44
 
45
+ TAG_ORDER = %w(model inherits discriminator property example additional_properties)
46
+
44
47
  def parse_tags(tags)
45
- tags.each do |tag|
48
+ sorted_tags = tags.each_with_index.sort_by { |t,i|
49
+ [TAG_ORDER.index(t.tag_name), i] }.map(&:first)
50
+ sorted_tags.each do |tag|
46
51
  case tag.tag_name
47
52
  when "model"
48
53
  @has_model_tag = true
@@ -63,11 +68,13 @@ module SwaggerYard
63
68
  if (prop = property(tag.name))
64
69
  prop.example = tag.text
65
70
  else
66
- SwaggerYard.log.warn("no property '#{tag.name}' defined yet to which to attach example: #{value.inspect}")
71
+ SwaggerYard.log.warn("no property '#{tag.name}' defined yet to which to attach example: #{tag.text.inspect}")
67
72
  end
68
73
  else
69
74
  self.example = tag.text
70
75
  end
76
+ when "additional_properties"
77
+ @additional_properties = Type.new(tag.text).schema
71
78
  end
72
79
  end
73
80
 
@@ -158,6 +158,7 @@ module SwaggerYard
158
158
 
159
159
  h["example"] = mod.example if mod.example
160
160
 
161
+ h["additionalProperties"] = mod.additional_properties if !mod.additional_properties.nil?
161
162
  h
162
163
  end
163
164
 
@@ -24,12 +24,16 @@ module SwaggerYard
24
24
 
25
25
  rule(:identifier) { name >> (str('::') >> name).repeat }
26
26
 
27
+ rule(:constant) { spaced('{') >> identifier.as(:constant) >> spaced('}') }
28
+
27
29
  rule(:external_identifier) { name.as(:namespace) >> str('#') >> identifier.as(:identifier) }
28
30
 
31
+ rule(:_false) { str('false').as(:false) }
32
+
29
33
  rule(:regexp) { stri('regex') >> match['Pp'].maybe >> space >>
30
34
  str('<') >> (str('\\\\') | str('\\>') | match['[^>]']).repeat.as(:regexp) >> str('>') }
31
35
 
32
- rule(:enum_list) { name.as(:value) >> (spaced(',') >> name.as(:value)).repeat }
36
+ rule(:enum_list) { (name.as(:value) | constant) >> (spaced(',') >> (name.as(:value) | constant)).repeat }
33
37
 
34
38
  rule(:enum) { stri('enum') >> spaced('<') >> enum_list >> spaced('>') }
35
39
 
@@ -43,10 +47,17 @@ module SwaggerYard
43
47
 
44
48
  rule(:formatted) { name.as(:name) >> spaced('<') >> name.as(:format) >> spaced('>') }
45
49
 
50
+ rule(:union) { spaced('(') >> type >> (spaced('|') >> type).repeat >> spaced(')') }
51
+
52
+ rule(:intersect) { spaced('(') >> type >> (spaced('&') >> type).repeat >> spaced(')') }
53
+
46
54
  rule(:type) { enum.as(:enum) |
47
55
  array.as(:array) |
48
56
  object.as(:object) |
49
57
  formatted.as(:formatted) |
58
+ union.as(:union) |
59
+ intersect.as(:intersect) |
60
+ _false |
50
61
  external_identifier.as(:external_identifier) |
51
62
  identifier.as(:identifier) |
52
63
  regexp }
@@ -58,9 +69,9 @@ module SwaggerYard
58
69
  rule(identifier: simple(:id)) do
59
70
  v = id.to_s
60
71
  case v
61
- when /array/i
72
+ when /^array$/i
62
73
  { 'type' => 'array', 'items' => { 'type' => 'string' } }
63
- when /object/i
74
+ when /^object$/i
64
75
  { 'type' => 'object' }
65
76
  when "float", "double"
66
77
  { 'type' => 'number', 'format' => v }
@@ -76,17 +87,16 @@ module SwaggerYard
76
87
  end
77
88
  end
78
89
 
90
+ rule(constant: simple(:constant)) do
91
+ constant.to_s.constantize
92
+ rescue NameError => e
93
+ raise UnknownConstant, e.message
94
+ end
95
+
79
96
  rule(external_identifier: { namespace: simple(:namespace), identifier: simple(:identifier) }) do
80
- prefix, name = namespace.to_s, identifier.to_s
81
- unless url = SwaggerYard.config.external_schema[prefix]
82
- raise UndefinedSchemaError, "unknown prefix #{prefix} for #{name}"
83
- end
84
- uri = URI(url)
85
- fragment = uri.fragment ? "##{uri.fragment}" : model_path
86
- uri.fragment = nil
87
- fragment += '/' unless fragment.end_with?('/')
88
- url = uri.to_s
89
- { '$ref' => "#{url}#{fragment}#{Model.mangle(name)}"}
97
+ prefix, name = namespace.to_s, identifier.to_s
98
+ url, fragment = resolve_uri.call(name, prefix)
99
+ { '$ref' => "#{url}#{fragment}#{Model.mangle(name)}" }
90
100
  end
91
101
 
92
102
  rule(formatted: { name: simple(:name), format: simple(:format) }) do
@@ -99,8 +109,10 @@ module SwaggerYard
99
109
 
100
110
  rule(value: simple(:value)) { value.to_s }
101
111
 
112
+ rule(false: simple(:false)) { false }
113
+
102
114
  rule(enum: subtree(:values)) do
103
- { 'type' => 'string', 'enum' => Array(values) }
115
+ { 'type' => 'string', 'enum' => Array(values).flatten }
104
116
  end
105
117
 
106
118
  rule(array: subtree(:type)) do
@@ -125,6 +137,14 @@ module SwaggerYard
125
137
  result.update additional.first unless additional.empty?
126
138
  end
127
139
  end
140
+
141
+ rule(union: subtree(:types)) do
142
+ { 'oneOf' => Array(types) }
143
+ end
144
+
145
+ rule(intersect: subtree(:types)) do
146
+ { 'allOf' => Array(types) }
147
+ end
128
148
  end
129
149
 
130
150
  def initialize(model_path = Type::MODEL_PATH)
@@ -138,9 +158,21 @@ module SwaggerYard
138
158
  end
139
159
 
140
160
  def json_schema(str)
141
- @xform.apply(parse(str), model_path: @model_path)
161
+ @xform.apply(parse(str),
162
+ model_path: @model_path,
163
+ resolve_uri: ->(name, prefix) { resolve_uri(name, prefix) })
142
164
  rescue Parslet::ParseFailed => e
143
165
  raise InvalidTypeError, "'#{str}': #{e.message}"
144
166
  end
167
+
168
+ def resolve_uri(name, prefix)
169
+ unless url = SwaggerYard.config.external_schema[prefix]
170
+ raise UndefinedSchemaError, "unknown prefix #{prefix} for #{name}"
171
+ end
172
+ uri, fragment = url.split '#'
173
+ fragment = fragment ? "##{fragment}" : @model_path
174
+ fragment += '/' unless fragment.end_with?('/')
175
+ [uri, fragment]
176
+ end
145
177
  end
146
178
  end
@@ -0,0 +1,146 @@
1
+ require 'parslet'
2
+
3
+ module SwaggerYard
4
+ class TypeParser
5
+ class Parser < Parslet::Parser
6
+ # Allow for whitespace surrounding a string value
7
+ def spaced(arg)
8
+ space >> str(arg) >> space
9
+ end
10
+
11
+ def stri(str)
12
+ key_chars = str.split(//)
13
+ key_chars.collect! { |char| match["#{char.upcase}#{char.downcase}"] }.
14
+ reduce(:>>)
15
+ end
16
+
17
+ rule(:space) { match[" \n"].repeat }
18
+
19
+ rule(:id_char) { match['-a-zA-Z0-9_'].repeat }
20
+
21
+ rule(:id_start) { match('[a-zA-Z_]') }
22
+
23
+ rule(:name) { id_start >> id_char }
24
+
25
+ rule(:identifier) { name >> (str('::') >> name).repeat }
26
+
27
+ rule(:external_identifier) { name.as(:namespace) >> str('#') >> identifier.as(:identifier) }
28
+
29
+ rule(:regexp) { stri('regex') >> match['Pp'].maybe >> space >>
30
+ str('<') >> (str('\\\\') | str('\\>') | match['[^>]']).repeat.as(:regexp) >> str('>') }
31
+
32
+ rule(:enum_list) { name.as(:value) >> (spaced(',') >> name.as(:value)).repeat }
33
+
34
+ rule(:enum) { stri('enum') >> spaced('<') >> enum_list >> spaced('>') }
35
+
36
+ rule(:array) { stri('array') >> spaced('<') >> type >> spaced('>') }
37
+
38
+ rule(:pair) { (name.as(:property) >> spaced(':') >> type.as(:type)).as(:pair) }
39
+
40
+ rule(:pairs) { pair >> (spaced(',') >> pair).repeat >> (spaced(',') >> type.as(:additional)).maybe }
41
+
42
+ rule(:object) { stri('object') >> spaced('<') >> (pairs | type.as(:additional)) >> spaced('>') }
43
+
44
+ rule(:formatted) { name.as(:name) >> spaced('<') >> name.as(:format) >> spaced('>') }
45
+
46
+ rule(:type) { enum.as(:enum) |
47
+ array.as(:array) |
48
+ object.as(:object) |
49
+ formatted.as(:formatted) |
50
+ external_identifier.as(:external_identifier) |
51
+ identifier.as(:identifier) |
52
+ regexp }
53
+
54
+ root :type
55
+ end
56
+
57
+ class Transform < Parslet::Transform
58
+ rule(identifier: simple(:id)) do
59
+ v = id.to_s
60
+ case v
61
+ when /^array$/i
62
+ { 'type' => 'array', 'items' => { 'type' => 'string' } }
63
+ when /^object$/i
64
+ { 'type' => 'object' }
65
+ when "float", "double"
66
+ { 'type' => 'number', 'format' => v }
67
+ when "date-time", "date", "time", "uuid"
68
+ { 'type' => 'string', 'format' => v }
69
+ else
70
+ name = Model.mangle(v)
71
+ if /[[:upper:]]/.match(name)
72
+ { '$ref' => "#{model_path}#{name}" }
73
+ else
74
+ { 'type' => name }
75
+ end
76
+ end
77
+ end
78
+
79
+ rule(external_identifier: { namespace: simple(:namespace), identifier: simple(:identifier) }) do
80
+ prefix, name = namespace.to_s, identifier.to_s
81
+ unless url = SwaggerYard.config.external_schema[prefix]
82
+ raise UndefinedSchemaError, "unknown prefix #{prefix} for #{name}"
83
+ end
84
+ uri = URI(url)
85
+ fragment = uri.fragment ? "##{uri.fragment}" : model_path
86
+ uri.fragment = nil
87
+ fragment += '/' unless fragment.end_with?('/')
88
+ url = uri.to_s
89
+ { '$ref' => "#{url}#{fragment}#{Model.mangle(name)}"}
90
+ end
91
+
92
+ rule(formatted: { name: simple(:name), format: simple(:format) }) do
93
+ { 'type' => name.to_s, 'format' => format.to_s }
94
+ end
95
+
96
+ rule(regexp: simple(:pattern)) do
97
+ { 'type' => 'string', 'pattern' => pattern.to_s.gsub('\\\\', '\\').gsub('\>', '>') }
98
+ end
99
+
100
+ rule(value: simple(:value)) { value.to_s }
101
+
102
+ rule(enum: subtree(:values)) do
103
+ { 'type' => 'string', 'enum' => Array(values) }
104
+ end
105
+
106
+ rule(array: subtree(:type)) do
107
+ { 'type' => 'array', 'items' => type }
108
+ end
109
+
110
+ rule(pair: { property: simple(:prop), type: subtree(:type) }) do
111
+ { 'properties' => { prop.to_s => type } }
112
+ end
113
+
114
+ rule(additional: subtree(:type)) do
115
+ { 'additionalProperties' => type }
116
+ end
117
+
118
+ rule(object: subtree(:properties)) do
119
+ { 'type' => 'object' }.tap do |result|
120
+ all_props = Array === properties ? properties : [properties]
121
+ props, additional = all_props.partition {|pr| pr['properties'] }
122
+ props.each do |pr|
123
+ result['properties'] = (result['properties'] || {}).merge(pr['properties'])
124
+ end
125
+ result.update additional.first unless additional.empty?
126
+ end
127
+ end
128
+ end
129
+
130
+ def initialize(model_path = Type::MODEL_PATH)
131
+ @parser = Parser.new
132
+ @xform = Transform.new
133
+ @model_path = model_path
134
+ end
135
+
136
+ def parse(str)
137
+ @parser.parse(str)
138
+ end
139
+
140
+ def json_schema(str)
141
+ @xform.apply(parse(str), model_path: @model_path)
142
+ rescue Parslet::ParseFailed => e
143
+ raise InvalidTypeError, "'#{str}': #{e.message}"
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,151 @@
1
+ require 'parslet'
2
+
3
+ module SwaggerYard
4
+ class TypeParser
5
+ class Parser < Parslet::Parser
6
+ # Allow for whitespace surrounding a string value
7
+ def spaced(arg)
8
+ space >> str(arg) >> space
9
+ end
10
+
11
+ def stri(str)
12
+ key_chars = str.split(//)
13
+ key_chars.collect! { |char| match["#{char.upcase}#{char.downcase}"] }.
14
+ reduce(:>>)
15
+ end
16
+
17
+ rule(:space) { match[" \n"].repeat }
18
+
19
+ rule(:id_char) { match['-a-zA-Z0-9_'].repeat }
20
+
21
+ rule(:id_start) { match('[a-zA-Z_]') }
22
+
23
+ rule(:name) { id_start >> id_char }
24
+
25
+ rule(:identifier) { name >> (str('::') >> name).repeat }
26
+
27
+ rule(:external_identifier) { name.as(:namespace) >> str('#') >> identifier.as(:identifier) }
28
+
29
+ rule(:regexp) { stri('regex') >> match['Pp'].maybe >> space >>
30
+ str('<') >> (str('\\\\') | str('\\>') | match['[^>]']).repeat.as(:regexp) >> str('>') }
31
+
32
+ rule(:enum_list) { name.as(:value) >> (spaced(',') >> name.as(:value)).repeat }
33
+
34
+ rule(:enum) { stri('enum') >> spaced('<') >> enum_list >> spaced('>') }
35
+
36
+ rule(:array) { stri('array') >> spaced('<') >> type >> spaced('>') }
37
+
38
+ rule(:pair) { (name.as(:property) >> spaced(':') >> type.as(:type)).as(:pair) }
39
+
40
+ rule(:pairs) { pair >> (spaced(',') >> pair).repeat >> (spaced(',') >> type.as(:additional)).maybe }
41
+
42
+ rule(:object) { stri('object') >> spaced('<') >> (pairs | type.as(:additional)) >> spaced('>') }
43
+
44
+ rule(:formatted) { name.as(:name) >> spaced('<') >> name.as(:format) >> spaced('>') }
45
+
46
+ rule(:type) { enum.as(:enum) |
47
+ array.as(:array) |
48
+ object.as(:object) |
49
+ formatted.as(:formatted) |
50
+ external_identifier.as(:external_identifier) |
51
+ identifier.as(:identifier) |
52
+ regexp }
53
+
54
+ root :type
55
+ end
56
+
57
+ class Transform < Parslet::Transform
58
+ rule(identifier: simple(:id)) do
59
+ v = id.to_s
60
+ case v
61
+ when /^array$/i
62
+ { 'type' => 'array', 'items' => { 'type' => 'string' } }
63
+ when /^object$/i
64
+ { 'type' => 'object' }
65
+ when "float", "double"
66
+ { 'type' => 'number', 'format' => v }
67
+ when "date-time", "date", "time", "uuid"
68
+ { 'type' => 'string', 'format' => v }
69
+ else
70
+ name = Model.mangle(v)
71
+ if /[[:upper:]]/.match(name)
72
+ { '$ref' => "#{model_path}#{name}" }
73
+ else
74
+ { 'type' => name }
75
+ end
76
+ end
77
+ end
78
+
79
+ rule(external_identifier: { namespace: simple(:namespace), identifier: simple(:identifier) }) do
80
+ prefix, name = namespace.to_s, identifier.to_s
81
+ url, fragment = resolve_uri.call(name, prefix)
82
+ { '$ref' => "#{url}#{fragment}#{Model.mangle(name)}" }
83
+ end
84
+
85
+ rule(formatted: { name: simple(:name), format: simple(:format) }) do
86
+ { 'type' => name.to_s, 'format' => format.to_s }
87
+ end
88
+
89
+ rule(regexp: simple(:pattern)) do
90
+ { 'type' => 'string', 'pattern' => pattern.to_s.gsub('\\\\', '\\').gsub('\>', '>') }
91
+ end
92
+
93
+ rule(value: simple(:value)) { value.to_s }
94
+
95
+ rule(enum: subtree(:values)) do
96
+ { 'type' => 'string', 'enum' => Array(values) }
97
+ end
98
+
99
+ rule(array: subtree(:type)) do
100
+ { 'type' => 'array', 'items' => type }
101
+ end
102
+
103
+ rule(pair: { property: simple(:prop), type: subtree(:type) }) do
104
+ { 'properties' => { prop.to_s => type } }
105
+ end
106
+
107
+ rule(additional: subtree(:type)) do
108
+ { 'additionalProperties' => type }
109
+ end
110
+
111
+ rule(object: subtree(:properties)) do
112
+ { 'type' => 'object' }.tap do |result|
113
+ all_props = Array === properties ? properties : [properties]
114
+ props, additional = all_props.partition {|pr| pr['properties'] }
115
+ props.each do |pr|
116
+ result['properties'] = (result['properties'] || {}).merge(pr['properties'])
117
+ end
118
+ result.update additional.first unless additional.empty?
119
+ end
120
+ end
121
+ end
122
+
123
+ def initialize(model_path = Type::MODEL_PATH)
124
+ @parser = Parser.new
125
+ @xform = Transform.new
126
+ @model_path = model_path
127
+ end
128
+
129
+ def parse(str)
130
+ @parser.parse(str)
131
+ end
132
+
133
+ def json_schema(str)
134
+ @xform.apply(parse(str),
135
+ model_path: @model_path,
136
+ resolve_uri: ->(name, prefix) { resolve_uri(name, prefix) })
137
+ rescue Parslet::ParseFailed => e
138
+ raise InvalidTypeError, "'#{str}': #{e.message}"
139
+ end
140
+
141
+ def resolve_uri(name, prefix)
142
+ unless url = SwaggerYard.config.external_schema[prefix]
143
+ raise UndefinedSchemaError, "unknown prefix #{prefix} for #{name}"
144
+ end
145
+ uri, fragment = url.split '#'
146
+ fragment = fragment ? "##{fragment}" : @model_path
147
+ fragment += '/' unless fragment.end_with?('/')
148
+ [uri, fragment]
149
+ end
150
+ end
151
+ end
@@ -1,3 +1,3 @@
1
1
  module SwaggerYard
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/swagger_yard.rb CHANGED
@@ -15,9 +15,11 @@ require "swagger_yard/path_item"
15
15
  require "swagger_yard/swagger"
16
16
  require "swagger_yard/openapi"
17
17
  require "swagger_yard/handlers"
18
+ require "swagger_yard/directives"
18
19
 
19
20
  module SwaggerYard
20
21
  class Error < StandardError; end
22
+ class UnknownConstant < Error; end
21
23
  class InvalidTypeError < Error; end
22
24
  class UndefinedSchemaError < Error; end
23
25
 
@@ -111,10 +113,12 @@ module SwaggerYard
111
113
  ::YARD::Tags::Library.define_tag("Model superclass", :inherits)
112
114
  ::YARD::Tags::Library.define_tag("Model property", :property, :with_types_name_and_default)
113
115
  ::YARD::Tags::Library.define_tag("Model discriminator", :discriminator, :with_types_name_and_default)
116
+ ::YARD::Tags::Library.define_tag("Additional properties", :additional_properties)
114
117
  ::YARD::Tags::Library.define_tag("Authorization", :authorization, :with_types_and_name)
115
118
  ::YARD::Tags::Library.define_tag("Authorization Use", :authorize_with)
116
119
  # @example is a core YARD tag, let's use it
117
120
  # ::YARD::Tags::Library.define_tag("Example", :example, :with_title_and_text)
121
+ ::YARD::Tags::Library.define_directive(:model, :with_title_and_text, Directives::ParamClassDirective)
118
122
  end
119
123
  end
120
124
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: swagger_yard
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - chtrinh (Chris Trinh)
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-24 00:00:00.000000000 Z
11
+ date: 2021-10-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard
@@ -42,16 +42,16 @@ dependencies:
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "<"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '12'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "<"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '12'
54
+ version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -84,44 +84,44 @@ dependencies:
84
84
  name: apivore
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "<"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '1.6'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "<"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '1.6'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: nokogiri
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "<"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '1.8'
103
+ version: '0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "<"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '1.8'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: addressable
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "<="
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: 2.4.0
117
+ version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "<="
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: 2.4.0
124
+ version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +193,7 @@ files:
193
193
  - lib/swagger_yard/api_group.rb
194
194
  - lib/swagger_yard/authorization.rb
195
195
  - lib/swagger_yard/configuration.rb
196
+ - lib/swagger_yard/directives.rb
196
197
  - lib/swagger_yard/example.rb
197
198
  - lib/swagger_yard/handlers.rb
198
199
  - lib/swagger_yard/model.rb
@@ -207,12 +208,14 @@ files:
207
208
  - lib/swagger_yard/type.rb
208
209
  - lib/swagger_yard/type.rb.~master~
209
210
  - lib/swagger_yard/type_parser.rb
211
+ - lib/swagger_yard/type_parser.rb.~15b1c21e39906b88fcc3b6ec11649ad1a159adbf~
212
+ - lib/swagger_yard/type_parser.rb.~5b9ef33~
210
213
  - lib/swagger_yard/version.rb
211
214
  homepage: http://www.synctv.com
212
215
  licenses:
213
216
  - MIT
214
217
  metadata: {}
215
- post_install_message:
218
+ post_install_message:
216
219
  rdoc_options: []
217
220
  require_paths:
218
221
  - lib
@@ -220,16 +223,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
220
223
  requirements:
221
224
  - - ">="
222
225
  - !ruby/object:Gem::Version
223
- version: '0'
226
+ version: 2.5.0
224
227
  required_rubygems_version: !ruby/object:Gem::Requirement
225
228
  requirements:
226
229
  - - ">="
227
230
  - !ruby/object:Gem::Version
228
231
  version: '0'
229
232
  requirements: []
230
- rubyforge_project:
231
- rubygems_version: 2.5.2.1
232
- signing_key:
233
+ rubygems_version: 3.0.3
234
+ signing_key:
233
235
  specification_version: 4
234
236
  summary: SwaggerYard API doc through YARD
235
237
  test_files: []