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 +5 -5
- data/README.md +27 -2
- data/lib/swagger_yard/directives.rb +32 -0
- data/lib/swagger_yard/model.rb +10 -3
- data/lib/swagger_yard/swagger.rb +1 -0
- data/lib/swagger_yard/type_parser.rb +47 -15
- data/lib/swagger_yard/type_parser.rb.~15b1c21e39906b88fcc3b6ec11649ad1a159adbf~ +146 -0
- data/lib/swagger_yard/type_parser.rb.~5b9ef33~ +151 -0
- data/lib/swagger_yard/version.rb +1 -1
- data/lib/swagger_yard.rb +4 -0
- metadata +26 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 92fee53b6f39a4a44e733619571d5c051054df3e7721058b50d6f18654898d73
|
|
4
|
+
data.tar.gz: 93e2c4acd9fb8c523a39c916257e41a5c6426e5033439f814223d91397317606
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 23e2f8c39bdaf2a8009dc7f78b4a8f589da25737fa4cb3e77ab89395abe84459a52106d85aa026b533bbefdfe646cfa9c7fc7d9ede512be5edd4e242ada164ba
|
|
7
|
+
data.tar.gz: b893eddf59099d0fc2cba25486d5e7b2073b2386d3e0805addf7f7d2b280ccdd165038cd5323ab5315ecd8ac7a5ad07b0b6541471c6c14f7ff5890309de313cf
|
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# SwaggerYard [](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
|
data/lib/swagger_yard/model.rb
CHANGED
|
@@ -5,7 +5,8 @@ module SwaggerYard
|
|
|
5
5
|
#
|
|
6
6
|
class Model
|
|
7
7
|
include Example
|
|
8
|
-
attr_reader :id, :discriminator, :inherits,
|
|
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.
|
|
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: #{
|
|
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
|
|
data/lib/swagger_yard/swagger.rb
CHANGED
|
@@ -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
|
|
72
|
+
when /^array$/i
|
|
62
73
|
{ 'type' => 'array', 'items' => { 'type' => 'string' } }
|
|
63
|
-
when
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
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),
|
|
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
|
data/lib/swagger_yard/version.rb
CHANGED
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
|
|
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:
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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: '
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
231
|
-
|
|
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: []
|