gitlab-grape-openapi 0.3.0 → 0.5.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 +4 -4
- data/README.md +84 -12
- data/lib/gitlab/grape_openapi/concerns/constraint_applier.rb +83 -0
- data/lib/gitlab/grape_openapi/concerns/nullability.rb +53 -0
- data/lib/gitlab/grape_openapi/converters/cross_field_validation_resolver.rb +152 -0
- data/lib/gitlab/grape_openapi/converters/entity_converter.rb +4 -3
- data/lib/gitlab/grape_openapi/converters/media_type_resolver.rb +54 -0
- data/lib/gitlab/grape_openapi/converters/operation_converter.rb +21 -9
- data/lib/gitlab/grape_openapi/converters/parameter_converter.rb +31 -73
- data/lib/gitlab/grape_openapi/converters/path_converter.rb +40 -18
- data/lib/gitlab/grape_openapi/converters/request_body_converter.rb +39 -0
- data/lib/gitlab/grape_openapi/converters/response_converter.rb +73 -14
- data/lib/gitlab/grape_openapi/converters/type_resolver.rb +17 -1
- data/lib/gitlab/grape_openapi/grape_compat.rb +59 -7
- data/lib/gitlab/grape_openapi/models/request_body/parameter_schema.rb +9 -25
- data/lib/gitlab/grape_openapi/models/request_body/parameters.rb +16 -4
- data/lib/gitlab/grape_openapi/normalized_path.rb +68 -0
- data/lib/gitlab/grape_openapi/request_body_registry.rb +1 -1
- data/lib/gitlab/grape_openapi/version.rb +1 -1
- data/lib/gitlab-grape-openapi.rb +5 -0
- metadata +7 -2
|
@@ -6,10 +6,11 @@ module Gitlab
|
|
|
6
6
|
module RequestBody
|
|
7
7
|
class ParameterSchema
|
|
8
8
|
include Converters::CoercerResolver
|
|
9
|
-
include Concerns::
|
|
9
|
+
include Concerns::ConstraintApplier
|
|
10
10
|
include Concerns::LimitResolver
|
|
11
11
|
include Concerns::FailFastAnnotatable
|
|
12
12
|
include Concerns::RegexConverter
|
|
13
|
+
include Concerns::Nullability
|
|
13
14
|
|
|
14
15
|
def initialize(route:, key:, param_options:)
|
|
15
16
|
@route = route
|
|
@@ -29,8 +30,10 @@ module Gitlab
|
|
|
29
30
|
built_schema = build_resolved_schema(object_type, object_format)
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
apply_nullability!(built_schema, param_options)
|
|
33
34
|
apply_limit!(built_schema, validations)
|
|
35
|
+
apply_array_enum!(built_schema, param_options[:values])
|
|
36
|
+
apply_default!(built_schema, param_options[:default])
|
|
34
37
|
built_schema
|
|
35
38
|
end
|
|
36
39
|
|
|
@@ -109,7 +112,10 @@ module Gitlab
|
|
|
109
112
|
|
|
110
113
|
def build_union_type_schema
|
|
111
114
|
types = param_options[:type][1..-2].split(", ")
|
|
112
|
-
|
|
115
|
+
members = types.map { |type| Converters::TypeResolver.resolve_union_member(type) }
|
|
116
|
+
schema = Converters::TypeResolver.union_schema(members)
|
|
117
|
+
schema[:description] = annotated_description if param_options[:desc]
|
|
118
|
+
schema
|
|
113
119
|
end
|
|
114
120
|
|
|
115
121
|
def build_range_schema(object_type)
|
|
@@ -117,10 +123,6 @@ module Gitlab
|
|
|
117
123
|
schema = { type: object_type }
|
|
118
124
|
schema[:minimum] = range.begin if range.begin
|
|
119
125
|
schema[:maximum] = range.end if range.end
|
|
120
|
-
if param_options[:default] && serializable?(param_options[:default])
|
|
121
|
-
schema[:default] = param_options[:default]
|
|
122
|
-
end
|
|
123
|
-
|
|
124
126
|
schema[:description] = annotated_description if param_options[:desc]
|
|
125
127
|
schema
|
|
126
128
|
end
|
|
@@ -128,10 +130,6 @@ module Gitlab
|
|
|
128
130
|
def build_enum_schema(object_type)
|
|
129
131
|
schema = { type: object_type }
|
|
130
132
|
schema[:enum] = param_options[:values] unless param_options[:values].is_a?(Proc)
|
|
131
|
-
if param_options[:default] && serializable?(param_options[:default])
|
|
132
|
-
schema[:default] = param_options[:default]
|
|
133
|
-
end
|
|
134
|
-
|
|
135
133
|
schema[:description] = annotated_description if param_options[:desc]
|
|
136
134
|
schema
|
|
137
135
|
end
|
|
@@ -202,10 +200,6 @@ module Gitlab
|
|
|
202
200
|
def build_basic_schema(object_type, object_format)
|
|
203
201
|
schema = { type: object_type }
|
|
204
202
|
schema[:format] = object_format if object_format
|
|
205
|
-
if param_options[:default] && serializable?(param_options[:default])
|
|
206
|
-
schema[:default] = param_options[:default]
|
|
207
|
-
end
|
|
208
|
-
|
|
209
203
|
schema[:description] = annotated_description if param_options[:desc]
|
|
210
204
|
|
|
211
205
|
if param_options.dig(:documentation, :example)
|
|
@@ -228,16 +222,6 @@ module Gitlab
|
|
|
228
222
|
def validations_for(attribute)
|
|
229
223
|
GrapeCompat.validations_for(route, attribute)
|
|
230
224
|
end
|
|
231
|
-
|
|
232
|
-
def apply_allow_blank(schema)
|
|
233
|
-
if param_options[:allow_blank] == false || (param_options[:required] && param_options[:values])
|
|
234
|
-
schema[:minLength] = 1 if schema[:type] == 'string'
|
|
235
|
-
elsif schema[:oneOf]
|
|
236
|
-
schema[:oneOf].each { |s| s[:nullable] = true }
|
|
237
|
-
else
|
|
238
|
-
schema[:nullable] = true
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
225
|
end
|
|
242
226
|
end
|
|
243
227
|
end
|
|
@@ -14,7 +14,7 @@ module Gitlab
|
|
|
14
14
|
|
|
15
15
|
def extract
|
|
16
16
|
body_params = params.reject do |key, _|
|
|
17
|
-
path_with_params.include?("{#{key}}")
|
|
17
|
+
path_with_params.include?("{#{key}}") || hidden?(key)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
restructure_nested_params(body_params)
|
|
@@ -22,10 +22,22 @@ module Gitlab
|
|
|
22
22
|
|
|
23
23
|
private
|
|
24
24
|
|
|
25
|
+
# `documentation: { hidden: true }` marks a param the author does not want
|
|
26
|
+
# documented. Params nested below it, in bracket notation, go with it.
|
|
27
|
+
def hidden?(key)
|
|
28
|
+
key_str = key.to_s
|
|
29
|
+
|
|
30
|
+
hidden_keys.any? { |hidden| key_str == hidden || key_str.start_with?("#{hidden}[") }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def hidden_keys
|
|
34
|
+
@hidden_keys ||= params.filter_map do |key, param_options|
|
|
35
|
+
key.to_s if param_options.is_a?(Hash) && param_options.dig(:documentation, :hidden)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
25
39
|
def path_with_params
|
|
26
|
-
@path_with_params ||= route.origin
|
|
27
|
-
.gsub(/\(\.:format\)$/, '')
|
|
28
|
-
.gsub(/:\w+/) { |match| "{#{match[1..]}}" }
|
|
40
|
+
@path_with_params ||= NormalizedPath.new(route.origin).to_s
|
|
29
41
|
end
|
|
30
42
|
|
|
31
43
|
def restructure_nested_params(body_params)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gitlab
|
|
4
|
+
module GrapeOpenapi
|
|
5
|
+
# A Grape route pattern rendered as an OpenAPI path template.
|
|
6
|
+
#
|
|
7
|
+
# Grape spells path placeholders two ways: `:name` matches a single segment,
|
|
8
|
+
# and `*name` (a splat) matches one or more segments, so its value may contain
|
|
9
|
+
# slashes. OpenAPI 3.0 has no splat syntax, so both collapse to `{name}`.
|
|
10
|
+
#
|
|
11
|
+
# The splat mapping is lossy - `{name}` implies a single segment - but the
|
|
12
|
+
# endpoint is documented, which is strictly better than omitting it. Do not
|
|
13
|
+
# "fix" this by dropping splat routes: that is the bug in issue #22, which
|
|
14
|
+
# silently removed the entire package registry surface from the spec.
|
|
15
|
+
class NormalizedPath
|
|
16
|
+
# Grape appends the format suffix to `route.path`, not to `route.pattern.origin`,
|
|
17
|
+
# so this only strips a suffix an author wrote into the pattern by hand. Without
|
|
18
|
+
# it, a declared `format` param would be mistaken for a path parameter.
|
|
19
|
+
FORMAT_SUFFIX = /\(\.:format\)$/
|
|
20
|
+
|
|
21
|
+
PLACEHOLDER = /[:*](\w+)/
|
|
22
|
+
NORMALIZED_PLACEHOLDER = /\{(\w+)\}/
|
|
23
|
+
|
|
24
|
+
# Grape's optional-segment markup: the parentheses grouping an optional
|
|
25
|
+
# segment, and the backslashes escaping a literal parenthesis. Both are
|
|
26
|
+
# noise once a path is rendered for a human.
|
|
27
|
+
OPTIONAL_SEGMENT_MARKUP = /[()\\]/
|
|
28
|
+
|
|
29
|
+
# The API version is substituted away with the configured value before a path
|
|
30
|
+
# is emitted, so it never surfaces as a path parameter.
|
|
31
|
+
API_VERSION_PLACEHOLDER = 'version'
|
|
32
|
+
|
|
33
|
+
attr_reader :origin
|
|
34
|
+
|
|
35
|
+
# Takes a `route.pattern.origin` - the pattern as the author declared it.
|
|
36
|
+
# Do not pass `route.path`: Grape rewrites that one, appending the format
|
|
37
|
+
# suffix and turning a trailing `*path` into `?*path`.
|
|
38
|
+
def initialize(origin)
|
|
39
|
+
@origin = origin
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_s
|
|
43
|
+
@to_s ||= origin
|
|
44
|
+
.sub(FORMAT_SUFFIX, '')
|
|
45
|
+
.gsub(PLACEHOLDER) { "{#{Regexp.last_match(1)}}" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# The path as shown to humans in emitted paths and warnings: placeholders
|
|
49
|
+
# collapsed (via `to_s`), optional-segment markup removed, and the API
|
|
50
|
+
# version substituted in for `{version}`.
|
|
51
|
+
def to_display_path(api_version)
|
|
52
|
+
to_s.gsub(OPTIONAL_SEGMENT_MARKUP, '').gsub("{#{API_VERSION_PLACEHOLDER}}", api_version)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def placeholder_names
|
|
56
|
+
# Scanning whole `{name}` placeholders sidesteps the boundary problem a regex
|
|
57
|
+
# over the raw pattern has: real routes introduce a placeholder after `/`,
|
|
58
|
+
# `(`, `)` and `'` - `(*path`, `):file_name`, `Id='*package_name'` - so there
|
|
59
|
+
# is no single delimiter to anchor on.
|
|
60
|
+
@placeholder_names ||= to_s.scan(NORMALIZED_PLACEHOLDER).flatten
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def path_parameter_names
|
|
64
|
+
placeholder_names - [API_VERSION_PLACEHOLDER]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/gitlab-grape-openapi.rb
CHANGED
|
@@ -7,19 +7,24 @@ require_relative "gitlab/grape_openapi/generator"
|
|
|
7
7
|
require_relative "gitlab/grape_openapi/schema_registry"
|
|
8
8
|
require_relative "gitlab/grape_openapi/request_body_registry"
|
|
9
9
|
require_relative "gitlab/grape_openapi/tag_registry"
|
|
10
|
+
require_relative "gitlab/grape_openapi/normalized_path"
|
|
10
11
|
|
|
11
12
|
# Concerns
|
|
12
13
|
require_relative "gitlab/grape_openapi/concerns/serializable"
|
|
14
|
+
require_relative "gitlab/grape_openapi/concerns/constraint_applier"
|
|
13
15
|
require_relative "gitlab/grape_openapi/concerns/limit_resolver"
|
|
14
16
|
require_relative "gitlab/grape_openapi/concerns/fail_fast_annotatable"
|
|
15
17
|
require_relative "gitlab/grape_openapi/concerns/regex_converter"
|
|
18
|
+
require_relative "gitlab/grape_openapi/concerns/nullability"
|
|
16
19
|
|
|
17
20
|
# Serializers
|
|
18
21
|
require_relative "gitlab/grape_openapi/serializers/time"
|
|
19
22
|
|
|
20
23
|
# Converters
|
|
21
24
|
require_relative "gitlab/grape_openapi/converters/coercer_resolver"
|
|
25
|
+
require_relative "gitlab/grape_openapi/converters/cross_field_validation_resolver"
|
|
22
26
|
require_relative "gitlab/grape_openapi/converters/entity_converter"
|
|
27
|
+
require_relative "gitlab/grape_openapi/converters/media_type_resolver"
|
|
23
28
|
require_relative "gitlab/grape_openapi/converters/type_resolver"
|
|
24
29
|
require_relative "gitlab/grape_openapi/converters/tag_converter"
|
|
25
30
|
require_relative "gitlab/grape_openapi/converters/operation_converter"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-grape-openapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- group::api
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: grape
|
|
@@ -139,13 +139,17 @@ files:
|
|
|
139
139
|
- LICENSE
|
|
140
140
|
- README.md
|
|
141
141
|
- lib/gitlab-grape-openapi.rb
|
|
142
|
+
- lib/gitlab/grape_openapi/concerns/constraint_applier.rb
|
|
142
143
|
- lib/gitlab/grape_openapi/concerns/fail_fast_annotatable.rb
|
|
143
144
|
- lib/gitlab/grape_openapi/concerns/limit_resolver.rb
|
|
145
|
+
- lib/gitlab/grape_openapi/concerns/nullability.rb
|
|
144
146
|
- lib/gitlab/grape_openapi/concerns/regex_converter.rb
|
|
145
147
|
- lib/gitlab/grape_openapi/concerns/serializable.rb
|
|
146
148
|
- lib/gitlab/grape_openapi/configuration.rb
|
|
147
149
|
- lib/gitlab/grape_openapi/converters/coercer_resolver.rb
|
|
150
|
+
- lib/gitlab/grape_openapi/converters/cross_field_validation_resolver.rb
|
|
148
151
|
- lib/gitlab/grape_openapi/converters/entity_converter.rb
|
|
152
|
+
- lib/gitlab/grape_openapi/converters/media_type_resolver.rb
|
|
149
153
|
- lib/gitlab/grape_openapi/converters/operation_converter.rb
|
|
150
154
|
- lib/gitlab/grape_openapi/converters/parameter_converter.rb
|
|
151
155
|
- lib/gitlab/grape_openapi/converters/path_converter.rb
|
|
@@ -167,6 +171,7 @@ files:
|
|
|
167
171
|
- lib/gitlab/grape_openapi/models/server.rb
|
|
168
172
|
- lib/gitlab/grape_openapi/models/server_variable.rb
|
|
169
173
|
- lib/gitlab/grape_openapi/models/tag.rb
|
|
174
|
+
- lib/gitlab/grape_openapi/normalized_path.rb
|
|
170
175
|
- lib/gitlab/grape_openapi/request_body_registry.rb
|
|
171
176
|
- lib/gitlab/grape_openapi/schema_registry.rb
|
|
172
177
|
- lib/gitlab/grape_openapi/serializers/time.rb
|