grape_openapi3 0.1.2 → 0.1.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 +4 -4
- data/lib/grape_openapi3/builders/operation_builder.rb +16 -10
- data/lib/grape_openapi3/builders/parameter_builder.rb +113 -37
- data/lib/grape_openapi3/config.rb +14 -2
- data/lib/grape_openapi3/document.rb +16 -1
- data/lib/grape_openapi3/readers/entity_reader.rb +113 -13
- data/lib/grape_openapi3/version.rb +1 -1
- data/lib/grape_openapi3.rb +8 -6
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 78e04b539409184f39938ed214a2a3b0840278df89caa2e996b53398aa0cb6da
|
|
4
|
+
data.tar.gz: '08cf3d2c3cb9741e74dddd315df30c6b6d268ad70835d7c9ca9c1f5028e45fcb'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13666a9f3bbab86c2cbcd4e8972a5ade583a09a10e471dfdf3301fc8a3ce374e1341a64190f1aa90d2fea4d13b0eb887dfb0e8910252838e9a495e1d155ba052
|
|
7
|
+
data.tar.gz: 4ccecbac4a3a0a6843da4819657a9a1bd8f3a6697517c860278a7d8cd9ecea8201120c65f39e683107e1bf67a770875f74907d059425932562c883c14f63e8f5
|
|
@@ -81,8 +81,13 @@ module GrapeOpenapi3
|
|
|
81
81
|
entity = @route[:success_entity]
|
|
82
82
|
return { "description" => "Success" } unless entity
|
|
83
83
|
|
|
84
|
-
# Unpack hash form: { code: 201, message: "Created", model: ProductEntity }
|
|
85
|
-
description, entity_class = unpack_success(entity)
|
|
84
|
+
# Unpack hash form: { code: 201, message: "Created", model: ProductEntity, schema: {...} }
|
|
85
|
+
description, entity_class, inline_schema = unpack_success(entity)
|
|
86
|
+
|
|
87
|
+
if inline_schema
|
|
88
|
+
media = @route[:produces].first || "application/json"
|
|
89
|
+
return { "description" => description, "content" => { media => { "schema" => inline_schema } } }
|
|
90
|
+
end
|
|
86
91
|
|
|
87
92
|
unless entity_class
|
|
88
93
|
return { "description" => description }
|
|
@@ -98,19 +103,20 @@ module GrapeOpenapi3
|
|
|
98
103
|
{ "description" => description, "content" => { media => { "schema" => schema } } }
|
|
99
104
|
end
|
|
100
105
|
|
|
101
|
-
# Returns [description_string, entity_class_or_nil].
|
|
102
|
-
# Accepts
|
|
106
|
+
# Returns [description_string, entity_class_or_nil, inline_schema_or_nil].
|
|
107
|
+
# Accepts the bare-class form, the hash form with :model, and the hash form with :schema.
|
|
103
108
|
def unpack_success(entity)
|
|
104
109
|
if entity.is_a?(Class)
|
|
105
|
-
return ["Success", entity]
|
|
110
|
+
return ["Success", entity, nil]
|
|
106
111
|
end
|
|
107
112
|
|
|
108
|
-
return ["Success", nil] unless entity.is_a?(Hash)
|
|
113
|
+
return ["Success", nil, nil] unless entity.is_a?(Hash)
|
|
109
114
|
|
|
110
|
-
message
|
|
111
|
-
model
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
message = entity[:message] || entity["message"] || "Success"
|
|
116
|
+
model = entity[:model] || entity["model"]
|
|
117
|
+
inline_schema = entity[:schema] || entity["schema"]
|
|
118
|
+
klass = model.is_a?(Class) ? model : nil
|
|
119
|
+
[message, klass, inline_schema]
|
|
114
120
|
end
|
|
115
121
|
end
|
|
116
122
|
end
|
|
@@ -4,15 +4,21 @@ module GrapeOpenapi3
|
|
|
4
4
|
module Builders
|
|
5
5
|
# Converts a route's params hash into OpenAPI 3.0 `parameters` + `requestBody`.
|
|
6
6
|
#
|
|
7
|
-
# Param location is determined in this priority order:
|
|
7
|
+
# Param location is determined (per top-level param) in this priority order:
|
|
8
8
|
# 1. Explicit: documentation: { param_type: "path|query|header|body|formData" }
|
|
9
9
|
# 2. Name matches a {segment} in the path → path
|
|
10
10
|
# 3. type: File → formData (multipart)
|
|
11
11
|
# 4. HTTP method GET/DELETE → query
|
|
12
12
|
# 5. HTTP method POST/PUT/PATCH → body (requestBody JSON)
|
|
13
13
|
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# Nested params:
|
|
15
|
+
# Grape flattens nested params into bracketed keys, e.g.
|
|
16
|
+
# "address" => { type: "Hash" }
|
|
17
|
+
# "address[street]"=> { type: "String", required: true }
|
|
18
|
+
# "tags" => { type: "Array" }
|
|
19
|
+
# "tags[label]" => { type: "String" }
|
|
20
|
+
# These are reassembled into nested object / array-of-object schemas in the
|
|
21
|
+
# request body. Location is decided by the TOP-LEVEL key; children inherit it.
|
|
16
22
|
class ParameterBuilder
|
|
17
23
|
BODY_METHODS = %w[post put patch].freeze
|
|
18
24
|
PATH_SEGMENT = /\{(\w+)\}/
|
|
@@ -29,22 +35,20 @@ module GrapeOpenapi3
|
|
|
29
35
|
end
|
|
30
36
|
|
|
31
37
|
def call
|
|
32
|
-
inline = [] # path / query / header params
|
|
33
|
-
json = {} # body params → application/json
|
|
34
|
-
formdata = {} # formData params → multipart/form-data
|
|
38
|
+
inline = [] # path / query / header params (flat)
|
|
39
|
+
json = {} # body params (full bracket keys) → application/json
|
|
40
|
+
formdata = {} # formData params → multipart/form-data
|
|
35
41
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
defn = definition.is_a?(Hash) ? definition : {}
|
|
39
|
-
loc = location(name_s, defn)
|
|
42
|
+
grouped_by_top.each do |top, info|
|
|
43
|
+
loc = location(top, info[:root] || {})
|
|
40
44
|
|
|
41
45
|
case loc
|
|
42
46
|
when :path, :query, :header
|
|
43
|
-
inline << build_parameter(
|
|
47
|
+
inline << build_parameter(top, info[:root] || {}, loc)
|
|
44
48
|
when :form
|
|
45
|
-
formdata[
|
|
49
|
+
formdata.merge!(info[:entries])
|
|
46
50
|
when :body
|
|
47
|
-
json[
|
|
51
|
+
json.merge!(info[:entries])
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
|
|
@@ -56,6 +60,27 @@ module GrapeOpenapi3
|
|
|
56
60
|
|
|
57
61
|
private
|
|
58
62
|
|
|
63
|
+
# Group every param entry under its top-level name, keeping the full bracket
|
|
64
|
+
# keys so the body schema can be rebuilt with nesting.
|
|
65
|
+
def grouped_by_top
|
|
66
|
+
@route[:params].each_with_object({}) do |(name, definition), acc|
|
|
67
|
+
key = name.to_s
|
|
68
|
+
segments = bracket_segments(key)
|
|
69
|
+
top = segments.first
|
|
70
|
+
next if top.nil? || top.empty?
|
|
71
|
+
|
|
72
|
+
defn = definition.is_a?(Hash) ? definition : {}
|
|
73
|
+
acc[top] ||= { entries: {}, root: nil }
|
|
74
|
+
acc[top][:entries][key] = defn
|
|
75
|
+
acc[top][:root] = defn if segments.length == 1
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# "address[street]" → ["address", "street"]; "tags[]" → ["tags"]; "id" → ["id"]
|
|
80
|
+
def bracket_segments(key)
|
|
81
|
+
key.scan(/[^\[\]]+/)
|
|
82
|
+
end
|
|
83
|
+
|
|
59
84
|
def location(name, defn)
|
|
60
85
|
# Path segments are structural — they always win over any explicit hint.
|
|
61
86
|
return :path if @path_names.include?(name)
|
|
@@ -83,9 +108,7 @@ module GrapeOpenapi3
|
|
|
83
108
|
end
|
|
84
109
|
|
|
85
110
|
def build_parameter(name, defn, loc)
|
|
86
|
-
schema =
|
|
87
|
-
schema["enum"] = Array(defn[:values]) if defn[:values]
|
|
88
|
-
schema["default"] = defn[:default] unless defn[:default].nil?
|
|
111
|
+
schema = leaf_schema(defn, nullable: false)
|
|
89
112
|
|
|
90
113
|
param = {
|
|
91
114
|
"name" => name,
|
|
@@ -103,39 +126,92 @@ module GrapeOpenapi3
|
|
|
103
126
|
def build_request_body(json, formdata)
|
|
104
127
|
return nil if json.empty? && formdata.empty?
|
|
105
128
|
|
|
106
|
-
all = formdata.merge(json)
|
|
107
|
-
|
|
108
129
|
if formdata.any?
|
|
109
|
-
build_body("multipart/form-data",
|
|
130
|
+
build_body("multipart/form-data", formdata.merge(json))
|
|
110
131
|
else
|
|
111
|
-
build_body("application/json",
|
|
132
|
+
build_body("application/json", json)
|
|
112
133
|
end
|
|
113
134
|
end
|
|
114
135
|
|
|
115
|
-
|
|
136
|
+
# Build the requestBody object from flat bracket-keyed entries, rebuilding
|
|
137
|
+
# nested objects and arrays-of-objects along the way.
|
|
138
|
+
def build_body(media_type, entries)
|
|
139
|
+
schema = object_schema_from(entries)
|
|
140
|
+
{
|
|
141
|
+
"required" => Array(schema["required"]).any?,
|
|
142
|
+
"content" => { media_type => { "schema" => schema } },
|
|
143
|
+
}
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# --- nested schema assembly -----------------------------------------
|
|
147
|
+
|
|
148
|
+
def object_schema_from(entries)
|
|
149
|
+
root = { children: {}, defn: nil }
|
|
150
|
+
|
|
151
|
+
entries.each do |key, defn|
|
|
152
|
+
node = root
|
|
153
|
+
bracket_segments(key).each do |seg|
|
|
154
|
+
node[:children][seg] ||= { children: {}, defn: nil }
|
|
155
|
+
node = node[:children][seg]
|
|
156
|
+
end
|
|
157
|
+
node[:defn] = defn
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
node_to_schema(root)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def node_to_schema(node)
|
|
164
|
+
return leaf_schema(node[:defn] || {}) if node[:children].empty?
|
|
165
|
+
|
|
166
|
+
object = build_object(node[:children])
|
|
167
|
+
array_container?(node[:defn]) ? wrap_array(object, node[:defn]) : decorate(object, node[:defn])
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def build_object(children)
|
|
116
171
|
properties = {}
|
|
117
172
|
required = []
|
|
118
173
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
schema["nullable"] = true unless defn[:required]
|
|
174
|
+
children.each do |name, child|
|
|
175
|
+
properties[name] = node_to_schema(child)
|
|
176
|
+
required << name if child[:defn] && child[:defn][:required]
|
|
177
|
+
end
|
|
124
178
|
|
|
125
|
-
|
|
126
|
-
|
|
179
|
+
schema = { "type" => "object", "properties" => properties }
|
|
180
|
+
schema["required"] = required unless required.empty?
|
|
181
|
+
schema
|
|
182
|
+
end
|
|
127
183
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
184
|
+
def wrap_array(items_schema, defn)
|
|
185
|
+
decorate({ "type" => "array", "items" => items_schema }, defn)
|
|
186
|
+
end
|
|
131
187
|
|
|
132
|
-
|
|
133
|
-
|
|
188
|
+
def array_container?(defn)
|
|
189
|
+
return false unless defn
|
|
134
190
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
191
|
+
name = defn[:type].is_a?(Class) ? defn[:type].name.to_s : defn[:type].to_s
|
|
192
|
+
name == "Array" || name.match?(/\A\[.*\]\z/)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# A scalar/terminal param: map its type and attach enum/default/desc/nullable.
|
|
196
|
+
def leaf_schema(defn, nullable: nil)
|
|
197
|
+
schema = TypeMapper.call(defn[:type] || String)
|
|
198
|
+
decorate(schema, defn, nullable: nullable)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Attach enum/default/description and (for body params) nullable when optional.
|
|
202
|
+
def decorate(schema, defn, nullable: nil)
|
|
203
|
+
return schema unless defn
|
|
204
|
+
|
|
205
|
+
schema["enum"] = Array(defn[:values]) if defn[:values]
|
|
206
|
+
schema["default"] = defn[:default] unless defn[:default].nil?
|
|
207
|
+
|
|
208
|
+
desc = defn[:desc] || defn[:description]
|
|
209
|
+
schema["description"] = desc.to_s if desc
|
|
210
|
+
|
|
211
|
+
nullable = !defn[:required] if nullable.nil?
|
|
212
|
+
schema["nullable"] = true if nullable
|
|
213
|
+
|
|
214
|
+
schema
|
|
139
215
|
end
|
|
140
216
|
end
|
|
141
217
|
end
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module GrapeOpenapi3
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
# Configuration object passed through the document build.
|
|
5
|
+
#
|
|
6
|
+
# schema_name_separator: joins namespace segments when two entities share the
|
|
7
|
+
# same short name (e.g. V2::Mentors::MenteeResponse vs V2::Kids::MenteeResponse
|
|
8
|
+
# become "Mentors_MenteeResponse" / "Kids_MenteeResponse"). Default "_".
|
|
9
|
+
Config = Struct.new(
|
|
10
|
+
:info, :servers, :security_schemes, :security, :tags, :schema_name_separator,
|
|
11
|
+
keyword_init: true
|
|
12
|
+
) do
|
|
13
|
+
def schema_name_separator
|
|
14
|
+
self[:schema_name_separator] || "_"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -15,12 +15,15 @@ module GrapeOpenapi3
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def build
|
|
18
|
-
entity_reader = Readers::EntityReader.new
|
|
18
|
+
entity_reader = Readers::EntityReader.new(separator: @config.schema_name_separator)
|
|
19
19
|
|
|
20
20
|
route_list = @app.routes
|
|
21
21
|
.map { |r| Readers::RouteReader.call(r) }
|
|
22
22
|
.compact
|
|
23
23
|
|
|
24
|
+
# Pre-scan all reachable entities so schema names are stable and collision-free.
|
|
25
|
+
entity_reader.prepare(route_list.flat_map { |rd| success_entity_classes(rd) })
|
|
26
|
+
|
|
24
27
|
paths = build_paths(route_list, entity_reader)
|
|
25
28
|
components = build_components(entity_reader)
|
|
26
29
|
|
|
@@ -39,6 +42,18 @@ module GrapeOpenapi3
|
|
|
39
42
|
|
|
40
43
|
private
|
|
41
44
|
|
|
45
|
+
# The Grape::Entity class(es) referenced by a route's success response, if any.
|
|
46
|
+
# Handles both the bare-class form and the hash form ({ model: SomeEntity }).
|
|
47
|
+
def success_entity_classes(route_data)
|
|
48
|
+
entity = route_data[:success_entity]
|
|
49
|
+
klass = if entity.is_a?(Class)
|
|
50
|
+
entity
|
|
51
|
+
elsif entity.is_a?(Hash)
|
|
52
|
+
entity[:model] || entity["model"]
|
|
53
|
+
end
|
|
54
|
+
klass.is_a?(Class) ? [klass] : []
|
|
55
|
+
end
|
|
56
|
+
|
|
42
57
|
def build_paths(route_list, entity_reader)
|
|
43
58
|
route_list.each_with_object({}) do |route_data, paths|
|
|
44
59
|
path = route_data[:path]
|
|
@@ -9,17 +9,36 @@ module GrapeOpenapi3
|
|
|
9
9
|
# - Circular references are broken (placeholder set before recursing)
|
|
10
10
|
# - Nested entities (using: SomeEntity) become $ref pointers
|
|
11
11
|
#
|
|
12
|
+
# Schema naming (collision-free):
|
|
13
|
+
# By default each schema is named after the entity's last namespace segment
|
|
14
|
+
# (e.g. "MenteeResponse"). When two DIFFERENT entity classes share that short
|
|
15
|
+
# name, #prepare disambiguates them deterministically using the shortest
|
|
16
|
+
# namespace suffix that makes them unique (e.g. "Mentors_MenteeResponse" vs
|
|
17
|
+
# "Kids_MenteeResponse"). Names are stable regardless of registration order.
|
|
18
|
+
#
|
|
12
19
|
# Usage:
|
|
13
|
-
# reader = EntityReader.new
|
|
14
|
-
#
|
|
15
|
-
# reader.
|
|
20
|
+
# reader = EntityReader.new(separator: "_")
|
|
21
|
+
# reader.prepare([RootEntityA, RootEntityB]) # optional but recommended
|
|
22
|
+
# name = reader.register(MyEntity) # → schema name, adds to schemas
|
|
23
|
+
# reader.schemas # → { "MyEntity" => { ... } }
|
|
16
24
|
class EntityReader
|
|
17
25
|
REPRESENT_EXPOSURE = "Grape::Entity::Exposure::RepresentExposure"
|
|
18
26
|
|
|
19
27
|
attr_reader :schemas
|
|
20
28
|
|
|
21
|
-
def initialize
|
|
22
|
-
@schemas
|
|
29
|
+
def initialize(separator: "_")
|
|
30
|
+
@schemas = {}
|
|
31
|
+
@separator = separator
|
|
32
|
+
@name_map = {} # full Ruby class name => assigned schema name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Walk the entity graph reachable from the given root entities and assign a
|
|
36
|
+
# stable, collision-free schema name to every entity. Call before #register.
|
|
37
|
+
def prepare(root_entities)
|
|
38
|
+
all = []
|
|
39
|
+
Array(root_entities).each { |e| collect(resolve_class(e), all) }
|
|
40
|
+
@name_map = build_name_map(all)
|
|
41
|
+
self
|
|
23
42
|
end
|
|
24
43
|
|
|
25
44
|
# Register an entity class and return its schema name.
|
|
@@ -38,6 +57,57 @@ module GrapeOpenapi3
|
|
|
38
57
|
|
|
39
58
|
private
|
|
40
59
|
|
|
60
|
+
# --- name assignment -------------------------------------------------
|
|
61
|
+
|
|
62
|
+
def collect(entity_class, acc)
|
|
63
|
+
return unless entity_class.respond_to?(:root_exposures)
|
|
64
|
+
return if acc.include?(entity_class)
|
|
65
|
+
|
|
66
|
+
acc << entity_class
|
|
67
|
+
entity_class.root_exposures.each do |exposure|
|
|
68
|
+
next unless represent_exposure?(exposure)
|
|
69
|
+
|
|
70
|
+
nested = resolve_class(exposure.using_class_name)
|
|
71
|
+
collect(nested, acc) if nested
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def build_name_map(classes)
|
|
76
|
+
map = {}
|
|
77
|
+
classes.group_by { |c| short_name(c) }.each_value do |group|
|
|
78
|
+
if group.size == 1
|
|
79
|
+
map[group.first.name] = short_name(group.first)
|
|
80
|
+
else
|
|
81
|
+
disambiguate(group).each { |full, name| map[full] = name }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
map
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Pick the shortest trailing namespace suffix that makes every class in the
|
|
88
|
+
# colliding group unique. Deterministic — depends only on class names.
|
|
89
|
+
def disambiguate(group)
|
|
90
|
+
parts = group.to_h { |c| [c.name, c.name.to_s.split("::")] }
|
|
91
|
+
depth = 2
|
|
92
|
+
loop do
|
|
93
|
+
names = parts.transform_values { |segs| segs.last(depth).join(@separator) }
|
|
94
|
+
break names if names.values.uniq.size == names.size
|
|
95
|
+
break names if parts.values.all? { |segs| segs.size <= depth }
|
|
96
|
+
|
|
97
|
+
depth += 1
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def short_name(entity_class)
|
|
102
|
+
entity_class.name.to_s.split("::").last
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def schema_name(entity_class)
|
|
106
|
+
@name_map[entity_class.name] || short_name(entity_class)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# --- schema building -------------------------------------------------
|
|
110
|
+
|
|
41
111
|
def build_schema(entity_class)
|
|
42
112
|
properties = {}
|
|
43
113
|
required = []
|
|
@@ -46,12 +116,14 @@ module GrapeOpenapi3
|
|
|
46
116
|
key = exposure.key(nil).to_s
|
|
47
117
|
doc = exposure.documentation || {}
|
|
48
118
|
|
|
49
|
-
prop
|
|
119
|
+
prop = build_property(exposure, doc)
|
|
120
|
+
ref_like = prop.key?("$ref")
|
|
50
121
|
|
|
51
122
|
# OpenAPI 3.0: $ref cannot have sibling properties — wrap in allOf
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
123
|
+
prop = { "allOf" => [prop] } if ref_like && (!doc[:desc].nil? || doc[:nullable])
|
|
124
|
+
|
|
125
|
+
# enum/format/example/min/max only apply to value schemas, never to $refs
|
|
126
|
+
apply_keywords(prop, doc) unless ref_like
|
|
55
127
|
|
|
56
128
|
prop["description"] = doc[:desc].to_s unless doc[:desc].nil?
|
|
57
129
|
prop["nullable"] = true if doc[:nullable]
|
|
@@ -65,6 +137,38 @@ module GrapeOpenapi3
|
|
|
65
137
|
schema
|
|
66
138
|
end
|
|
67
139
|
|
|
140
|
+
# Attach OpenAPI value keywords from the entity's documentation hash.
|
|
141
|
+
# enum (or values), format, minimum, maximum → the scalar schema
|
|
142
|
+
# (the array's items when it's an array of scalars, otherwise the prop)
|
|
143
|
+
# default, example → the property itself
|
|
144
|
+
def apply_keywords(prop, doc)
|
|
145
|
+
scalar = scalar_target(prop)
|
|
146
|
+
enum = doc[:enum] || doc[:values]
|
|
147
|
+
|
|
148
|
+
if scalar
|
|
149
|
+
scalar["enum"] = Array(enum) if enum
|
|
150
|
+
scalar["format"] = doc[:format].to_s if doc[:format]
|
|
151
|
+
scalar["minimum"] = doc[:minimum] unless doc[:minimum].nil?
|
|
152
|
+
scalar["maximum"] = doc[:maximum] unless doc[:maximum].nil?
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
prop["default"] = doc[:default] unless doc[:default].nil?
|
|
156
|
+
prop["example"] = doc[:example] unless doc[:example].nil?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# The schema that scalar keywords belong to, or nil when there is none
|
|
160
|
+
# (e.g. an array of $refs). For an array of scalars it is the `items` schema.
|
|
161
|
+
def scalar_target(prop)
|
|
162
|
+
return nil if prop.key?("$ref") || prop.key?("allOf")
|
|
163
|
+
|
|
164
|
+
if prop["type"] == "array"
|
|
165
|
+
items = prop["items"]
|
|
166
|
+
items.is_a?(Hash) && !items.key?("$ref") ? items : nil
|
|
167
|
+
else
|
|
168
|
+
prop
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
68
172
|
def build_property(exposure, doc)
|
|
69
173
|
if represent_exposure?(exposure)
|
|
70
174
|
nested_class = resolve_class(exposure.using_class_name)
|
|
@@ -87,10 +191,6 @@ module GrapeOpenapi3
|
|
|
87
191
|
rescue NameError
|
|
88
192
|
nil
|
|
89
193
|
end
|
|
90
|
-
|
|
91
|
-
def schema_name(entity_class)
|
|
92
|
-
entity_class.name.to_s.split("::").last
|
|
93
|
-
end
|
|
94
194
|
end
|
|
95
195
|
end
|
|
96
196
|
end
|
data/lib/grape_openapi3.rb
CHANGED
|
@@ -44,13 +44,15 @@ module GrapeOpenapi3
|
|
|
44
44
|
# },
|
|
45
45
|
# security: [{ "Bearer" => [] }]
|
|
46
46
|
# )
|
|
47
|
-
def generate(app, info:, servers: [], security_schemes: {}, security: [], tags: []
|
|
47
|
+
def generate(app, info:, servers: [], security_schemes: {}, security: [], tags: [],
|
|
48
|
+
schema_name_separator: "_")
|
|
48
49
|
config = Config.new(
|
|
49
|
-
info:
|
|
50
|
-
servers:
|
|
51
|
-
security_schemes:
|
|
52
|
-
security:
|
|
53
|
-
tags:
|
|
50
|
+
info: info,
|
|
51
|
+
servers: servers,
|
|
52
|
+
security_schemes: security_schemes,
|
|
53
|
+
security: security,
|
|
54
|
+
tags: tags,
|
|
55
|
+
schema_name_separator: schema_name_separator,
|
|
54
56
|
)
|
|
55
57
|
Document.build(app, config)
|
|
56
58
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: grape_openapi3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rodrigonbarreto
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-06-
|
|
11
|
+
date: 2026-06-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: grape
|