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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7d4674d14687a7f9eb3547135c22df22c1cada2dc55b7f2b0f011462d20bf1a
4
- data.tar.gz: f580dcdf234e49b933b1636f2a2c14d2ca11caf4d5364a70ec574763978c834c
3
+ metadata.gz: 5949ff5143ec3bdd3c0349b639eb17306ce00d32c6567a618bdd1f8f3474d9fc
4
+ data.tar.gz: 3d8e835b75f3c85725e17459ae24c6b6154002b7733f76315826a776799a9860
5
5
  SHA512:
6
- metadata.gz: 33a16b4c739c5c13ba2374882d0997bea4cda29b00eef1c1afe7c810fe0247fab1ca6568bb225ac8e32617742b0ff9df85920518db7a4c062a223cdc688cee6b
7
- data.tar.gz: d707cbf17e3df65f57a523e7695b5ce2c7d009b3aa28599b071fb0ec1ba910281af9224493708214654a979ffe6362d661d6d5410bcec93ffadc08e37c9f6f96
6
+ metadata.gz: 93ac9cbc50161930b09f247309256590241180080a710c2e49621874a654a0d70d02888a9972ed342058047312371b5de9f47f0039f4f09525991e2a3ba252a7
7
+ data.tar.gz: 757058cf4777c10627862c7dd4142473c76027f340042d636978a805c8adaa82e2c74963e366d168cb2140273e5e845bc3fba8bb9fe76d5d5f5a21d5ad9ce208
data/README.md CHANGED
@@ -1,7 +1,12 @@
1
1
  # Apiwork
2
2
 
3
- ![Apiwork](docs/public/logo-light.svg#gh-light-mode-only)
4
- ![Apiwork](docs/public/logo-dark.svg#gh-dark-mode-only)
3
+ ![Apiwork](docs/public/banner.svg)
4
+
5
+ ---
6
+
7
+ > 🚧 **Apiwork is pre-1.0 and working toward a stable 1.0.** Minor versions can still bring breaking changes, so follow the [changelog](./CHANGELOG.md).
8
+
9
+ ---
5
10
 
6
11
  [![Gem Version](https://img.shields.io/gem/v/apiwork)](https://rubygems.org/gems/apiwork)
7
12
  [![CI](https://github.com/skiftle/apiwork/actions/workflows/ci.yml/badge.svg)](https://github.com/skiftle/apiwork/actions/workflows/ci.yml)
@@ -12,7 +12,12 @@ module Apiwork
12
12
  object(:error_issue) do |object|
13
13
  object.string(:code)
14
14
  object.string(:detail)
15
- object.array(:path, &:string)
15
+ object.array(:path) do |array|
16
+ array.of(:union) do |union|
17
+ union.variant(&:string)
18
+ union.variant(&:integer)
19
+ end
20
+ end
16
21
  object.string(:pointer)
17
22
  object.object(:meta)
18
23
  end
@@ -168,6 +168,7 @@ module Apiwork
168
168
  type: attribute.type,
169
169
  }
170
170
 
171
+ options[:default] = attribute.default if attribute.default? && action_name == :create
171
172
  options[:min] = attribute.min if attribute.min
172
173
  options[:max] = attribute.max if attribute.max
173
174
  options[:of] = attribute.of if attribute.of
@@ -763,6 +763,13 @@ module Apiwork
763
763
  result.transform { |hash| hash.deep_transform_keys { |key| transform_key(key).to_sym } }
764
764
  end
765
765
 
766
+ def prepare_error_response(response)
767
+ result = prepare_response(response)
768
+ return result if key_format == :keep
769
+
770
+ result.transform { |body| transform_issue_paths(body) }
771
+ end
772
+
766
773
  def type?(name, scope: nil)
767
774
  type_registry.exists?(name, scope:)
768
775
  end
@@ -838,6 +845,30 @@ module Apiwork
838
845
 
839
846
  private
840
847
 
848
+ def transform_issue_paths(body)
849
+ return body unless body.is_a?(Hash)
850
+ return body unless body[:issues].is_a?(Array)
851
+
852
+ body.merge(issues: body[:issues].map { |issue| transform_issue_path(issue) })
853
+ end
854
+
855
+ def transform_issue_path(issue)
856
+ return issue unless issue.is_a?(Hash)
857
+
858
+ result = issue.dup
859
+ result[:path] = transform_path_segments(result[:path]) if result[:path].is_a?(Array)
860
+ result[:pointer] = transform_pointer_segments(result[:pointer]) if result[:pointer].is_a?(String)
861
+ result
862
+ end
863
+
864
+ def transform_path_segments(segments)
865
+ segments.map { |segment| segment.is_a?(String) ? transform_key(segment) : segment }
866
+ end
867
+
868
+ def transform_pointer_segments(pointer)
869
+ pointer.split('/').map { |segment| segment.empty? ? segment : transform_key(segment) }.join('/')
870
+ end
871
+
841
872
  def extract_namespaces(mount_path)
842
873
  return [] if mount_path.nil? || mount_path == '/'
843
874
 
@@ -36,8 +36,8 @@ module Apiwork
36
36
  # The param type.
37
37
  # @param as [Symbol, nil] (nil)
38
38
  # The target attribute name.
39
- # @param default [Object, nil] (nil)
40
- # The default value.
39
+ # @param default [Object] (UNSET)
40
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
41
41
  # @param deprecated [Boolean] (false)
42
42
  # Whether deprecated. Metadata included in exports.
43
43
  # @param description [String, nil] (nil)
@@ -61,7 +61,7 @@ module Apiwork
61
61
  # @param of [Symbol, Hash, nil] (nil)
62
62
  # The element or value type. Arrays and records only.
63
63
  # @param optional [Boolean] (false)
64
- # Whether the param is optional.
64
+ # Whether the param is optional. Always `true` for params with a `default`.
65
65
  # @param required [Boolean] (false)
66
66
  # Whether the param is required.
67
67
  # @param shape [API::Object, API::Union, nil] (nil)
@@ -86,7 +86,7 @@ module Apiwork
86
86
  type: nil,
87
87
  as: nil,
88
88
  custom_type: nil,
89
- default: nil,
89
+ default: UNSET,
90
90
  deprecated: false,
91
91
  description: nil,
92
92
  discriminator: nil,
@@ -103,6 +103,7 @@ module Apiwork
103
103
  value: nil,
104
104
  &block
105
105
  )
106
+ optional = normalize_optional(optional, default)
106
107
  resolved_of = resolve_of(of, type, &block)
107
108
  resolved_shape = [:array, :record].include?(type) ? nil : (shape || build_shape(type, discriminator, &block))
108
109
  discriminator = resolved_of&.discriminator if type == :array
@@ -110,7 +111,6 @@ module Apiwork
110
111
  param_hash = {
111
112
  as:,
112
113
  custom_type:,
113
- default:,
114
114
  deprecated:,
115
115
  description:,
116
116
  discriminator:,
@@ -126,10 +126,11 @@ module Apiwork
126
126
  type:,
127
127
  value:,
128
128
  of: resolved_of,
129
- }
129
+ }.compact
130
+ param_hash[:default] = default unless UNSET.equal?(default)
130
131
  param_hash[:shape] = resolved_shape if resolved_shape
131
132
 
132
- @params[name] = (@params[name] || {}).merge(param_hash.compact)
133
+ @params[name] = (@params[name] || {}).merge(param_hash)
133
134
  end
134
135
 
135
136
  # @api public
@@ -139,8 +140,8 @@ module Apiwork
139
140
  # The param name.
140
141
  # @param as [Symbol, nil] (nil)
141
142
  # The target attribute name.
142
- # @param default [Object, nil] (nil)
143
- # The default value.
143
+ # @param default [Object] (UNSET)
144
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
144
145
  # @param deprecated [Boolean] (false)
145
146
  # Whether deprecated. Metadata included in exports.
146
147
  # @param description [String, nil] (nil)
@@ -148,7 +149,7 @@ module Apiwork
148
149
  # @param nullable [Boolean] (false)
149
150
  # Whether the value can be `null`.
150
151
  # @param optional [Boolean] (false)
151
- # Whether the param is optional.
152
+ # Whether the param is optional. Always `true` for params with a `default`.
152
153
  # @param required [Boolean] (false)
153
154
  # Whether the param is required.
154
155
  # @yield block for defining element type
@@ -167,7 +168,7 @@ module Apiwork
167
168
  def array(
168
169
  name,
169
170
  as: nil,
170
- default: nil,
171
+ default: UNSET,
171
172
  deprecated: false,
172
173
  description: nil,
173
174
  nullable: false,
@@ -202,8 +203,8 @@ module Apiwork
202
203
  # The param name.
203
204
  # @param as [Symbol, nil] (nil)
204
205
  # The target attribute name.
205
- # @param default [Object, nil] (nil)
206
- # The default value.
206
+ # @param default [Object] (UNSET)
207
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
207
208
  # @param deprecated [Boolean] (false)
208
209
  # Whether deprecated. Metadata included in exports.
209
210
  # @param description [String, nil] (nil)
@@ -211,7 +212,7 @@ module Apiwork
211
212
  # @param nullable [Boolean] (false)
212
213
  # Whether the value can be `null`.
213
214
  # @param optional [Boolean] (false)
214
- # Whether the param is optional.
215
+ # Whether the param is optional. Always `true` for params with a `default`.
215
216
  # @param required [Boolean] (false)
216
217
  # Whether the param is required.
217
218
  # @yield block for defining value type
@@ -230,7 +231,7 @@ module Apiwork
230
231
  def record(
231
232
  name,
232
233
  as: nil,
233
- default: nil,
234
+ default: UNSET,
234
235
  deprecated: false,
235
236
  description: nil,
236
237
  nullable: false,
@@ -76,7 +76,7 @@ module Apiwork
76
76
  required_error = validate_required(name, value, param_options, data, field_path)
77
77
  return [[required_error], NOT_SET] if required_error
78
78
 
79
- value = param_options[:default] if value.nil? && param_options[:default]
79
+ value = param_options[:default] if value.nil? && param_options.key?(:default)
80
80
 
81
81
  nullable_error = validate_nullable(name, value, param_options, data, field_path)
82
82
  return [[nullable_error], NOT_SET] if nullable_error
@@ -60,8 +60,8 @@ module Apiwork
60
60
  # The param type.
61
61
  # @param as [Symbol, nil] (nil)
62
62
  # The target attribute name.
63
- # @param default [Object, nil] (nil)
64
- # The default value.
63
+ # @param default [Object] (UNSET)
64
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
65
65
  # @param deprecated [Boolean] (false)
66
66
  # Whether deprecated. Metadata included in exports.
67
67
  # @param description [String, nil] (nil)
@@ -85,7 +85,7 @@ module Apiwork
85
85
  # @param of [Symbol, Hash, nil] (nil)
86
86
  # The element or value type. Arrays and records only.
87
87
  # @param optional [Boolean] (false)
88
- # Whether the param is optional.
88
+ # Whether the param is optional. Always `true` for params with a `default`.
89
89
  # @param required [Boolean] (false)
90
90
  # Whether the param is required.
91
91
  # @param shape [Contract::Object, Contract::Union, nil] (nil)
@@ -110,7 +110,7 @@ module Apiwork
110
110
  type: nil,
111
111
  as: nil,
112
112
  custom_type: nil,
113
- default: nil,
113
+ default: UNSET,
114
114
  deprecated: false,
115
115
  description: nil,
116
116
  discriminator: nil,
@@ -140,6 +140,8 @@ module Apiwork
140
140
 
141
141
  raise ConfigurationError, 'discriminator can only be used with type: :union' if discriminator && type != :union
142
142
 
143
+ optional = normalize_optional(optional, default)
144
+
143
145
  visited_types ||= @visited_types
144
146
  visited_types ||= Set.new
145
147
 
@@ -183,8 +185,8 @@ module Apiwork
183
185
  # The param name.
184
186
  # @param as [Symbol, nil] (nil)
185
187
  # The target attribute name.
186
- # @param default [Object, nil] (nil)
187
- # The default value.
188
+ # @param default [Object] (UNSET)
189
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
188
190
  # @param deprecated [Boolean] (false)
189
191
  # Whether deprecated. Metadata included in exports.
190
192
  # @param description [String, nil] (nil)
@@ -196,7 +198,7 @@ module Apiwork
196
198
  # @param nullable [Boolean] (false)
197
199
  # Whether the value can be `null`.
198
200
  # @param optional [Boolean] (false)
199
- # Whether the param is optional.
201
+ # Whether the param is optional. Always `true` for params with a `default`.
200
202
  # @param required [Boolean] (false)
201
203
  # Whether the param is required.
202
204
  # @yield block for defining element type
@@ -215,7 +217,7 @@ module Apiwork
215
217
  def array(
216
218
  name,
217
219
  as: nil,
218
- default: nil,
220
+ default: UNSET,
219
221
  deprecated: false,
220
222
  description: nil,
221
223
  max: nil,
@@ -254,8 +256,8 @@ module Apiwork
254
256
  # The param name.
255
257
  # @param as [Symbol, nil] (nil)
256
258
  # The target attribute name.
257
- # @param default [Object, nil] (nil)
258
- # The default value.
259
+ # @param default [Object] (UNSET)
260
+ # The default value. Omit to declare no default. Pass `nil` for an explicit null default. A param with a default is always optional.
259
261
  # @param deprecated [Boolean] (false)
260
262
  # Whether deprecated. Metadata included in exports.
261
263
  # @param description [String, nil] (nil)
@@ -263,7 +265,7 @@ module Apiwork
263
265
  # @param nullable [Boolean] (false)
264
266
  # Whether the value can be `null`.
265
267
  # @param optional [Boolean] (false)
266
- # Whether the param is optional.
268
+ # Whether the param is optional. Always `true` for params with a `default`.
267
269
  # @param required [Boolean] (false)
268
270
  # Whether the param is required.
269
271
  # @yield block for defining value type
@@ -282,7 +284,7 @@ module Apiwork
282
284
  def record(
283
285
  name,
284
286
  as: nil,
285
- default: nil,
287
+ default: UNSET,
286
288
  deprecated: false,
287
289
  description: nil,
288
290
  nullable: false,
@@ -395,18 +397,18 @@ module Apiwork
395
397
  def define_literal_param(name, as:, default:, deprecated:, description:, optional:, value:)
396
398
  raise ConfigurationError, 'Literal type requires a value parameter' if value.nil?
397
399
 
398
- @params[name] = (@params[name] || {}).merge(
399
- {
400
- as:,
401
- default:,
402
- deprecated:,
403
- description:,
404
- name:,
405
- optional:,
406
- value:,
407
- type: :literal,
408
- }.compact,
409
- )
400
+ params = {
401
+ as:,
402
+ deprecated:,
403
+ description:,
404
+ name:,
405
+ optional:,
406
+ value:,
407
+ type: :literal,
408
+ }.compact
409
+ params[:default] = default unless UNSET.equal?(default)
410
+
411
+ @params[name] = (@params[name] || {}).merge(params)
410
412
  end
411
413
 
412
414
  def define_union_param(name, as:, default:, discriminator:, optional:, options:, resolved_enum:, &block)
@@ -415,19 +417,19 @@ module Apiwork
415
417
  union = Union.new(@contract_class, discriminator:)
416
418
  block.arity.positive? ? yield(union) : union.instance_eval(&block)
417
419
 
418
- @params[name] = (@params[name] || {}).merge(
419
- {
420
- as:,
421
- default:,
422
- discriminator:,
423
- enum: resolved_enum,
424
- name:,
425
- optional:,
426
- type: :union,
427
- union:,
428
- **options,
429
- }.compact,
430
- )
420
+ params = {
421
+ as:,
422
+ discriminator:,
423
+ enum: resolved_enum,
424
+ name:,
425
+ optional:,
426
+ type: :union,
427
+ union:,
428
+ **options,
429
+ }.compact
430
+ params[:default] = default unless UNSET.equal?(default)
431
+
432
+ @params[name] = (@params[name] || {}).merge(params)
431
433
  end
432
434
 
433
435
  def define_regular_param(name, as:, default:, of:, optional:, options:, resolved_enum:, shape:, type:, visited_types:, &block)
@@ -514,20 +516,20 @@ module Apiwork
514
516
  end
515
517
  end
516
518
 
517
- @params[name] = (@params[name] || {}).merge(
518
- {
519
- as:,
520
- custom_type: type,
521
- default:,
522
- discriminator: type_definition.discriminator,
523
- enum: resolved_enum,
524
- name:,
525
- optional:,
526
- type: :union,
527
- union:,
528
- **options,
529
- }.compact,
530
- )
519
+ params = {
520
+ as:,
521
+ custom_type: type,
522
+ discriminator: type_definition.discriminator,
523
+ enum: resolved_enum,
524
+ name:,
525
+ optional:,
526
+ type: :union,
527
+ union:,
528
+ **options,
529
+ }.compact
530
+ params[:default] = default unless UNSET.equal?(default)
531
+
532
+ @params[name] = (@params[name] || {}).merge(params)
531
533
  end
532
534
 
533
535
  def define_custom_type_param(
@@ -557,38 +559,38 @@ module Apiwork
557
559
  block.arity.positive? ? yield(shape) : shape.instance_eval(&block)
558
560
  end
559
561
 
560
- @params[name] = (@params[name] || {}).merge(
561
- {
562
- as:,
563
- custom_type: type,
564
- default:,
565
- enum: resolved_enum,
566
- name:,
567
- of:,
568
- optional:,
569
- shape:,
570
- type: :object,
571
- **options,
572
- }.compact,
573
- )
562
+ params = {
563
+ as:,
564
+ custom_type: type,
565
+ enum: resolved_enum,
566
+ name:,
567
+ of:,
568
+ optional:,
569
+ shape:,
570
+ type: :object,
571
+ **options,
572
+ }.compact
573
+ params[:default] = default unless UNSET.equal?(default)
574
+
575
+ @params[name] = (@params[name] || {}).merge(params)
574
576
  end
575
577
 
576
578
  def define_standard_param(name, as:, default:, of:, optional:, options:, resolved_enum:, shape:, type:, &block)
577
579
  resolved_of = resolve_of(of, type, &block)
578
580
  resolved_shape = resolve_shape(shape, type, &block)
579
581
 
580
- @params[name] = (@params[name] || {}).merge(
581
- {
582
- as:,
583
- default:,
584
- enum: resolved_enum,
585
- name:,
586
- of: resolved_of,
587
- optional:,
588
- type:,
589
- **options,
590
- }.compact,
591
- )
582
+ params = {
583
+ as:,
584
+ enum: resolved_enum,
585
+ name:,
586
+ of: resolved_of,
587
+ optional:,
588
+ type:,
589
+ **options,
590
+ }.compact
591
+ params[:default] = default unless UNSET.equal?(default)
592
+
593
+ @params[name] = (@params[name] || {}).merge(params)
592
594
 
593
595
  @params[name][:shape] = resolved_shape if resolved_shape
594
596
  end
@@ -245,8 +245,12 @@ module Apiwork
245
245
 
246
246
  def render_error(error)
247
247
  representation_class = resource ? contract_class.representation_class : nil
248
- json = adapter.process_error(error, representation_class, context:)
249
- render json:, status: error.status
248
+ body = adapter.process_error(error, representation_class, context:)
249
+
250
+ response = Response.new(body:)
251
+ response = api_class.prepare_error_response(response)
252
+
253
+ render json: response.body, status: error.status
250
254
  end
251
255
 
252
256
  def contract_class
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module Export
5
+ class Apiwork < Base
6
+ export_name :apiwork
7
+ output :hash
8
+
9
+ def generate
10
+ ApiworkMapper.map(self, surface)
11
+ end
12
+
13
+ private
14
+
15
+ def surface
16
+ @surface ||= SurfaceResolver.resolve(api)
17
+ end
18
+ end
19
+ end
20
+ end