apiwork 0.0.0.pre → 0.1.2
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/LICENSE.txt +2 -2
- data/README.md +117 -1
- data/Rakefile +5 -3
- data/app/controllers/apiwork/errors_controller.rb +13 -0
- data/app/controllers/apiwork/exports_controller.rb +22 -0
- data/lib/apiwork/abstractable.rb +26 -0
- data/lib/apiwork/adapter/base.rb +369 -0
- data/lib/apiwork/adapter/builder/api/base.rb +66 -0
- data/lib/apiwork/adapter/builder/contract/base.rb +86 -0
- data/lib/apiwork/adapter/capability/api/base.rb +51 -0
- data/lib/apiwork/adapter/capability/api/scope.rb +64 -0
- data/lib/apiwork/adapter/capability/base.rb +291 -0
- data/lib/apiwork/adapter/capability/contract/base.rb +37 -0
- data/lib/apiwork/adapter/capability/contract/scope.rb +110 -0
- data/lib/apiwork/adapter/capability/operation/base.rb +172 -0
- data/lib/apiwork/adapter/capability/operation/metadata_shape.rb +165 -0
- data/lib/apiwork/adapter/capability/result.rb +21 -0
- data/lib/apiwork/adapter/capability/runner.rb +56 -0
- data/lib/apiwork/adapter/capability/transformer/request/base.rb +72 -0
- data/lib/apiwork/adapter/capability/transformer/response/base.rb +45 -0
- data/lib/apiwork/adapter/registry.rb +16 -0
- data/lib/apiwork/adapter/serializer/error/base.rb +72 -0
- data/lib/apiwork/adapter/serializer/error/default/api_builder.rb +32 -0
- data/lib/apiwork/adapter/serializer/error/default.rb +37 -0
- data/lib/apiwork/adapter/serializer/resource/base.rb +84 -0
- data/lib/apiwork/adapter/serializer/resource/default/contract_builder.rb +209 -0
- data/lib/apiwork/adapter/serializer/resource/default.rb +39 -0
- data/lib/apiwork/adapter/standard/capability/filtering/api_builder.rb +75 -0
- data/lib/apiwork/adapter/standard/capability/filtering/constants.rb +37 -0
- data/lib/apiwork/adapter/standard/capability/filtering/contract_builder.rb +193 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/builder.rb +47 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter/operator_builder.rb +36 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation/filter.rb +462 -0
- data/lib/apiwork/adapter/standard/capability/filtering/operation.rb +22 -0
- data/lib/apiwork/adapter/standard/capability/filtering/request_transformer.rb +47 -0
- data/lib/apiwork/adapter/standard/capability/filtering.rb +18 -0
- data/lib/apiwork/adapter/standard/capability/including/contract_builder.rb +169 -0
- data/lib/apiwork/adapter/standard/capability/including/operation.rb +20 -0
- data/lib/apiwork/adapter/standard/capability/including.rb +16 -0
- data/lib/apiwork/adapter/standard/capability/pagination/api_builder.rb +34 -0
- data/lib/apiwork/adapter/standard/capability/pagination/contract_builder.rb +35 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/cursor.rb +84 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate/offset.rb +66 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation/paginate.rb +24 -0
- data/lib/apiwork/adapter/standard/capability/pagination/operation.rb +24 -0
- data/lib/apiwork/adapter/standard/capability/pagination.rb +21 -0
- data/lib/apiwork/adapter/standard/capability/sorting/api_builder.rb +19 -0
- data/lib/apiwork/adapter/standard/capability/sorting/contract_builder.rb +84 -0
- data/lib/apiwork/adapter/standard/capability/sorting/operation/sort.rb +83 -0
- data/lib/apiwork/adapter/standard/capability/sorting/operation.rb +22 -0
- data/lib/apiwork/adapter/standard/capability/sorting.rb +17 -0
- data/lib/apiwork/adapter/standard/capability/writing/constants.rb +15 -0
- data/lib/apiwork/adapter/standard/capability/writing/contract_builder.rb +253 -0
- data/lib/apiwork/adapter/standard/capability/writing/operation/issue_mapper.rb +210 -0
- data/lib/apiwork/adapter/standard/capability/writing/operation.rb +32 -0
- data/lib/apiwork/adapter/standard/capability/writing/request_transformer.rb +37 -0
- data/lib/apiwork/adapter/standard/capability/writing.rb +17 -0
- data/lib/apiwork/adapter/standard/includes_resolver.rb +106 -0
- data/lib/apiwork/adapter/standard.rb +22 -0
- data/lib/apiwork/adapter/wrapper/base.rb +70 -0
- data/lib/apiwork/adapter/wrapper/collection/base.rb +60 -0
- data/lib/apiwork/adapter/wrapper/collection/default.rb +47 -0
- data/lib/apiwork/adapter/wrapper/error/base.rb +30 -0
- data/lib/apiwork/adapter/wrapper/error/default.rb +34 -0
- data/lib/apiwork/adapter/wrapper/member/base.rb +58 -0
- data/lib/apiwork/adapter/wrapper/member/default.rb +40 -0
- data/lib/apiwork/adapter/wrapper/shape.rb +203 -0
- data/lib/apiwork/adapter.rb +50 -0
- data/lib/apiwork/api/base.rb +802 -0
- data/lib/apiwork/api/element.rb +110 -0
- data/lib/apiwork/api/enum_registry/definition.rb +51 -0
- data/lib/apiwork/api/enum_registry.rb +98 -0
- data/lib/apiwork/api/info/contact.rb +67 -0
- data/lib/apiwork/api/info/license.rb +50 -0
- data/lib/apiwork/api/info/server.rb +50 -0
- data/lib/apiwork/api/info.rb +221 -0
- data/lib/apiwork/api/object.rb +235 -0
- data/lib/apiwork/api/registry.rb +33 -0
- data/lib/apiwork/api/representation_registry.rb +76 -0
- data/lib/apiwork/api/resource/action.rb +41 -0
- data/lib/apiwork/api/resource.rb +648 -0
- data/lib/apiwork/api/router.rb +104 -0
- data/lib/apiwork/api/type_registry/definition.rb +117 -0
- data/lib/apiwork/api/type_registry.rb +99 -0
- data/lib/apiwork/api/union.rb +49 -0
- data/lib/apiwork/api.rb +85 -0
- data/lib/apiwork/configurable.rb +71 -0
- data/lib/apiwork/configuration/option.rb +125 -0
- data/lib/apiwork/configuration/validatable.rb +25 -0
- data/lib/apiwork/configuration.rb +95 -0
- data/lib/apiwork/configuration_error.rb +6 -0
- data/lib/apiwork/constraint_error.rb +20 -0
- data/lib/apiwork/contract/action/request.rb +79 -0
- data/lib/apiwork/contract/action/response.rb +87 -0
- data/lib/apiwork/contract/action.rb +258 -0
- data/lib/apiwork/contract/base.rb +714 -0
- data/lib/apiwork/contract/element.rb +130 -0
- data/lib/apiwork/contract/object/coercer.rb +194 -0
- data/lib/apiwork/contract/object/deserializer.rb +101 -0
- data/lib/apiwork/contract/object/transformer.rb +95 -0
- data/lib/apiwork/contract/object/validator/result.rb +27 -0
- data/lib/apiwork/contract/object/validator.rb +734 -0
- data/lib/apiwork/contract/object.rb +566 -0
- data/lib/apiwork/contract/request_parser/result.rb +25 -0
- data/lib/apiwork/contract/request_parser.rb +72 -0
- data/lib/apiwork/contract/response_parser/result.rb +25 -0
- data/lib/apiwork/contract/response_parser.rb +35 -0
- data/lib/apiwork/contract/union.rb +56 -0
- data/lib/apiwork/contract_error.rb +9 -0
- data/lib/apiwork/controller.rb +300 -0
- data/lib/apiwork/domain_error.rb +13 -0
- data/lib/apiwork/element.rb +386 -0
- data/lib/apiwork/engine.rb +20 -0
- data/lib/apiwork/error.rb +6 -0
- data/lib/apiwork/error_code/definition.rb +63 -0
- data/lib/apiwork/error_code/registry.rb +18 -0
- data/lib/apiwork/error_code.rb +132 -0
- data/lib/apiwork/export/base.rb +291 -0
- data/lib/apiwork/export/open_api.rb +600 -0
- data/lib/apiwork/export/pipeline/writer.rb +66 -0
- data/lib/apiwork/export/pipeline.rb +84 -0
- data/lib/apiwork/export/registry.rb +16 -0
- data/lib/apiwork/export/surface_resolver.rb +189 -0
- data/lib/apiwork/export/type_analysis.rb +170 -0
- data/lib/apiwork/export/type_script.rb +23 -0
- data/lib/apiwork/export/type_script_mapper.rb +349 -0
- data/lib/apiwork/export/zod.rb +39 -0
- data/lib/apiwork/export/zod_mapper.rb +421 -0
- data/lib/apiwork/export.rb +80 -0
- data/lib/apiwork/http_error.rb +16 -0
- data/lib/apiwork/introspection/action/request.rb +66 -0
- data/lib/apiwork/introspection/action/response.rb +57 -0
- data/lib/apiwork/introspection/action.rb +124 -0
- data/lib/apiwork/introspection/api/info/contact.rb +59 -0
- data/lib/apiwork/introspection/api/info/license.rb +49 -0
- data/lib/apiwork/introspection/api/info/server.rb +50 -0
- data/lib/apiwork/introspection/api/info.rb +107 -0
- data/lib/apiwork/introspection/api/resource.rb +83 -0
- data/lib/apiwork/introspection/api.rb +92 -0
- data/lib/apiwork/introspection/contract.rb +63 -0
- data/lib/apiwork/introspection/dump/action.rb +101 -0
- data/lib/apiwork/introspection/dump/api.rb +119 -0
- data/lib/apiwork/introspection/dump/contract.rb +129 -0
- data/lib/apiwork/introspection/dump/param.rb +486 -0
- data/lib/apiwork/introspection/dump/resource.rb +112 -0
- data/lib/apiwork/introspection/dump/type.rb +339 -0
- data/lib/apiwork/introspection/dump.rb +17 -0
- data/lib/apiwork/introspection/enum.rb +63 -0
- data/lib/apiwork/introspection/error_code.rb +44 -0
- data/lib/apiwork/introspection/param/array.rb +88 -0
- data/lib/apiwork/introspection/param/base.rb +285 -0
- data/lib/apiwork/introspection/param/binary.rb +73 -0
- data/lib/apiwork/introspection/param/boolean.rb +73 -0
- data/lib/apiwork/introspection/param/date.rb +73 -0
- data/lib/apiwork/introspection/param/date_time.rb +73 -0
- data/lib/apiwork/introspection/param/decimal.rb +121 -0
- data/lib/apiwork/introspection/param/integer.rb +131 -0
- data/lib/apiwork/introspection/param/literal.rb +45 -0
- data/lib/apiwork/introspection/param/number.rb +121 -0
- data/lib/apiwork/introspection/param/object.rb +59 -0
- data/lib/apiwork/introspection/param/reference.rb +45 -0
- data/lib/apiwork/introspection/param/string.rb +122 -0
- data/lib/apiwork/introspection/param/time.rb +73 -0
- data/lib/apiwork/introspection/param/union.rb +57 -0
- data/lib/apiwork/introspection/param/unknown.rb +26 -0
- data/lib/apiwork/introspection/param/uuid.rb +73 -0
- data/lib/apiwork/introspection/param.rb +31 -0
- data/lib/apiwork/introspection/type.rb +129 -0
- data/lib/apiwork/introspection.rb +28 -0
- data/lib/apiwork/issue.rb +80 -0
- data/lib/apiwork/json_pointer.rb +21 -0
- data/lib/apiwork/object.rb +1618 -0
- data/lib/apiwork/reference_generator.rb +638 -0
- data/lib/apiwork/registry.rb +56 -0
- data/lib/apiwork/representation/association.rb +391 -0
- data/lib/apiwork/representation/attribute.rb +335 -0
- data/lib/apiwork/representation/base.rb +819 -0
- data/lib/apiwork/representation/deserializer.rb +95 -0
- data/lib/apiwork/representation/element.rb +128 -0
- data/lib/apiwork/representation/inheritance.rb +78 -0
- data/lib/apiwork/representation/model_detector.rb +75 -0
- data/lib/apiwork/representation/root_key.rb +35 -0
- data/lib/apiwork/representation/serializer.rb +127 -0
- data/lib/apiwork/request.rb +79 -0
- data/lib/apiwork/response.rb +56 -0
- data/lib/apiwork/union.rb +102 -0
- data/lib/apiwork/version.rb +2 -2
- data/lib/apiwork.rb +61 -3
- data/lib/generators/apiwork/api_generator.rb +38 -0
- data/lib/generators/apiwork/contract_generator.rb +25 -0
- data/lib/generators/apiwork/install_generator.rb +27 -0
- data/lib/generators/apiwork/representation_generator.rb +25 -0
- data/lib/generators/apiwork/templates/api/api.rb.tt +4 -0
- data/lib/generators/apiwork/templates/contract/contract.rb.tt +6 -0
- data/lib/generators/apiwork/templates/install/application_contract.rb.tt +5 -0
- data/lib/generators/apiwork/templates/install/application_representation.rb.tt +5 -0
- data/lib/generators/apiwork/templates/representation/representation.rb.tt +6 -0
- data/lib/tasks/apiwork.rake +102 -0
- metadata +319 -19
- data/.rubocop.yml +0 -8
- data/sig/apiwork.rbs +0 -4
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
# @api public
|
|
6
|
+
# Block context for defining request/response structure.
|
|
7
|
+
#
|
|
8
|
+
# Accessed via `body do` and `query do` inside contract actions,
|
|
9
|
+
# or `object :name do` at contract level to define reusable types.
|
|
10
|
+
#
|
|
11
|
+
# @see API::Object Block context for reusable types
|
|
12
|
+
#
|
|
13
|
+
# @example instance_eval style
|
|
14
|
+
# body do
|
|
15
|
+
# string :title
|
|
16
|
+
# decimal :amount
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example yield style
|
|
20
|
+
# body do |body|
|
|
21
|
+
# body.string :title
|
|
22
|
+
# body.decimal :amount
|
|
23
|
+
# end
|
|
24
|
+
class Object < Apiwork::Object
|
|
25
|
+
attr_reader :action_name,
|
|
26
|
+
:contract_class
|
|
27
|
+
|
|
28
|
+
def params
|
|
29
|
+
return @params if @merged.empty?
|
|
30
|
+
|
|
31
|
+
expanded = @params.dup
|
|
32
|
+
@merged.each do |type_name|
|
|
33
|
+
merged_type = @contract_class.api_class&.type_registry&.[](type_name)
|
|
34
|
+
next unless merged_type&.params
|
|
35
|
+
|
|
36
|
+
merged_type.params.each do |name, param_options|
|
|
37
|
+
expanded[name] ||= param_options
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
expanded
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize(contract_class, action_name: nil, visited_types: nil, wrapped: false)
|
|
44
|
+
super()
|
|
45
|
+
@contract_class = contract_class
|
|
46
|
+
@action_name = action_name
|
|
47
|
+
@wrapped = wrapped
|
|
48
|
+
@visited_types = visited_types
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api public
|
|
52
|
+
# Defines a param with explicit type.
|
|
53
|
+
#
|
|
54
|
+
# This is the verbose form. Prefer sugar methods (string, integer, etc.)
|
|
55
|
+
# for static definitions.
|
|
56
|
+
#
|
|
57
|
+
# @param name [Symbol]
|
|
58
|
+
# The param name.
|
|
59
|
+
# @param type [Symbol, nil] (nil) [:array, :binary, :boolean, :date, :datetime, :decimal, :integer, :literal, :number, :object, :string, :time, :union, :uuid]
|
|
60
|
+
# The param type.
|
|
61
|
+
# @param as [Symbol, nil] (nil)
|
|
62
|
+
# The target attribute name.
|
|
63
|
+
# @param default [Object, nil] (nil)
|
|
64
|
+
# The default value.
|
|
65
|
+
# @param deprecated [Boolean] (false)
|
|
66
|
+
# Whether deprecated. Metadata included in exports.
|
|
67
|
+
# @param description [String, nil] (nil)
|
|
68
|
+
# The description. Metadata included in exports.
|
|
69
|
+
# @param discriminator [Symbol, nil] (nil)
|
|
70
|
+
# The discriminator param name. Unions only.
|
|
71
|
+
# @param enum [Array, Symbol, nil] (nil)
|
|
72
|
+
# The allowed values or enum reference.
|
|
73
|
+
# @param example [Object, nil] (nil)
|
|
74
|
+
# The example value. Metadata included in exports.
|
|
75
|
+
# @param format [Symbol, nil] (nil) [:date, :datetime, :double, :email, :float, :hostname, :int32, :int64, :ipv4, :ipv6, :password, :url, :uuid]
|
|
76
|
+
# Format hint for exports. Does not change the type, but exports may add validation or documentation based on it.
|
|
77
|
+
# Valid formats by type: `:decimal`/`:number` (`:double`, `:float`), `:integer` (`:int32`, `:int64`),
|
|
78
|
+
# `:string` (`:date`, `:datetime`, `:email`, `:hostname`, `:ipv4`, `:ipv6`, `:password`, `:url`, `:uuid`).
|
|
79
|
+
# @param max [Integer, nil] (nil)
|
|
80
|
+
# The maximum. For `:array`: size. For `:decimal`, `:integer`, `:number`: value. For `:string`: length.
|
|
81
|
+
# @param min [Integer, nil] (nil)
|
|
82
|
+
# The minimum. For `:array`: size. For `:decimal`, `:integer`, `:number`: value. For `:string`: length.
|
|
83
|
+
# @param nullable [Boolean] (false)
|
|
84
|
+
# Whether the value can be `null`.
|
|
85
|
+
# @param of [Symbol, Hash, nil] (nil)
|
|
86
|
+
# The element type. Arrays only.
|
|
87
|
+
# @param optional [Boolean] (false)
|
|
88
|
+
# Whether the param is optional.
|
|
89
|
+
# @param required [Boolean] (false)
|
|
90
|
+
# Whether the param is required.
|
|
91
|
+
# @param shape [Contract::Object, Contract::Union, nil] (nil)
|
|
92
|
+
# The pre-built shape.
|
|
93
|
+
# @param value [Object, nil] (nil)
|
|
94
|
+
# The literal value.
|
|
95
|
+
# @yield block for nested structure
|
|
96
|
+
# @yieldparam shape [Contract::Object, Contract::Union, Contract::Element]
|
|
97
|
+
# @return [void]
|
|
98
|
+
#
|
|
99
|
+
# @example Dynamic param generation
|
|
100
|
+
# param_type = :string
|
|
101
|
+
# param :title, type: param_type
|
|
102
|
+
#
|
|
103
|
+
# @example Object with block
|
|
104
|
+
# param :address, type: :object do
|
|
105
|
+
# string :street
|
|
106
|
+
# string :city
|
|
107
|
+
# end
|
|
108
|
+
def param(
|
|
109
|
+
name,
|
|
110
|
+
type: nil,
|
|
111
|
+
as: nil,
|
|
112
|
+
default: nil,
|
|
113
|
+
deprecated: false,
|
|
114
|
+
description: nil,
|
|
115
|
+
discriminator: nil,
|
|
116
|
+
enum: nil,
|
|
117
|
+
example: nil,
|
|
118
|
+
format: nil,
|
|
119
|
+
max: nil,
|
|
120
|
+
min: nil,
|
|
121
|
+
nullable: false,
|
|
122
|
+
of: nil,
|
|
123
|
+
optional: false,
|
|
124
|
+
required: false,
|
|
125
|
+
shape: nil,
|
|
126
|
+
value: nil,
|
|
127
|
+
&block
|
|
128
|
+
)
|
|
129
|
+
options = {
|
|
130
|
+
deprecated:,
|
|
131
|
+
description:,
|
|
132
|
+
example:,
|
|
133
|
+
format:,
|
|
134
|
+
max:,
|
|
135
|
+
min:,
|
|
136
|
+
nullable:,
|
|
137
|
+
required:,
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
raise ConfigurationError, 'discriminator can only be used with type: :union' if discriminator && type != :union
|
|
141
|
+
|
|
142
|
+
visited_types ||= @visited_types
|
|
143
|
+
visited_types ||= Set.new
|
|
144
|
+
|
|
145
|
+
resolved_enum = resolve_enum(enum)
|
|
146
|
+
|
|
147
|
+
case type
|
|
148
|
+
when :literal
|
|
149
|
+
define_literal_param(name, as:, default:, deprecated:, description:, optional:, value:)
|
|
150
|
+
when :union
|
|
151
|
+
define_union_param(
|
|
152
|
+
name,
|
|
153
|
+
as:,
|
|
154
|
+
default:,
|
|
155
|
+
discriminator:,
|
|
156
|
+
optional:,
|
|
157
|
+
options:,
|
|
158
|
+
resolved_enum:,
|
|
159
|
+
&block
|
|
160
|
+
)
|
|
161
|
+
else
|
|
162
|
+
define_regular_param(
|
|
163
|
+
name,
|
|
164
|
+
as:,
|
|
165
|
+
default:,
|
|
166
|
+
of:,
|
|
167
|
+
optional:,
|
|
168
|
+
options:,
|
|
169
|
+
resolved_enum:,
|
|
170
|
+
shape:,
|
|
171
|
+
type:,
|
|
172
|
+
visited_types:,
|
|
173
|
+
&block
|
|
174
|
+
)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# @api public
|
|
179
|
+
# Defines an array param with element type.
|
|
180
|
+
#
|
|
181
|
+
# @param name [Symbol]
|
|
182
|
+
# The param name.
|
|
183
|
+
# @param as [Symbol, nil] (nil)
|
|
184
|
+
# The target attribute name.
|
|
185
|
+
# @param default [Object, nil] (nil)
|
|
186
|
+
# The default value.
|
|
187
|
+
# @param deprecated [Boolean] (false)
|
|
188
|
+
# Whether deprecated. Metadata included in exports.
|
|
189
|
+
# @param description [String, nil] (nil)
|
|
190
|
+
# The description. Metadata included in exports.
|
|
191
|
+
# @param nullable [Boolean] (false)
|
|
192
|
+
# Whether the value can be `null`.
|
|
193
|
+
# @param optional [Boolean] (false)
|
|
194
|
+
# Whether the param is optional.
|
|
195
|
+
# @param required [Boolean] (false)
|
|
196
|
+
# Whether the param is required.
|
|
197
|
+
# @yield block for defining element type
|
|
198
|
+
# @yieldparam element [Contract::Element]
|
|
199
|
+
# @return [void]
|
|
200
|
+
#
|
|
201
|
+
# @example instance_eval style
|
|
202
|
+
# array :tags do
|
|
203
|
+
# string
|
|
204
|
+
# end
|
|
205
|
+
#
|
|
206
|
+
# @example yield style
|
|
207
|
+
# array :tags do |element|
|
|
208
|
+
# element.string
|
|
209
|
+
# end
|
|
210
|
+
def array(
|
|
211
|
+
name,
|
|
212
|
+
as: nil,
|
|
213
|
+
default: nil,
|
|
214
|
+
deprecated: false,
|
|
215
|
+
description: nil,
|
|
216
|
+
nullable: false,
|
|
217
|
+
optional: false,
|
|
218
|
+
required: false,
|
|
219
|
+
&block
|
|
220
|
+
)
|
|
221
|
+
raise ConfigurationError, 'array requires a block' unless block
|
|
222
|
+
|
|
223
|
+
element = Element.new(@contract_class)
|
|
224
|
+
block.arity.positive? ? yield(element) : element.instance_eval(&block)
|
|
225
|
+
element.validate!
|
|
226
|
+
|
|
227
|
+
param(
|
|
228
|
+
name,
|
|
229
|
+
as:,
|
|
230
|
+
default:,
|
|
231
|
+
deprecated:,
|
|
232
|
+
description:,
|
|
233
|
+
nullable:,
|
|
234
|
+
optional:,
|
|
235
|
+
required:,
|
|
236
|
+
of: element,
|
|
237
|
+
type: :array,
|
|
238
|
+
)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def wrapped?
|
|
242
|
+
@wrapped
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def validate(data, current_depth: 0, max_depth: 10, path: [])
|
|
246
|
+
Validator.validate(self, data, current_depth:, max_depth:, path:)
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def coerce(data)
|
|
250
|
+
Coercer.coerce(self, data)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def deserialize(data)
|
|
254
|
+
Deserializer.deserialize(self, data)
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def transform(data)
|
|
258
|
+
Transformer.transform(self, data)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def copy_type_definition_params(type_definition, target_param)
|
|
262
|
+
return unless type_definition.object?
|
|
263
|
+
|
|
264
|
+
type_definition.params.each do |param_name, param_options|
|
|
265
|
+
nested_shape = param_options[:shape]
|
|
266
|
+
|
|
267
|
+
if param_options[:type] == :array && nested_shape.is_a?(Apiwork::API::Object)
|
|
268
|
+
target_param.param(param_name, **param_options.except(:name))
|
|
269
|
+
elsif nested_shape.is_a?(API::Object)
|
|
270
|
+
copy_nested_object_param(target_param, param_name, param_options, nested_shape)
|
|
271
|
+
elsif nested_shape.is_a?(API::Union)
|
|
272
|
+
copy_nested_union_param(target_param, param_name, param_options, nested_shape)
|
|
273
|
+
else
|
|
274
|
+
target_param.param(param_name, **param_options.except(:name, :shape))
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
private
|
|
280
|
+
|
|
281
|
+
def copy_nested_object_param(target_param, param_name, param_options, nested_shape)
|
|
282
|
+
target_param.param(
|
|
283
|
+
param_name,
|
|
284
|
+
type: param_options[:type],
|
|
285
|
+
**param_options.except(:name, :type, :shape),
|
|
286
|
+
) do
|
|
287
|
+
nested_shape.params.each do |nested_name, nested_param_options|
|
|
288
|
+
param(nested_name, **nested_param_options.except(:name, :shape))
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def copy_nested_union_param(target_param, param_name, param_options, nested_shape)
|
|
294
|
+
target_param.param(
|
|
295
|
+
param_name,
|
|
296
|
+
type: param_options[:type],
|
|
297
|
+
**param_options.except(:name, :type, :shape),
|
|
298
|
+
) do
|
|
299
|
+
nested_shape.variants.each do |variant|
|
|
300
|
+
if variant[:shape].is_a?(API::Object)
|
|
301
|
+
variant tag: variant[:tag] do
|
|
302
|
+
object do
|
|
303
|
+
variant[:shape].params.each do |name, param_options|
|
|
304
|
+
param(name, **param_options.except(:name, :shape))
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
elsif variant[:custom_type]
|
|
309
|
+
variant tag: variant[:tag] do
|
|
310
|
+
reference variant[:custom_type]
|
|
311
|
+
end
|
|
312
|
+
else
|
|
313
|
+
variant tag: variant[:tag] do
|
|
314
|
+
send(variant[:type])
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
def define_literal_param(name, as:, default:, deprecated:, description:, optional:, value:)
|
|
322
|
+
raise ConfigurationError, 'Literal type requires a value parameter' if value.nil?
|
|
323
|
+
|
|
324
|
+
@params[name] = (@params[name] || {}).merge(
|
|
325
|
+
{
|
|
326
|
+
as:,
|
|
327
|
+
default:,
|
|
328
|
+
deprecated:,
|
|
329
|
+
description:,
|
|
330
|
+
name:,
|
|
331
|
+
optional:,
|
|
332
|
+
value:,
|
|
333
|
+
type: :literal,
|
|
334
|
+
}.compact,
|
|
335
|
+
)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def define_union_param(name, as:, default:, discriminator:, optional:, options:, resolved_enum:, &block)
|
|
339
|
+
raise ConfigurationError, 'Union type requires a block with variant definitions' unless block_given?
|
|
340
|
+
|
|
341
|
+
union = Union.new(@contract_class, discriminator:)
|
|
342
|
+
block.arity.positive? ? yield(union) : union.instance_eval(&block)
|
|
343
|
+
|
|
344
|
+
@params[name] = (@params[name] || {}).merge(
|
|
345
|
+
{
|
|
346
|
+
as:,
|
|
347
|
+
default:,
|
|
348
|
+
discriminator:,
|
|
349
|
+
enum: resolved_enum,
|
|
350
|
+
name:,
|
|
351
|
+
optional:,
|
|
352
|
+
type: :union,
|
|
353
|
+
union:,
|
|
354
|
+
**options,
|
|
355
|
+
}.compact,
|
|
356
|
+
)
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
def define_regular_param(name, as:, default:, of:, optional:, options:, resolved_enum:, shape:, type:, visited_types:, &block)
|
|
360
|
+
type_definition = @contract_class.resolve_custom_type(type)
|
|
361
|
+
|
|
362
|
+
if type_definition
|
|
363
|
+
expansion_key = [@contract_class.object_id, type]
|
|
364
|
+
|
|
365
|
+
type_definition = nil if visited_types.include?(expansion_key)
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
if type_definition&.object?
|
|
369
|
+
define_custom_type_param(
|
|
370
|
+
name,
|
|
371
|
+
as:,
|
|
372
|
+
default:,
|
|
373
|
+
of:,
|
|
374
|
+
optional:,
|
|
375
|
+
options:,
|
|
376
|
+
resolved_enum:,
|
|
377
|
+
type:,
|
|
378
|
+
type_definition:,
|
|
379
|
+
visited_types:,
|
|
380
|
+
&block
|
|
381
|
+
)
|
|
382
|
+
elsif type_definition&.union?
|
|
383
|
+
define_custom_union_type_param(
|
|
384
|
+
name,
|
|
385
|
+
as:,
|
|
386
|
+
default:,
|
|
387
|
+
optional:,
|
|
388
|
+
options:,
|
|
389
|
+
resolved_enum:,
|
|
390
|
+
type:,
|
|
391
|
+
type_definition:,
|
|
392
|
+
)
|
|
393
|
+
else
|
|
394
|
+
define_standard_param(
|
|
395
|
+
name,
|
|
396
|
+
as:,
|
|
397
|
+
default:,
|
|
398
|
+
of:,
|
|
399
|
+
optional:,
|
|
400
|
+
options:,
|
|
401
|
+
resolved_enum:,
|
|
402
|
+
shape:,
|
|
403
|
+
type:,
|
|
404
|
+
&block
|
|
405
|
+
)
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def define_custom_union_type_param(
|
|
410
|
+
name,
|
|
411
|
+
type:,
|
|
412
|
+
type_definition:,
|
|
413
|
+
resolved_enum:,
|
|
414
|
+
optional:,
|
|
415
|
+
default:,
|
|
416
|
+
as:,
|
|
417
|
+
options:
|
|
418
|
+
)
|
|
419
|
+
union = Union.new(@contract_class, discriminator: type_definition.discriminator)
|
|
420
|
+
|
|
421
|
+
type_definition.variants.each do |variant|
|
|
422
|
+
if variant[:shape].is_a?(API::Object)
|
|
423
|
+
union.variant tag: variant[:tag] do
|
|
424
|
+
object do
|
|
425
|
+
variant[:shape].params.each do |name, param_options|
|
|
426
|
+
param(name, **param_options.except(:name, :shape))
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
elsif variant[:custom_type]
|
|
431
|
+
union.variant tag: variant[:tag] do
|
|
432
|
+
reference variant[:custom_type]
|
|
433
|
+
end
|
|
434
|
+
else
|
|
435
|
+
union.variant tag: variant[:tag] do
|
|
436
|
+
send(variant[:type])
|
|
437
|
+
end
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
@params[name] = (@params[name] || {}).merge(
|
|
442
|
+
{
|
|
443
|
+
as:,
|
|
444
|
+
custom_type: type,
|
|
445
|
+
default:,
|
|
446
|
+
discriminator: type_definition.discriminator,
|
|
447
|
+
enum: resolved_enum,
|
|
448
|
+
name:,
|
|
449
|
+
optional:,
|
|
450
|
+
type: :union,
|
|
451
|
+
union:,
|
|
452
|
+
**options,
|
|
453
|
+
}.compact,
|
|
454
|
+
)
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def define_custom_type_param(
|
|
458
|
+
name,
|
|
459
|
+
type:,
|
|
460
|
+
type_definition:,
|
|
461
|
+
resolved_enum:,
|
|
462
|
+
optional:,
|
|
463
|
+
default:,
|
|
464
|
+
of:,
|
|
465
|
+
as:,
|
|
466
|
+
visited_types:,
|
|
467
|
+
options:,
|
|
468
|
+
&block
|
|
469
|
+
)
|
|
470
|
+
shape = Object.new(
|
|
471
|
+
@contract_class,
|
|
472
|
+
action_name: @action_name,
|
|
473
|
+
visited_types: visited_types.dup.add([@contract_class.object_id, type]),
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
copy_type_definition_params(type_definition, shape)
|
|
477
|
+
|
|
478
|
+
if block_given?
|
|
479
|
+
block.arity.positive? ? yield(shape) : shape.instance_eval(&block)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
@params[name] = (@params[name] || {}).merge(
|
|
483
|
+
{
|
|
484
|
+
as:,
|
|
485
|
+
custom_type: type,
|
|
486
|
+
default:,
|
|
487
|
+
enum: resolved_enum,
|
|
488
|
+
name:,
|
|
489
|
+
of:,
|
|
490
|
+
optional:,
|
|
491
|
+
shape:,
|
|
492
|
+
type: :object,
|
|
493
|
+
**options,
|
|
494
|
+
}.compact,
|
|
495
|
+
)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def define_standard_param(name, as:, default:, of:, optional:, options:, resolved_enum:, shape:, type:, &block)
|
|
499
|
+
resolved_of = resolve_of(of, type, &block)
|
|
500
|
+
resolved_shape = resolve_shape(shape, type, &block)
|
|
501
|
+
|
|
502
|
+
@params[name] = (@params[name] || {}).merge(
|
|
503
|
+
{
|
|
504
|
+
as:,
|
|
505
|
+
default:,
|
|
506
|
+
enum: resolved_enum,
|
|
507
|
+
name:,
|
|
508
|
+
of: resolved_of,
|
|
509
|
+
optional:,
|
|
510
|
+
type:,
|
|
511
|
+
**options,
|
|
512
|
+
}.compact,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
@params[name][:shape] = resolved_shape if resolved_shape
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def resolve_of(of, type, &block)
|
|
519
|
+
return nil unless type == :array
|
|
520
|
+
|
|
521
|
+
if block_given?
|
|
522
|
+
element = Element.new(@contract_class)
|
|
523
|
+
block.arity.positive? ? yield(element) : element.instance_eval(&block)
|
|
524
|
+
element.validate!
|
|
525
|
+
element
|
|
526
|
+
elsif of.is_a?(Symbol)
|
|
527
|
+
wrap_symbol_in_element(of)
|
|
528
|
+
else
|
|
529
|
+
of
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
|
|
533
|
+
def resolve_shape(shape, type, &block)
|
|
534
|
+
return nil if type == :array
|
|
535
|
+
|
|
536
|
+
if shape
|
|
537
|
+
shape
|
|
538
|
+
elsif block_given?
|
|
539
|
+
nested_shape = Object.new(@contract_class, action_name: @action_name)
|
|
540
|
+
block.arity.positive? ? yield(nested_shape) : nested_shape.instance_eval(&block)
|
|
541
|
+
nested_shape
|
|
542
|
+
end
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
def wrap_symbol_in_element(type_symbol)
|
|
546
|
+
element = Element.new(@contract_class)
|
|
547
|
+
element.of(type_symbol)
|
|
548
|
+
element
|
|
549
|
+
end
|
|
550
|
+
|
|
551
|
+
def resolve_enum(enum)
|
|
552
|
+
return nil if enum.nil?
|
|
553
|
+
return enum if enum.is_a?(Array)
|
|
554
|
+
|
|
555
|
+
raise ConfigurationError, "enum must be a Symbol (reference) or Array (inline values), got #{enum.class}" unless enum.is_a?(Symbol)
|
|
556
|
+
|
|
557
|
+
unless @contract_class.enum?(enum)
|
|
558
|
+
raise ConfigurationError,
|
|
559
|
+
"Enum :#{enum} not found. Define it using `enum :#{enum}, %w[...]` in definition scope."
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
enum
|
|
563
|
+
end
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
class RequestParser
|
|
6
|
+
class Result
|
|
7
|
+
attr_reader :issues,
|
|
8
|
+
:request
|
|
9
|
+
|
|
10
|
+
def initialize(issues: [], request:)
|
|
11
|
+
@request = request
|
|
12
|
+
@issues = issues
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid?
|
|
16
|
+
issues.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def invalid?
|
|
20
|
+
issues.any?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
class RequestParser
|
|
6
|
+
class << self
|
|
7
|
+
def parse(contract_class, action_name, request, coerce: false)
|
|
8
|
+
new(contract_class, action_name).parse(request, coerce:)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(contract_class, action_name)
|
|
13
|
+
@contract_class = contract_class
|
|
14
|
+
@action_name = action_name.to_sym
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse(request, coerce: false)
|
|
18
|
+
request = coerce_request(request) if coerce
|
|
19
|
+
|
|
20
|
+
parsed_query, query_issues = parse_part(request.query, :query)
|
|
21
|
+
parsed_body, body_issues = parse_part(request.body, :body)
|
|
22
|
+
|
|
23
|
+
Result.new(
|
|
24
|
+
issues: query_issues + body_issues,
|
|
25
|
+
request: Request.new(body: parsed_body, query: parsed_query),
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def coerce_request(request)
|
|
32
|
+
request
|
|
33
|
+
.transform_query { |query| coerce(query, shape_for(:query)) }
|
|
34
|
+
.transform_body { |body| coerce(body, shape_for(:body)) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_part(data, part_type)
|
|
38
|
+
shape = shape_for(part_type)
|
|
39
|
+
return [{}, []] if shape.nil? && data.blank?
|
|
40
|
+
return [data, []] unless shape
|
|
41
|
+
|
|
42
|
+
validated = shape.validate(data)
|
|
43
|
+
|
|
44
|
+
return [{}, validated.issues] if validated.invalid?
|
|
45
|
+
|
|
46
|
+
[shape.transform(shape.deserialize(validated.params)), []]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def action
|
|
50
|
+
@action ||= @contract_class.action_for(@action_name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def shape_for(part_type)
|
|
54
|
+
return unless action
|
|
55
|
+
|
|
56
|
+
case part_type
|
|
57
|
+
when :query
|
|
58
|
+
action.request.query
|
|
59
|
+
when :body
|
|
60
|
+
action.request.body
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def coerce(data, shape)
|
|
65
|
+
return data unless shape
|
|
66
|
+
return data unless data.is_a?(Hash)
|
|
67
|
+
|
|
68
|
+
shape.coerce(data)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
class ResponseParser
|
|
6
|
+
class Result
|
|
7
|
+
attr_reader :issues,
|
|
8
|
+
:response
|
|
9
|
+
|
|
10
|
+
def initialize(issues: [], response:)
|
|
11
|
+
@response = response
|
|
12
|
+
@issues = issues
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def valid?
|
|
16
|
+
issues.empty?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def invalid?
|
|
20
|
+
issues.any?
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|