swagger_yard 1.0.3 → 1.0.4

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
2
  SHA256:
3
- metadata.gz: 756c19df66b9851bc539daa6a77ba78b00814a73e514411c03ed036aaa0c2921
4
- data.tar.gz: fa7b4ca209d1654df04d659f57c4e38803ce90c4c0a65d45c1aef7f19f655978
3
+ metadata.gz: 17346f3dae0e585379c27fc085e3dba2d22546eb5b01f31e2575ea1c2237e95c
4
+ data.tar.gz: a346029b12de6f15c72d0caa3b4bd5e59ccff4741c474e7bb3ecfa70a8dc878e
5
5
  SHA512:
6
- metadata.gz: e00185e0b6e4d36c9933f3567b6223a7918ec47fe3a31f54a08d87442306efa3b6398a83cb820e4e04839f0b475102463963df580a75fd3918f974c014269993
7
- data.tar.gz: ef4cdc1187050861e1bc4bfe6a23c223473e86def53ada9d65320c853a79e71c1d7ddcfb62eb9f9d69b5fab34a68f04503bda6f7825881a12021d9fb985d5e0f
6
+ metadata.gz: 91f408ddf67843e217bdac7a69e18e67b513389e7ccb42b8f1a1a27d7781deefc65628fc7239909a3b4a8d9dc7c66236bc3e0bb076e623b93371bb9929517a4c
7
+ data.tar.gz: a7a235cad5fbf1f2289c216562bdf7814721589393facf775fac2a39e84e589384d346db16af05c58760ec8e08889b3173e36aae41052e839e80636e76d23f27
data/README.md CHANGED
@@ -110,7 +110,7 @@ end
110
110
 
111
111
  ### Model ###
112
112
 
113
- 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`).
114
114
 
115
115
  ```ruby
116
116
  #
@@ -120,6 +120,7 @@ Each model to be exposed in the specification must have a `@model` tag. Model pr
120
120
  # @property name [Array<string>] the names for the pet
121
121
  # @property age [integer] the age of the pet
122
122
  # @property relatives(required) [Array<Pet>] other Pets in its family
123
+ # @additional_properties string
123
124
  #
124
125
  class Pet
125
126
  end
@@ -179,6 +180,8 @@ Types of things (parameters or responses of an operation, properties of a model)
179
180
  - Structured data like objects, arrays, pairs, etc., definitions can also be nested; E.g., `[object<pairs:array<object<right:integer,left:integer>>>]`
180
181
  - JSON-Schema `format` attributes can be specified for basic types using `<...>`. For example, `[integer<int64>]` produces JSON `{ "type": "integer", "format": "int64" }`.
181
182
  - Regex pattern constraints can be specified for strings using `[regex<PATTERN>]`. For example, `[regex<^.{3}$>]` produces JSON `{ "type": "string", "pattern": "^.{3}$" }`.
183
+ - A union of two or more sub-types is expressed as `(A | B)` (parentheses required). This translates to `oneOf:` in JSON Schema.
184
+ - An intersection of two or more sub-types is expressed as `(A & B)` (parentheses required). This translates to `allOf:` in JSON Schema.
182
185
 
183
186
  ### Options ###
184
187
 
@@ -112,6 +112,7 @@ module SwaggerYard
112
112
  ::YARD::Tags::Library.define_tag("Model superclass", :inherits)
113
113
  ::YARD::Tags::Library.define_tag("Model property", :property, :with_types_name_and_default)
114
114
  ::YARD::Tags::Library.define_tag("Model discriminator", :discriminator, :with_types_name_and_default)
115
+ ::YARD::Tags::Library.define_tag("Additional properties", :additional_properties)
115
116
  ::YARD::Tags::Library.define_tag("Authorization", :authorization, :with_types_and_name)
116
117
  ::YARD::Tags::Library.define_tag("Authorization Use", :authorize_with)
117
118
  # @example is a core YARD tag, let's use it
@@ -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,7 +42,7 @@ module SwaggerYard
41
42
  properties.detect {|prop| prop.name == key }
42
43
  end
43
44
 
44
- TAG_ORDER = %w(model inherits discriminator property example)
45
+ TAG_ORDER = %w(model inherits discriminator property example additional_properties)
45
46
 
46
47
  def parse_tags(tags)
47
48
  sorted_tags = tags.each_with_index.sort_by { |t,i|
@@ -72,6 +73,8 @@ module SwaggerYard
72
73
  else
73
74
  self.example = tag.text
74
75
  end
76
+ when "additional_properties"
77
+ @additional_properties = Type.new(tag.text).schema
75
78
  end
76
79
  end
77
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
 
@@ -26,6 +26,8 @@ module SwaggerYard
26
26
 
27
27
  rule(:external_identifier) { name.as(:namespace) >> str('#') >> identifier.as(:identifier) }
28
28
 
29
+ rule(:_false) { str('false').as(:false) }
30
+
29
31
  rule(:regexp) { stri('regex') >> match['Pp'].maybe >> space >>
30
32
  str('<') >> (str('\\\\') | str('\\>') | match['[^>]']).repeat.as(:regexp) >> str('>') }
31
33
 
@@ -43,10 +45,17 @@ module SwaggerYard
43
45
 
44
46
  rule(:formatted) { name.as(:name) >> spaced('<') >> name.as(:format) >> spaced('>') }
45
47
 
48
+ rule(:union) { spaced('(') >> type >> (spaced('|') >> type).repeat >> spaced(')') }
49
+
50
+ rule(:intersect) { spaced('(') >> type >> (spaced('&') >> type).repeat >> spaced(')') }
51
+
46
52
  rule(:type) { enum.as(:enum) |
47
53
  array.as(:array) |
48
54
  object.as(:object) |
49
55
  formatted.as(:formatted) |
56
+ union.as(:union) |
57
+ intersect.as(:intersect) |
58
+ _false |
50
59
  external_identifier.as(:external_identifier) |
51
60
  identifier.as(:identifier) |
52
61
  regexp }
@@ -77,16 +86,9 @@ module SwaggerYard
77
86
  end
78
87
 
79
88
  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)}"}
89
+ prefix, name = namespace.to_s, identifier.to_s
90
+ url, fragment = resolve_uri.call(name, prefix)
91
+ { '$ref' => "#{url}#{fragment}#{Model.mangle(name)}" }
90
92
  end
91
93
 
92
94
  rule(formatted: { name: simple(:name), format: simple(:format) }) do
@@ -99,6 +101,8 @@ module SwaggerYard
99
101
 
100
102
  rule(value: simple(:value)) { value.to_s }
101
103
 
104
+ rule(false: simple(:false)) { false }
105
+
102
106
  rule(enum: subtree(:values)) do
103
107
  { 'type' => 'string', 'enum' => Array(values) }
104
108
  end
@@ -125,6 +129,14 @@ module SwaggerYard
125
129
  result.update additional.first unless additional.empty?
126
130
  end
127
131
  end
132
+
133
+ rule(union: subtree(:types)) do
134
+ { 'oneOf' => Array(types) }
135
+ end
136
+
137
+ rule(intersect: subtree(:types)) do
138
+ { 'allOf' => Array(types) }
139
+ end
128
140
  end
129
141
 
130
142
  def initialize(model_path = Type::MODEL_PATH)
@@ -138,9 +150,21 @@ module SwaggerYard
138
150
  end
139
151
 
140
152
  def json_schema(str)
141
- @xform.apply(parse(str), model_path: @model_path)
153
+ @xform.apply(parse(str),
154
+ model_path: @model_path,
155
+ resolve_uri: ->(name, prefix) { resolve_uri(name, prefix) })
142
156
  rescue Parslet::ParseFailed => e
143
157
  raise InvalidTypeError, "'#{str}': #{e.message}"
144
158
  end
159
+
160
+ def resolve_uri(name, prefix)
161
+ unless url = SwaggerYard.config.external_schema[prefix]
162
+ raise UndefinedSchemaError, "unknown prefix #{prefix} for #{name}"
163
+ end
164
+ uri, fragment = url.split '#'
165
+ fragment = fragment ? "##{fragment}" : @model_path
166
+ fragment += '/' unless fragment.end_with?('/')
167
+ [uri, fragment]
168
+ end
145
169
  end
146
170
  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.3"
2
+ VERSION = "1.0.4"
3
3
  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.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - chtrinh (Chris Trinh)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-22 00:00:00.000000000 Z
11
+ date: 2020-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yard
@@ -208,6 +208,8 @@ files:
208
208
  - lib/swagger_yard/type.rb
209
209
  - lib/swagger_yard/type.rb.~master~
210
210
  - lib/swagger_yard/type_parser.rb
211
+ - lib/swagger_yard/type_parser.rb.~15b1c21e39906b88fcc3b6ec11649ad1a159adbf~
212
+ - lib/swagger_yard/type_parser.rb.~5b9ef33~
211
213
  - lib/swagger_yard/version.rb
212
214
  homepage: http://www.synctv.com
213
215
  licenses: