gitlab-grape-openapi 0.2.3 → 0.3.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
2
  SHA256:
3
- metadata.gz: 6b3783b6b78454e5f8a76c0f2da60864f1f890272e8d9604cac6c0152becea17
4
- data.tar.gz: 21b5cec8a2792e118c480334b358aa62d3fc4d85553fbbe7ace7871a7fc91709
3
+ metadata.gz: 79aec4f1d4657df18a7ccbcf6b689c9f00f8f0b5c940d508da34b05112aa8432
4
+ data.tar.gz: 275e4524ae0d61929af41ca0540ff080f49790f0a2cbb1308f4a0e6d6b79f75f
5
5
  SHA512:
6
- metadata.gz: 36ca996cdcbb0a74ac4853fe174491ffbdb38176952155630b956d9dcef42da63a7e1403693e54b3db27689590e20acdc6989cf0af5d288e96c5e692e5c110c8
7
- data.tar.gz: 461ddbe6f58280b0678e394f20f722bb981e0cdb2c0684456597b5deff382e18ed96df69753df729e6e1e1cea73107eeaad9c4cff37a79aa2a92bb5b189948eb
6
+ metadata.gz: cd0dfaa149fb09c6a61bd2b8c9256d506e6bc62da716faed13e9b0c2f48932efed53b69315c330d1f91f8bf550bb5728fc284865748966b39f15b4a7d7598c67
7
+ data.tar.gz: c937af8a3e5f1a4f80ae412e7d444f1b11c7d30268b5c64dd9441c3e2b6125db1b325387effa1ec917821211ee12ca7e140dd1f9e659bcab039c86d99c3e3db4
@@ -54,9 +54,28 @@ module Gitlab
54
54
  Models::Schema.new.tap do |schema|
55
55
  schema.type = OBJECT_TYPE
56
56
  schema.properties = build_exposures_properties(root_exposures)
57
+
58
+ required = required_exposure_keys(root_exposures)
59
+ schema.required = required if required.any?
57
60
  end
58
61
  end
59
62
 
63
+ def required_exposure_keys(exposures)
64
+ exposures.flat_map do |exposure|
65
+ if inlineable_merge_exposure?(exposure)
66
+ # Merged entity properties become the parent's, so their
67
+ # `required` entries do too.
68
+ build_or_fetch_nested_schema(nested_entity_class(exposure))&.required || []
69
+ elsif block_exposure?(exposure) && exposure.for_merge
70
+ required_exposure_keys(exposure.nested_exposures)
71
+ elsif exposure_documentation(exposure)[:required]
72
+ [exposure.key.to_s]
73
+ else
74
+ []
75
+ end
76
+ end.uniq
77
+ end
78
+
60
79
  def build_exposures_properties(exposures)
61
80
  exposures.each_with_object({}) do |exposure, properties|
62
81
  if inlineable_merge_exposure?(exposure)
@@ -95,6 +114,9 @@ module Gitlab
95
114
  object_schema = { type: OBJECT_TYPE }
96
115
  object_schema[:properties] = nested_properties if nested_properties.any?
97
116
 
117
+ nested_required = required_exposure_keys(exposure.nested_exposures)
118
+ object_schema[:required] = nested_required if nested_required.any?
119
+
98
120
  if documentation[:is_array]
99
121
  { type: ARRAY_TYPE, description: documentation[:desc], items: object_schema }.compact
100
122
  else
@@ -169,11 +191,23 @@ module Gitlab
169
191
  type: TypeResolver.resolve_type(actual_type) || DEFAULT_TYPE,
170
192
  description: documentation[:desc],
171
193
  format: TypeResolver.resolve_format(documentation[:format], actual_type),
194
+ enum: enum_values(documentation),
172
195
  default: default_value,
173
196
  example: documentation[:example]
174
197
  }
175
198
  end
176
199
 
200
+ # `values:` may also be a Proc or a Range (runtime validation
201
+ # constructs); only a literal Array translates to a fixed OpenAPI
202
+ # enum. Mirrors `ParameterConverter#build_enum_schema`. Symbols are
203
+ # stringified because the response contains strings, not symbols.
204
+ def enum_values(documentation)
205
+ values = documentation[:values]
206
+ return unless values.is_a?(Array) && values.any?
207
+
208
+ values.map { |value| value.is_a?(Symbol) ? value.to_s : value }
209
+ end
210
+
177
211
  def build_type_schema(type, documentation)
178
212
  schema = { type: TypeResolver.resolve_type(type) || DEFAULT_TYPE }
179
213
 
@@ -229,10 +263,13 @@ module Gitlab
229
263
  def build_primitive_items(property, item_type)
230
264
  items = { type: item_type }
231
265
 
232
- # Move format to items if present
233
- if property[:format]
234
- items[:format] = property[:format]
235
- property[:format] = nil
266
+ # Move format and enum to items if present; they constrain each
267
+ # element, not the array itself
268
+ [:format, :enum].each do |key|
269
+ next unless property[key]
270
+
271
+ items[key] = property[key]
272
+ property[key] = nil
236
273
  end
237
274
 
238
275
  items
@@ -238,17 +238,10 @@ module Gitlab
238
238
  # Looks something like:
239
239
  # [{:attributes=>[:version_prefix],
240
240
  # :options=>/^[\d+.]+/,
241
- # :required=>false,
242
- # :params_scope=>#<Grape::Validations::ParamsScope:0x000000016dc35820
243
- # :opts=>{:allow_blank=>nil, :fail_fast=>false},
241
+ # :opts=>{:fail_fast=>false},
244
242
  # :validator_class=>Grape::Validations::Validators::RegexpValidator}]
245
243
  def validations_for(attribute)
246
- route
247
- .app
248
- .inheritable_setting
249
- .namespace_stackable
250
- .new_values[:validations]
251
- &.select { |v| v[:attributes].include?(attribute) }
244
+ GrapeCompat.validations_for(route, attribute)
252
245
  end
253
246
 
254
247
  def extract_request_body
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module GrapeOpenapi
5
+ # Isolates the Grape internals this gem reads so version differences live in
6
+ # one place.
7
+ #
8
+ # Grape < 3.2 stores each declared validation as a Hash; Grape >= 3.2 stores
9
+ # a frozen validator instance instead (ruby-grape/grape#2657). Both shapes
10
+ # are normalized to the Hash the converters expect.
11
+ module GrapeCompat
12
+ class << self
13
+ # Declared validations for a single attribute, newest scope only.
14
+ #
15
+ # Reads `new_values` rather than `[]` or `route[:saved_validations]` on
16
+ # purpose: those also include validations inherited from parent scopes,
17
+ # which would change the generated output.
18
+ def validations_for(route, attribute)
19
+ validations = route.app.inheritable_setting.namespace_stackable.new_values[:validations]
20
+ return unless validations
21
+
22
+ validations.filter_map do |validation|
23
+ normalized = normalize(validation)
24
+ normalized if normalized && normalized[:attributes].include?(attribute)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def normalize(validation)
31
+ return validation if validation.is_a?(Hash)
32
+
33
+ # Grape 3.2's ContractScopeValidator declares no attributes, so it maps
34
+ # to no parameter.
35
+ return unless validation.respond_to?(:attrs)
36
+
37
+ {
38
+ attributes: validation.attrs,
39
+ validator_class: validation.class,
40
+ # Grape 3.2 exposes no public reader for the validator options, which
41
+ # carry regexp patterns and limits. Switch to the reader if one is
42
+ # added upstream.
43
+ options: validation.instance_variable_get(:@options),
44
+ opts: { fail_fast: validation.fail_fast? }
45
+ }
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -226,12 +226,7 @@ module Gitlab
226
226
  end
227
227
 
228
228
  def validations_for(attribute)
229
- route
230
- .app
231
- .inheritable_setting
232
- .namespace_stackable
233
- .new_values[:validations]
234
- &.select { |v| v[:attributes].include?(attribute) }
229
+ GrapeCompat.validations_for(route, attribute)
235
230
  end
236
231
 
237
232
  def apply_allow_blank(schema)
@@ -5,7 +5,7 @@ module Gitlab
5
5
  module Models
6
6
  # https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#schema-object
7
7
  class Schema
8
- attr_accessor :properties, :type
8
+ attr_accessor :properties, :required, :type
9
9
 
10
10
  def initialize
11
11
  @properties = {}
@@ -31,6 +31,7 @@ module Gitlab
31
31
  {}.tap do |hash|
32
32
  add_type(hash)
33
33
  add_properties(hash)
34
+ add_required(hash)
34
35
  end
35
36
  end
36
37
 
@@ -44,6 +45,10 @@ module Gitlab
44
45
  hash[:properties] = properties.transform_values(&:to_h)
45
46
  end
46
47
 
48
+ def add_required(hash)
49
+ hash[:required] = required if required && !required.empty?
50
+ end
51
+
47
52
  def description
48
53
  @properties[:description]
49
54
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Gitlab
4
4
  module GrapeOpenapi
5
- VERSION = "0.2.3"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "gitlab/grape_openapi/version"
4
+ require_relative "gitlab/grape_openapi/grape_compat"
4
5
  require_relative "gitlab/grape_openapi/configuration"
5
6
  require_relative "gitlab/grape_openapi/generator"
6
7
  require_relative "gitlab/grape_openapi/schema_registry"
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-grape-openapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.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-07-20 00:00:00.000000000 Z
11
+ date: 2026-08-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grape
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '2.0'
22
+ version: '3.3'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '2.4'
30
+ - - "<"
25
31
  - !ruby/object:Gem::Version
26
- version: '2.0'
32
+ version: '3.3'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: grape-entity
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -148,6 +154,7 @@ files:
148
154
  - lib/gitlab/grape_openapi/converters/tag_converter.rb
149
155
  - lib/gitlab/grape_openapi/converters/type_resolver.rb
150
156
  - lib/gitlab/grape_openapi/generator.rb
157
+ - lib/gitlab/grape_openapi/grape_compat.rb
151
158
  - lib/gitlab/grape_openapi/models/info.rb
152
159
  - lib/gitlab/grape_openapi/models/operation.rb
153
160
  - lib/gitlab/grape_openapi/models/parameter.rb