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,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
# @api public
|
|
5
|
+
# Typed access to configuration values with automatic defaults.
|
|
6
|
+
#
|
|
7
|
+
# @see API::Base#adapter_config
|
|
8
|
+
# @see Representation::Base.adapter_config
|
|
9
|
+
#
|
|
10
|
+
# @example Reading values
|
|
11
|
+
# config.pagination.default_size # => 20
|
|
12
|
+
# config.pagination.strategy # => :offset
|
|
13
|
+
#
|
|
14
|
+
# @example Using dig for dynamic access
|
|
15
|
+
# config.dig(:pagination, :default_size) # => 20
|
|
16
|
+
class Configuration
|
|
17
|
+
def initialize(options_source, storage = {})
|
|
18
|
+
@options = extract_options(options_source)
|
|
19
|
+
@storage = storage
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def method_missing(name, *args, &block)
|
|
23
|
+
option = @options[name]
|
|
24
|
+
raise ConfigurationError, "Unknown option: #{name}" unless option
|
|
25
|
+
|
|
26
|
+
if args.empty? && !block
|
|
27
|
+
stored = @storage[name]
|
|
28
|
+
|
|
29
|
+
if option.nested?
|
|
30
|
+
nested_storage = stored || {}
|
|
31
|
+
return Configuration.new(option, nested_storage)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return stored.nil? ? option.default : stored
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
value = args.first
|
|
38
|
+
if block && option.nested?
|
|
39
|
+
@storage[name] ||= {}
|
|
40
|
+
nested = Configuration.new(option, @storage[name])
|
|
41
|
+
block.arity.positive? ? yield(nested) : nested.instance_eval(&block)
|
|
42
|
+
else
|
|
43
|
+
option.validate!(value)
|
|
44
|
+
@storage[name] = value
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def respond_to_missing?(name, include_private = false)
|
|
49
|
+
@options.key?(name) || super
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def merge(hash)
|
|
53
|
+
Configuration.new(@options, @storage.deep_merge(hash))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @api public
|
|
57
|
+
# Accesses nested configuration values by key path.
|
|
58
|
+
#
|
|
59
|
+
# @param keys [Symbol]
|
|
60
|
+
# One or more keys to traverse.
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# config.dig(:pagination) # => #<Apiwork::Configuration:...>
|
|
64
|
+
# config.dig(:pagination, :strategy) # => :offset
|
|
65
|
+
def dig(*keys)
|
|
66
|
+
keys.compact.reduce(self) { |config, key| config.public_send(key) }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @api public
|
|
70
|
+
# Converts the configuration to a hash.
|
|
71
|
+
#
|
|
72
|
+
# @return [Hash]
|
|
73
|
+
#
|
|
74
|
+
# @example
|
|
75
|
+
# config.to_h # => { pagination: { strategy: :offset, default_size: 20 } }
|
|
76
|
+
def to_h
|
|
77
|
+
@options.each_with_object({}) do |(name, option), result|
|
|
78
|
+
if option.nested?
|
|
79
|
+
result[name] = Configuration.new(option, @storage[name] || {}).to_h
|
|
80
|
+
else
|
|
81
|
+
stored = @storage[name]
|
|
82
|
+
result[name] = stored.nil? ? option.default : stored
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
def extract_options(source)
|
|
90
|
+
return source if source.is_a?(Hash)
|
|
91
|
+
|
|
92
|
+
source.respond_to?(:options) ? source.options : source.children
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
class ConstraintError < Error
|
|
5
|
+
attr_reader :issues
|
|
6
|
+
|
|
7
|
+
def initialize(issues)
|
|
8
|
+
@issues = Array(issues)
|
|
9
|
+
super(@issues.map(&:detail).join('; '))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def error_code
|
|
13
|
+
@error_code ||= ErrorCode.find!(:bad_request)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def status
|
|
17
|
+
error_code.status
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
class Action
|
|
6
|
+
# @api public
|
|
7
|
+
# Defines query and body for a request.
|
|
8
|
+
#
|
|
9
|
+
# Returns {Contract::Object} via `query` and `body`.
|
|
10
|
+
class Request
|
|
11
|
+
attr_reader :action_name,
|
|
12
|
+
:contract_class
|
|
13
|
+
|
|
14
|
+
def initialize(contract_class, action_name)
|
|
15
|
+
@contract_class = contract_class
|
|
16
|
+
@action_name = action_name
|
|
17
|
+
@query = nil
|
|
18
|
+
@body = nil
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api public
|
|
22
|
+
# Defines query parameters for this request.
|
|
23
|
+
#
|
|
24
|
+
# Query parameters are parsed from the URL query string.
|
|
25
|
+
#
|
|
26
|
+
# @yield block for defining query params (instance_eval style)
|
|
27
|
+
# @yieldparam query [Contract::Object]
|
|
28
|
+
# @return [Contract::Object]
|
|
29
|
+
#
|
|
30
|
+
# @example instance_eval style
|
|
31
|
+
# query do
|
|
32
|
+
# integer? :page
|
|
33
|
+
# string? :status, enum: :status
|
|
34
|
+
# end
|
|
35
|
+
#
|
|
36
|
+
# @example yield style
|
|
37
|
+
# query do |query|
|
|
38
|
+
# query.integer? :page
|
|
39
|
+
# query.string? :status, enum: :status
|
|
40
|
+
# end
|
|
41
|
+
def query(&block)
|
|
42
|
+
@query ||= Object.new(@contract_class, action_name: @action_name)
|
|
43
|
+
if block
|
|
44
|
+
block.arity.positive? ? yield(@query) : @query.instance_eval(&block)
|
|
45
|
+
end
|
|
46
|
+
@query
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @api public
|
|
50
|
+
# Defines the request body for this request.
|
|
51
|
+
#
|
|
52
|
+
# Body is parsed from the JSON request body.
|
|
53
|
+
#
|
|
54
|
+
# @yield block for defining body params (instance_eval style)
|
|
55
|
+
# @yieldparam body [Contract::Object]
|
|
56
|
+
# @return [Contract::Object]
|
|
57
|
+
#
|
|
58
|
+
# @example instance_eval style
|
|
59
|
+
# body do
|
|
60
|
+
# string :title
|
|
61
|
+
# decimal :amount
|
|
62
|
+
# end
|
|
63
|
+
#
|
|
64
|
+
# @example yield style
|
|
65
|
+
# body do |body|
|
|
66
|
+
# body.string :title
|
|
67
|
+
# body.decimal :amount
|
|
68
|
+
# end
|
|
69
|
+
def body(&block)
|
|
70
|
+
@body ||= Object.new(@contract_class, action_name: @action_name)
|
|
71
|
+
if block
|
|
72
|
+
block.arity.positive? ? yield(@body) : @body.instance_eval(&block)
|
|
73
|
+
end
|
|
74
|
+
@body
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
class Action
|
|
6
|
+
# @api public
|
|
7
|
+
# Defines body for a response.
|
|
8
|
+
#
|
|
9
|
+
# Returns {Contract::Object} via `body`.
|
|
10
|
+
class Response
|
|
11
|
+
attr_reader :action_name,
|
|
12
|
+
:contract_class
|
|
13
|
+
|
|
14
|
+
attr_accessor :result_wrapper
|
|
15
|
+
|
|
16
|
+
def initialize(contract_class, action_name)
|
|
17
|
+
@contract_class = contract_class
|
|
18
|
+
@action_name = action_name
|
|
19
|
+
@body = nil
|
|
20
|
+
@result_wrapper = nil
|
|
21
|
+
@no_content = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @api public
|
|
25
|
+
# Whether this response has no content.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
def no_content?
|
|
29
|
+
@no_content
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @api public
|
|
33
|
+
# Declares this response as 204 No Content.
|
|
34
|
+
#
|
|
35
|
+
# Use for actions that don't return a response body,
|
|
36
|
+
# like DELETE or actions that only perform side effects.
|
|
37
|
+
#
|
|
38
|
+
# @return [void]
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# action :destroy do
|
|
42
|
+
# response { no_content! }
|
|
43
|
+
# end
|
|
44
|
+
#
|
|
45
|
+
# @example Archive action
|
|
46
|
+
# action :archive do
|
|
47
|
+
# response { no_content! }
|
|
48
|
+
# end
|
|
49
|
+
def no_content!
|
|
50
|
+
@no_content = true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api public
|
|
54
|
+
# Defines the response body for this response.
|
|
55
|
+
#
|
|
56
|
+
# @yield block for defining body params (instance_eval style)
|
|
57
|
+
# @yieldparam body [Contract::Object]
|
|
58
|
+
# @return [Contract::Object]
|
|
59
|
+
#
|
|
60
|
+
# @example instance_eval style
|
|
61
|
+
# body do
|
|
62
|
+
# integer :id
|
|
63
|
+
# string :title
|
|
64
|
+
# decimal :amount
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# @example yield style
|
|
68
|
+
# body do |body|
|
|
69
|
+
# body.integer :id
|
|
70
|
+
# body.string :title
|
|
71
|
+
# body.decimal :amount
|
|
72
|
+
# end
|
|
73
|
+
def body(&block)
|
|
74
|
+
if block
|
|
75
|
+
@body ||= Object.new(
|
|
76
|
+
@contract_class,
|
|
77
|
+
action_name: @action_name,
|
|
78
|
+
wrapped: true,
|
|
79
|
+
)
|
|
80
|
+
block.arity.positive? ? yield(@body) : @body.instance_eval(&block)
|
|
81
|
+
end
|
|
82
|
+
@body
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Contract
|
|
5
|
+
# @api public
|
|
6
|
+
# Defines request/response structure for an action.
|
|
7
|
+
#
|
|
8
|
+
# Returns {Action::Request} via `request` and {Action::Response} via `response`.
|
|
9
|
+
class Action
|
|
10
|
+
attr_reader :contract_class,
|
|
11
|
+
:name
|
|
12
|
+
|
|
13
|
+
def initialize(contract_class, name, replace: false)
|
|
14
|
+
@name = name
|
|
15
|
+
@contract_class = contract_class
|
|
16
|
+
@reset_request = replace
|
|
17
|
+
@reset_response = replace
|
|
18
|
+
@request = nil
|
|
19
|
+
@response = nil
|
|
20
|
+
@raises = []
|
|
21
|
+
@summary = nil
|
|
22
|
+
@description = nil
|
|
23
|
+
@tags = nil
|
|
24
|
+
@deprecated = nil
|
|
25
|
+
@operation_id = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @api public
|
|
29
|
+
# The summary for this action.
|
|
30
|
+
#
|
|
31
|
+
# Used in generated specs as the operation summary.
|
|
32
|
+
#
|
|
33
|
+
# @param value [String, nil] (nil)
|
|
34
|
+
# The summary.
|
|
35
|
+
# @return [String, nil]
|
|
36
|
+
#
|
|
37
|
+
# @example
|
|
38
|
+
# action :create do
|
|
39
|
+
# summary 'Create a new invoice'
|
|
40
|
+
# end
|
|
41
|
+
def summary(value = nil)
|
|
42
|
+
return @summary if value.nil?
|
|
43
|
+
|
|
44
|
+
@summary = value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @api public
|
|
48
|
+
# The description for this action.
|
|
49
|
+
#
|
|
50
|
+
# Used in generated specs as the operation description.
|
|
51
|
+
# Supports Markdown formatting.
|
|
52
|
+
#
|
|
53
|
+
# @param value [String, nil] (nil)
|
|
54
|
+
# The description.
|
|
55
|
+
# @return [String, nil]
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# action :create do
|
|
59
|
+
# description 'Creates a new invoice and sends notification email.'
|
|
60
|
+
# end
|
|
61
|
+
def description(value = nil)
|
|
62
|
+
return @description if value.nil?
|
|
63
|
+
|
|
64
|
+
@description = value
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @api public
|
|
68
|
+
# The tags for this action.
|
|
69
|
+
#
|
|
70
|
+
# Tags help organize actions in generated documentation.
|
|
71
|
+
#
|
|
72
|
+
# @param tags [Array<String, Symbol>]
|
|
73
|
+
# The tag names.
|
|
74
|
+
# @return [Array<Symbol>, nil]
|
|
75
|
+
#
|
|
76
|
+
# @example
|
|
77
|
+
# action :create do
|
|
78
|
+
# tags :billing, :invoices
|
|
79
|
+
# end
|
|
80
|
+
def tags(*tags)
|
|
81
|
+
@tags = tags.flatten if tags.any?
|
|
82
|
+
@tags
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @api public
|
|
86
|
+
# Marks this action as deprecated.
|
|
87
|
+
#
|
|
88
|
+
# @return [void]
|
|
89
|
+
#
|
|
90
|
+
# @example
|
|
91
|
+
# action :legacy_create do
|
|
92
|
+
# deprecated!
|
|
93
|
+
# end
|
|
94
|
+
def deprecated!
|
|
95
|
+
@deprecated = true
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api public
|
|
99
|
+
# Whether this action is deprecated.
|
|
100
|
+
#
|
|
101
|
+
# @return [Boolean]
|
|
102
|
+
def deprecated?
|
|
103
|
+
@deprecated == true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @api public
|
|
107
|
+
# The operation ID for this action.
|
|
108
|
+
#
|
|
109
|
+
# @param value [String, nil] (nil)
|
|
110
|
+
# The operation ID.
|
|
111
|
+
# @return [String, nil]
|
|
112
|
+
#
|
|
113
|
+
# @example
|
|
114
|
+
# action :create do
|
|
115
|
+
# operation_id 'createNewInvoice'
|
|
116
|
+
# end
|
|
117
|
+
def operation_id(value = nil)
|
|
118
|
+
return @operation_id if value.nil?
|
|
119
|
+
|
|
120
|
+
@operation_id = value
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @api public
|
|
124
|
+
# Declares the raised error codes for this action.
|
|
125
|
+
#
|
|
126
|
+
# @param error_code_keys [Symbol]
|
|
127
|
+
# The error code keys.
|
|
128
|
+
# @return [void]
|
|
129
|
+
# @raise [ConfigurationError] if error code is not registered
|
|
130
|
+
#
|
|
131
|
+
# @example
|
|
132
|
+
# raises :not_found
|
|
133
|
+
# raises :forbidden
|
|
134
|
+
#
|
|
135
|
+
# @example
|
|
136
|
+
# action :show do
|
|
137
|
+
# raises :not_found, :forbidden
|
|
138
|
+
# end
|
|
139
|
+
def raises(*error_code_keys)
|
|
140
|
+
error_code_keys = error_code_keys.flatten
|
|
141
|
+
error_code_keys.each do |error_code_key|
|
|
142
|
+
unless error_code_key.is_a?(Symbol)
|
|
143
|
+
hint = error_code_key.is_a?(Integer) ? " Use :#{ErrorCode.key_for_status(error_code_key)} instead." : ''
|
|
144
|
+
raise ConfigurationError, "raises must be symbols, got #{error_code_key.class}: #{error_code_key}.#{hint}"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
next if ErrorCode.exists?(error_code_key)
|
|
148
|
+
|
|
149
|
+
raise ConfigurationError,
|
|
150
|
+
"Unknown error code :#{error_code_key}. Register it with: " \
|
|
151
|
+
"Apiwork::ErrorCode.register :#{error_code_key}, status: <status>"
|
|
152
|
+
end
|
|
153
|
+
@raises |= error_code_keys
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# @api public
|
|
157
|
+
# Defines the request structure for this action.
|
|
158
|
+
#
|
|
159
|
+
# Use the block to define query parameters and request body.
|
|
160
|
+
#
|
|
161
|
+
# @param replace [Boolean] (false)
|
|
162
|
+
# Whether to replace inherited definition.
|
|
163
|
+
# @yield block for defining query and body (instance_eval style)
|
|
164
|
+
# @yieldparam request [Action::Request]
|
|
165
|
+
# @return [Action::Request]
|
|
166
|
+
#
|
|
167
|
+
# @example instance_eval style
|
|
168
|
+
# action :create do
|
|
169
|
+
# request do
|
|
170
|
+
# query do
|
|
171
|
+
# boolean? :dry_run
|
|
172
|
+
# end
|
|
173
|
+
# body do
|
|
174
|
+
# string :title
|
|
175
|
+
# end
|
|
176
|
+
# end
|
|
177
|
+
# end
|
|
178
|
+
#
|
|
179
|
+
# @example yield style
|
|
180
|
+
# action :create do
|
|
181
|
+
# request do |request|
|
|
182
|
+
# request.query do |query|
|
|
183
|
+
# query.boolean? :dry_run
|
|
184
|
+
# end
|
|
185
|
+
# request.body do |body|
|
|
186
|
+
# body.string :title
|
|
187
|
+
# end
|
|
188
|
+
# end
|
|
189
|
+
# end
|
|
190
|
+
def request(replace: false, &block)
|
|
191
|
+
@reset_request = replace if replace
|
|
192
|
+
|
|
193
|
+
@request ||= Request.new(contract_class, name)
|
|
194
|
+
|
|
195
|
+
if block
|
|
196
|
+
block.arity.positive? ? yield(@request) : @request.instance_eval(&block)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
@request
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# @api public
|
|
203
|
+
# Defines the response structure for this action.
|
|
204
|
+
#
|
|
205
|
+
# Use the block to define response body or declare no_content.
|
|
206
|
+
#
|
|
207
|
+
# @param replace [Boolean] (false)
|
|
208
|
+
# Whether to replace inherited definition.
|
|
209
|
+
# @yield block for defining body or no_content (instance_eval style)
|
|
210
|
+
# @yieldparam response [Action::Response]
|
|
211
|
+
# @return [Action::Response]
|
|
212
|
+
#
|
|
213
|
+
# @example instance_eval style
|
|
214
|
+
# action :show do
|
|
215
|
+
# response do
|
|
216
|
+
# body do
|
|
217
|
+
# uuid :id
|
|
218
|
+
# string :title
|
|
219
|
+
# end
|
|
220
|
+
# end
|
|
221
|
+
# end
|
|
222
|
+
#
|
|
223
|
+
# @example yield style
|
|
224
|
+
# action :show do
|
|
225
|
+
# response do |response|
|
|
226
|
+
# response.body do |body|
|
|
227
|
+
# body.uuid :id
|
|
228
|
+
# body.string :title
|
|
229
|
+
# end
|
|
230
|
+
# end
|
|
231
|
+
# end
|
|
232
|
+
#
|
|
233
|
+
# @example No content response
|
|
234
|
+
# action :destroy do
|
|
235
|
+
# response { no_content! }
|
|
236
|
+
# end
|
|
237
|
+
def response(replace: false, &block)
|
|
238
|
+
@reset_response = replace if replace
|
|
239
|
+
|
|
240
|
+
@response ||= Response.new(contract_class, name)
|
|
241
|
+
|
|
242
|
+
if block
|
|
243
|
+
block.arity.positive? ? yield(@response) : @response.instance_eval(&block)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
@response
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def resets_request?
|
|
250
|
+
@reset_request
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def resets_response?
|
|
254
|
+
@reset_response
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|