apiwork 0.6.1 → 0.7.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -2
  3. data/lib/apiwork/adapter/serializer/error/default/api_builder.rb +6 -1
  4. data/lib/apiwork/adapter/standard/capability/writing/contract_builder.rb +1 -0
  5. data/lib/apiwork/api/base.rb +31 -0
  6. data/lib/apiwork/api/object.rb +16 -15
  7. data/lib/apiwork/contract/object/validator.rb +1 -1
  8. data/lib/apiwork/contract/object.rb +79 -77
  9. data/lib/apiwork/controller.rb +6 -2
  10. data/lib/apiwork/export/apiwork.rb +20 -0
  11. data/lib/apiwork/export/apiwork_mapper.rb +232 -0
  12. data/lib/apiwork/export/open_api.rb +2 -0
  13. data/lib/apiwork/export/registry.rb +1 -1
  14. data/lib/apiwork/export/sorbus.rb +1 -5
  15. data/lib/apiwork/export/type_script.rb +1 -4
  16. data/lib/apiwork/export/type_script_mapper.rb +12 -4
  17. data/lib/apiwork/export/zod.rb +0 -9
  18. data/lib/apiwork/export/zod_mapper.rb +22 -1
  19. data/lib/apiwork/export.rb +1 -0
  20. data/lib/apiwork/introspection/api/resource.rb +9 -0
  21. data/lib/apiwork/introspection/dump/param.rb +9 -10
  22. data/lib/apiwork/introspection/dump/resource.rb +3 -1
  23. data/lib/apiwork/introspection/dump/type.rb +15 -4
  24. data/lib/apiwork/introspection/enum.rb +9 -0
  25. data/lib/apiwork/introspection/param/array.rb +3 -0
  26. data/lib/apiwork/introspection/param/base.rb +11 -0
  27. data/lib/apiwork/introspection/param/binary.rb +3 -0
  28. data/lib/apiwork/introspection/param/boolean.rb +3 -0
  29. data/lib/apiwork/introspection/param/date.rb +3 -0
  30. data/lib/apiwork/introspection/param/date_time.rb +3 -0
  31. data/lib/apiwork/introspection/param/decimal.rb +3 -0
  32. data/lib/apiwork/introspection/param/integer.rb +3 -0
  33. data/lib/apiwork/introspection/param/number.rb +3 -0
  34. data/lib/apiwork/introspection/param/record.rb +3 -0
  35. data/lib/apiwork/introspection/param/string.rb +3 -0
  36. data/lib/apiwork/introspection/param/time.rb +3 -0
  37. data/lib/apiwork/introspection/param/uuid.rb +3 -0
  38. data/lib/apiwork/introspection/type.rb +9 -0
  39. data/lib/apiwork/issue.rb +1 -1
  40. data/lib/apiwork/object.rb +111 -103
  41. data/lib/apiwork/reference_generator.rb +43 -0
  42. data/lib/apiwork/representation/attribute.rb +87 -2
  43. data/lib/apiwork/representation/base.rb +4 -0
  44. data/lib/apiwork/version.rb +1 -1
  45. data/lib/apiwork.rb +4 -0
  46. metadata +4 -17
  47. data/lib/apiwork/export/builder_mapper.rb +0 -184
@@ -0,0 +1,232 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Export
5
+ class ApiworkMapper
6
+ class << self
7
+ def map(export, surface)
8
+ new(export).map(surface)
9
+ end
10
+ end
11
+
12
+ def initialize(export)
13
+ @export = export
14
+ end
15
+
16
+ def map(surface)
17
+ {
18
+ base_path: @export.api.base_path,
19
+ enums: serialize_enums(surface.enums),
20
+ error_codes: serialize_error_codes,
21
+ fingerprint: @export.api.fingerprint,
22
+ info: serialize_info,
23
+ locales: @export.api.locales.map(&:to_s),
24
+ resources: serialize_resources(@export.api.resources),
25
+ types: serialize_types(surface.types),
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ def serialize_enums(enums)
32
+ enums.map do |name, enum|
33
+ {
34
+ deprecated: enum.deprecated?,
35
+ description: enum.description,
36
+ example: enum.example,
37
+ name: name.to_s,
38
+ scope: enum.scope,
39
+ values: enum.values,
40
+ }
41
+ end
42
+ end
43
+
44
+ def serialize_error_codes
45
+ @export.api.error_codes.map do |name, error_code|
46
+ {
47
+ description: error_code.description,
48
+ name: name.to_s,
49
+ status: error_code.status,
50
+ }
51
+ end
52
+ end
53
+
54
+ def serialize_info
55
+ info = @export.api.info
56
+ return unless info
57
+
58
+ {
59
+ contact: info.contact && {
60
+ email: info.contact.email,
61
+ name: info.contact.name,
62
+ url: info.contact.url,
63
+ },
64
+ description: info.description,
65
+ license: info.license && {
66
+ name: info.license.name,
67
+ url: info.license.url,
68
+ },
69
+ servers: info.servers.map do |server|
70
+ { description: server.description, url: server.url }
71
+ end,
72
+ summary: info.summary,
73
+ terms_of_service: info.terms_of_service,
74
+ title: info.title,
75
+ version: info.version,
76
+ }
77
+ end
78
+
79
+ def serialize_types(types)
80
+ type_hashes = types.transform_values(&:to_h)
81
+ sorted = TypeAnalysis.topological_sort_types(type_hashes)
82
+ recursive = TypeAnalysis.cycle_breaking_types(type_hashes)
83
+
84
+ sorted.map { |name, _| serialize_type(name, types[name], recursive: recursive.include?(name)) }
85
+ end
86
+
87
+ def serialize_type(name, type, recursive:)
88
+ result = {
89
+ recursive:,
90
+ deprecated: type.deprecated?,
91
+ description: type.description,
92
+ example: type.example,
93
+ name: name.to_s,
94
+ scope: type.scope,
95
+ type: type.type.to_s,
96
+ }
97
+
98
+ if type.object?
99
+ result[:extends] = type.extends.map(&:to_s)
100
+ result[:shape] = serialize_shape(type.shape)
101
+ else
102
+ result[:discriminator] = transform_key(type.discriminator)
103
+ result[:variants] = type.variants.map do |variant|
104
+ serialized = serialize_param(variant)
105
+ serialized[:tag] = variant.tag if variant.respond_to?(:tag) && variant.tag
106
+ serialized
107
+ end
108
+ end
109
+
110
+ result
111
+ end
112
+
113
+ def serialize_resources(resources, prefix: nil)
114
+ resources.map do |name, resource|
115
+ qualified_name = prefix ? "#{prefix}.#{name}" : name.to_s
116
+
117
+ {
118
+ actions: resource.actions.map do |action_name, action|
119
+ serialize_action(action, name: "#{qualified_name}.#{action_name}")
120
+ end,
121
+ identifier: resource.identifier.to_s,
122
+ name: name.to_s,
123
+ parent_identifiers: resource.parent_identifiers.map(&:to_s),
124
+ path: transform_path(resource.path),
125
+ resources: serialize_resources(resource.resources, prefix: qualified_name),
126
+ scope: resource.scope,
127
+ }
128
+ end
129
+ end
130
+
131
+ def serialize_action(action, name:)
132
+ {
133
+ name:,
134
+ deprecated: action.deprecated?,
135
+ description: action.description,
136
+ method: action.method.to_s,
137
+ operation_id: action.operation_id,
138
+ path: transform_path(action.path),
139
+ raises: action.raises.map(&:to_s),
140
+ request: {
141
+ body: serialize_shape(action.request.body),
142
+ description: action.request.description,
143
+ query: serialize_shape(action.request.query),
144
+ },
145
+ response: {
146
+ body: serialize_response_body(action.response),
147
+ description: action.response.description,
148
+ no_content: action.response.no_content?,
149
+ },
150
+ summary: action.summary,
151
+ tags: action.tags,
152
+ }
153
+ end
154
+
155
+ def serialize_response_body(response)
156
+ return unless response.body?
157
+
158
+ serialize_param(response.body)
159
+ end
160
+
161
+ def serialize_shape(params)
162
+ params.sort_by { |name, _| name.to_s }.map do |name, param|
163
+ { name: transform_key(name) }.merge(serialize_param(param))
164
+ end
165
+ end
166
+
167
+ def serialize_param(param)
168
+ result = {
169
+ deprecated: param.deprecated?,
170
+ description: param.description,
171
+ nullable: param.nullable?,
172
+ optional: param.optional?,
173
+ type: param.type.to_s,
174
+ }
175
+
176
+ case param.type
177
+ when :string, :integer
178
+ result[:default] = param.default if param.default?
179
+ result[:enum] = param.enum.to_s if param.enum?
180
+ result[:example] = param.example
181
+ result[:format] = param.format
182
+ result[:max] = param.max
183
+ result[:min] = param.min
184
+ when :number, :decimal
185
+ result[:default] = param.default if param.default?
186
+ result[:enum] = param.enum.to_s if param.enum?
187
+ result[:example] = param.example
188
+ result[:max] = param.max
189
+ result[:min] = param.min
190
+ when :boolean, :date, :datetime, :time, :uuid, :binary
191
+ result[:default] = param.default if param.default?
192
+ result[:enum] = param.enum.to_s if param.enum?
193
+ result[:example] = param.example
194
+ when :literal
195
+ result[:value] = param.value
196
+ when :array
197
+ result[:default] = param.default if param.default?
198
+ result[:example] = param.example
199
+ result[:max] = param.max
200
+ result[:min] = param.min
201
+ result[:of] = param.of ? serialize_param(param.of) : nil
202
+ when :record
203
+ result[:default] = param.default if param.default?
204
+ result[:example] = param.example
205
+ result[:of] = param.of ? serialize_param(param.of) : nil
206
+ when :object
207
+ result[:partial] = param.partial?
208
+ result[:shape] = serialize_shape(param.shape)
209
+ when :union
210
+ result[:discriminator] = transform_key(param.discriminator)
211
+ result[:variants] = param.variants.map do |variant|
212
+ serialized = serialize_param(variant)
213
+ serialized[:tag] = variant.tag if variant.respond_to?(:tag) && variant.tag
214
+ serialized
215
+ end
216
+ when :reference
217
+ result[:reference] = param.reference.to_s
218
+ end
219
+
220
+ result
221
+ end
222
+
223
+ def transform_path(path)
224
+ path.to_s.gsub(/:(\w+)/) { ":#{transform_key(::Regexp.last_match(1))}" }
225
+ end
226
+
227
+ def transform_key(key)
228
+ @export.transform_key(key)
229
+ end
230
+ end
231
+ end
232
+ end
@@ -324,6 +324,8 @@ module Apiwork
324
324
 
325
325
  schema[:format] = param.format.to_s if param.formattable? && param.format
326
326
 
327
+ schema[:default] = param.default if param.respond_to?(:default) && param.default?
328
+
327
329
  apply_nullable(schema, param.nullable?)
328
330
  end
329
331
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Apiwork
4
4
  module Export
5
- class Registry < Apiwork::Registry
5
+ class Registry < ::Apiwork::Registry
6
6
  class << self
7
7
  def register(export_class)
8
8
  raise ArgumentError, 'Export must inherit from Apiwork::Export::Base' unless export_class < Base
@@ -7,12 +7,8 @@ module Apiwork
7
7
  output :string
8
8
  file_extension '.ts'
9
9
 
10
- option :builders, default: false, type: :boolean
11
-
12
10
  def generate
13
- output = SorbusMapper.map(self, surface)
14
- output += "\n\n#{BuilderMapper.map(self, surface)}" if options[:builders]
15
- output
11
+ SorbusMapper.map(self, surface)
16
12
  end
17
13
 
18
14
  private
@@ -7,13 +7,10 @@ module Apiwork
7
7
  output :string
8
8
  file_extension '.ts'
9
9
 
10
- option :builders, default: false, type: :boolean
11
10
  option :version, default: '5', enum: %w[4 5], type: :string
12
11
 
13
12
  def generate
14
- output = TypeScriptMapper.map(self, surface)
15
- output += "\n\n#{BuilderMapper.map(self, surface)}" if options[:builders]
16
- output
13
+ TypeScriptMapper.map(self, surface)
17
14
  end
18
15
 
19
16
  private
@@ -28,7 +28,7 @@ module Apiwork
28
28
  properties = type.shape.sort_by { |name, _param| name.to_s }.map do |name, param|
29
29
  key = @export.transform_key(name)
30
30
  ts_type = map_field(param)
31
- optional_marker = param.optional? ? '?' : ''
31
+ optional_marker = optional_in_output?(param) ? '?' : ''
32
32
 
33
33
  prop_jsdoc = jsdoc(description: param.description, example: param.concrete? ? param.example : nil)
34
34
  if prop_jsdoc
@@ -85,7 +85,7 @@ module Apiwork
85
85
  properties = query_params.sort_by { |name, _param| name.to_s }.map do |param_name, param|
86
86
  key = @export.transform_key(param_name)
87
87
  ts_type = map_field(param)
88
- optional_marker = param.optional? ? '?' : ''
88
+ optional_marker = optional_in_output?(param) ? '?' : ''
89
89
  " #{key}#{optional_marker}: #{ts_type};"
90
90
  end.join("\n")
91
91
 
@@ -96,7 +96,7 @@ module Apiwork
96
96
  properties = body_params.sort_by { |name, _param| name.to_s }.map do |param_name, param|
97
97
  key = @export.transform_key(param_name)
98
98
  ts_type = map_field(param)
99
- optional_marker = param.optional? ? '?' : ''
99
+ optional_marker = optional_in_output?(param) ? '?' : ''
100
100
  " #{key}#{optional_marker}: #{ts_type};"
101
101
  end.join("\n")
102
102
 
@@ -186,7 +186,7 @@ module Apiwork
186
186
  properties = param.shape.sort_by { |name, _field| name.to_s }.map do |name, field|
187
187
  key = @export.transform_key(name)
188
188
  ts_type = map_field(field)
189
- optional_marker = partial || field.optional? ? '?' : ''
189
+ optional_marker = partial || optional_in_output?(field) ? '?' : ''
190
190
  "#{key}#{optional_marker}: #{ts_type}"
191
191
  end.join('; ')
192
192
 
@@ -252,6 +252,14 @@ module Apiwork
252
252
  'unknown'
253
253
  end
254
254
 
255
+ def optional_in_output?(param)
256
+ return false unless param.optional?
257
+ return false if param.respond_to?(:default) && param.default?
258
+ return false if param.nullable?
259
+
260
+ true
261
+ end
262
+
255
263
  def type_reference(symbol)
256
264
  pascal_case(symbol)
257
265
  end
@@ -7,7 +7,6 @@ module Apiwork
7
7
  output :string
8
8
  file_extension '.ts'
9
9
 
10
- option :builders, default: false, type: :boolean
11
10
  option :version, default: '4', enum: %w[4], type: :string
12
11
 
13
12
  def generate
@@ -27,14 +26,6 @@ module Apiwork
27
26
  parts << ''
28
27
  end
29
28
 
30
- if options[:builders]
31
- builder_output = BuilderMapper.map(self, surface)
32
- if builder_output.present?
33
- parts << builder_output
34
- parts << ''
35
- end
36
- end
37
-
38
29
  parts.join("\n")
39
30
  end
40
31
 
@@ -458,10 +458,31 @@ module Apiwork
458
458
 
459
459
  def apply_modifiers(type, param, force_optional: nil)
460
460
  type += '.nullable()' if param.nullable?
461
+
462
+ has_default = param.respond_to?(:default) && param.default?
461
463
  optional = force_optional.nil? ? param.optional? : force_optional
462
- type += '.optional()' if optional
464
+
465
+ if has_default
466
+ type += ".default(#{serialize_default(param.default)})"
467
+ elsif optional
468
+ type += '.optional()'
469
+ end
470
+
463
471
  type
464
472
  end
473
+
474
+ def serialize_default(value)
475
+ case value
476
+ when String then "'#{value.gsub("'", "\\\\'")}'"
477
+ when Integer, Float then value.to_s
478
+ when BigDecimal then value.to_s('F')
479
+ when TrueClass, FalseClass then value.to_s
480
+ when Array then '[]'
481
+ when Hash then '{}'
482
+ when NilClass then 'null'
483
+ else value.to_s
484
+ end
485
+ end
465
486
  end
466
487
  end
467
488
  end
@@ -71,6 +71,7 @@ module Apiwork
71
71
  end
72
72
 
73
73
  def register_defaults!
74
+ register(Apiwork)
74
75
  register(OpenAPI)
75
76
  register(TypeScript)
76
77
  register(Zod)
@@ -23,6 +23,14 @@ module Apiwork
23
23
  @dump = dump
24
24
  end
25
25
 
26
+ # @api public
27
+ # The scope for this resource.
28
+ #
29
+ # @return [String, nil]
30
+ def scope
31
+ @dump[:scope]
32
+ end
33
+
26
34
  # @api public
27
35
  # The identifier for this resource.
28
36
  #
@@ -75,6 +83,7 @@ module Apiwork
75
83
  parent_identifiers: parent_identifiers,
76
84
  path: path,
77
85
  resources: resources.transform_values(&:to_h),
86
+ scope: scope,
78
87
  }
79
88
  end
80
89
  end
@@ -33,10 +33,9 @@ module Apiwork
33
33
 
34
34
  reference = resolve_type_reference(options[:type])
35
35
 
36
- {
36
+ result = {
37
37
  reference:,
38
38
  as: options[:as],
39
- default: options[:default],
40
39
  deprecated: options[:deprecated] == true,
41
40
  description: resolve_attribute_description(options),
42
41
  discriminator: nil,
@@ -55,14 +54,15 @@ module Apiwork
55
54
  value: options[:type] == :literal ? options[:value] : nil,
56
55
  variants: [],
57
56
  }
57
+ result[:default] = options[:default] if options.key?(:default)
58
+ result
58
59
  end
59
60
 
60
61
  def build_union_param(options)
61
62
  union_dump = build_union(options[:union])
62
63
 
63
- {
64
+ result = {
64
65
  as: options[:as],
65
- default: options[:default],
66
66
  deprecated: options[:deprecated] == true,
67
67
  description: resolve_attribute_description(options),
68
68
  discriminator: union_dump[:discriminator],
@@ -82,15 +82,16 @@ module Apiwork
82
82
  value: nil,
83
83
  variants: union_dump[:variants],
84
84
  }
85
+ result[:default] = options[:default] if options.key?(:default)
86
+ result
85
87
  end
86
88
 
87
89
  def build_custom_type_param(options)
88
90
  custom_type_name = options[:custom_type]
89
91
  custom_type_name = qualified_name(custom_type_name, @contract_param) if @contract_param.contract_class.resolve_custom_type(custom_type_name)
90
92
 
91
- {
93
+ result = {
92
94
  as: options[:as],
93
- default: options[:default],
94
95
  deprecated: options[:deprecated] == true,
95
96
  description: resolve_attribute_description(options),
96
97
  discriminator: nil,
@@ -110,6 +111,8 @@ module Apiwork
110
111
  value: nil,
111
112
  variants: [],
112
113
  }
114
+ result[:default] = options[:default] if options.key?(:default)
115
+ result
113
116
  end
114
117
 
115
118
  def resolve_type_reference(type_value)
@@ -163,7 +166,6 @@ module Apiwork
163
166
  result = {
164
167
  reference:,
165
168
  as: nil,
166
- default: nil,
167
169
  deprecated: false,
168
170
  description: nil,
169
171
  discriminator: nil,
@@ -205,7 +207,6 @@ module Apiwork
205
207
  {
206
208
  reference:,
207
209
  as: nil,
208
- default: nil,
209
210
  deprecated: false,
210
211
  description: nil,
211
212
  discriminator: nil,
@@ -312,7 +313,6 @@ module Apiwork
312
313
 
313
314
  result = {
314
315
  as: nil,
315
- default: nil,
316
316
  deprecated: false,
317
317
  description: nil,
318
318
  discriminator: union ? of.discriminator : nil,
@@ -343,7 +343,6 @@ module Apiwork
343
343
  def build_api_variant(variant)
344
344
  {
345
345
  as: nil,
346
- default: nil,
347
346
  deprecated: false,
348
347
  description: nil,
349
348
  discriminator: nil,
@@ -19,13 +19,15 @@ module Apiwork
19
19
  )
20
20
 
21
21
  resource_path = build_resource_path(formatted_segment)
22
+ contract_class = resolve_contract_class
22
23
 
23
24
  {
24
- actions: build_actions(resolve_contract_class, resource_path),
25
+ actions: build_actions(contract_class, resource_path),
25
26
  identifier: @resource.name.to_s,
26
27
  parent_identifiers: @parent_identifiers,
27
28
  path: resource_path,
28
29
  resources: build_nested_resources(resource_path),
30
+ scope: contract_class&.scope_prefix&.to_s,
29
31
  }
30
32
  end
31
33
 
@@ -39,8 +39,11 @@ module Apiwork
39
39
  end
40
40
 
41
41
  def build_type(qualified_name, type_definition)
42
+ scope = resolve_scope(type_definition.scope)
43
+
42
44
  if type_definition.union?
43
45
  {
46
+ scope:,
44
47
  deprecated: type_definition.deprecated?,
45
48
  description: resolve_type_description(qualified_name, type_definition),
46
49
  discriminator: type_definition.discriminator,
@@ -52,6 +55,7 @@ module Apiwork
52
55
  }
53
56
  else
54
57
  {
58
+ scope:,
55
59
  deprecated: type_definition.deprecated?,
56
60
  description: resolve_type_description(qualified_name, type_definition),
57
61
  discriminator: nil,
@@ -110,10 +114,9 @@ module Apiwork
110
114
  resolve_type_reference(options[:type], scope)
111
115
  end
112
116
 
113
- {
117
+ result = {
114
118
  reference:,
115
119
  as: options[:as],
116
- default: options[:default],
117
120
  deprecated: options[:deprecated] == true,
118
121
  description: resolve_param_description(name, options, scope),
119
122
  discriminator: nil,
@@ -132,6 +135,8 @@ module Apiwork
132
135
  value: options[:type] == :literal ? options[:value] : nil,
133
136
  variants: build_nested_variants(options[:shape]),
134
137
  }
138
+ result[:default] = options[:default] if options.key?(:default)
139
+ result
135
140
  end
136
141
 
137
142
  def build_variant(variant, scope)
@@ -141,7 +146,6 @@ module Apiwork
141
146
  {
142
147
  reference:,
143
148
  as: nil,
144
- default: nil,
145
149
  deprecated: false,
146
150
  description: nil,
147
151
  discriminator: nil,
@@ -217,7 +221,6 @@ module Apiwork
217
221
 
218
222
  result = {
219
223
  as: nil,
220
- default: nil,
221
224
  deprecated: false,
222
225
  description: nil,
223
226
  discriminator: union ? of.discriminator : nil,
@@ -260,12 +263,20 @@ module Apiwork
260
263
  deprecated: enum_definition.deprecated?,
261
264
  description: resolve_enum_description(qualified_name, enum_definition),
262
265
  example: enum_definition.example,
266
+ scope: resolve_scope(enum_definition.scope),
263
267
  values: enum_definition.values || [],
264
268
  }
265
269
  end
266
270
 
267
271
  private
268
272
 
273
+ def resolve_scope(scope)
274
+ return nil unless scope
275
+ return nil unless scope.respond_to?(:scope_prefix)
276
+
277
+ scope.scope_prefix.to_s
278
+ end
279
+
269
280
  def resolve_param_description(name, options, scope)
270
281
  return options[:description] if options[:description]
271
282
  return nil unless scope
@@ -38,6 +38,14 @@ module Apiwork
38
38
  @dump[:example]
39
39
  end
40
40
 
41
+ # @api public
42
+ # The scope for this enum.
43
+ #
44
+ # @return [String, nil]
45
+ def scope
46
+ @dump[:scope]
47
+ end
48
+
41
49
  # @api public
42
50
  # Whether this enum is deprecated.
43
51
  #
@@ -55,6 +63,7 @@ module Apiwork
55
63
  deprecated: deprecated?,
56
64
  description: description,
57
65
  example: example,
66
+ scope: scope,
58
67
  values: values,
59
68
  }
60
69
  end
@@ -23,6 +23,9 @@ module Apiwork
23
23
  # @api public
24
24
  # The default for this param.
25
25
  #
26
+ # Returns `nil` for both "no default" and "default is explicitly `nil`".
27
+ # Use {#default?} to distinguish these cases.
28
+ #
26
29
  # @return [Object, nil]
27
30
  def default
28
31
  @dump[:default]
@@ -48,6 +48,17 @@ module Apiwork
48
48
  @dump[:description]
49
49
  end
50
50
 
51
+ # @api public
52
+ # Whether this param has a default value.
53
+ #
54
+ # Use this to distinguish "no default" from "default is explicitly `nil`".
55
+ # The {#default} accessor returns `nil` in both cases.
56
+ #
57
+ # @return [Boolean]
58
+ def default?
59
+ @dump.key?(:default)
60
+ end
61
+
51
62
  # @api public
52
63
  # The tag for this param.
53
64
  #
@@ -23,6 +23,9 @@ module Apiwork
23
23
  # @api public
24
24
  # The default for this param.
25
25
  #
26
+ # Returns `nil` for both "no default" and "default is explicitly `nil`".
27
+ # Use {#default?} to distinguish these cases.
28
+ #
26
29
  # @return [Object, nil]
27
30
  def default
28
31
  @dump[:default]
@@ -23,6 +23,9 @@ module Apiwork
23
23
  # @api public
24
24
  # The default for this param.
25
25
  #
26
+ # Returns `nil` for both "no default" and "default is explicitly `nil`".
27
+ # Use {#default?} to distinguish these cases.
28
+ #
26
29
  # @return [Object, nil]
27
30
  def default
28
31
  @dump[:default]
@@ -23,6 +23,9 @@ module Apiwork
23
23
  # @api public
24
24
  # The default for this param.
25
25
  #
26
+ # Returns `nil` for both "no default" and "default is explicitly `nil`".
27
+ # Use {#default?} to distinguish these cases.
28
+ #
26
29
  # @return [Object, nil]
27
30
  def default
28
31
  @dump[:default]