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,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Builder
|
|
6
|
+
module Contract
|
|
7
|
+
# @api public
|
|
8
|
+
# Base class for Contract-phase type builders.
|
|
9
|
+
#
|
|
10
|
+
# Contract phase runs once per contract with representation at registration time.
|
|
11
|
+
# Use it to generate contract-specific types based on the representation.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# module Builder
|
|
15
|
+
# class Contract < Adapter::Builder::Contract::Base
|
|
16
|
+
# def build
|
|
17
|
+
# object(representation_class.root_key.singular.to_sym) do |object|
|
|
18
|
+
# object.string(:id)
|
|
19
|
+
# object.string(:name)
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
# end
|
|
23
|
+
# end
|
|
24
|
+
class Base
|
|
25
|
+
attr_reader :representation_class
|
|
26
|
+
|
|
27
|
+
# @!method api_class
|
|
28
|
+
# @api public
|
|
29
|
+
# @see Contract::Base.api_class
|
|
30
|
+
# @!method enum(name, values:, **options, &block)
|
|
31
|
+
# @api public
|
|
32
|
+
# @see Contract::Base#enum
|
|
33
|
+
# @!method enum?(name)
|
|
34
|
+
# @api public
|
|
35
|
+
# @see Contract::Base#enum?
|
|
36
|
+
# @!method contract_for(representation_class)
|
|
37
|
+
# @api public
|
|
38
|
+
# @see Contract::Base.contract_for
|
|
39
|
+
# @!method import(type_name, from:)
|
|
40
|
+
# @api public
|
|
41
|
+
# @see Contract::Base#import
|
|
42
|
+
# @!method object(name, **options, &block)
|
|
43
|
+
# @api public
|
|
44
|
+
# @see Contract::Base#object
|
|
45
|
+
# @!method scoped_enum_name(name)
|
|
46
|
+
# @api public
|
|
47
|
+
# @see Contract::Base#scoped_enum_name
|
|
48
|
+
# @!method scoped_type_name(name)
|
|
49
|
+
# @api public
|
|
50
|
+
# @see Contract::Base#scoped_type_name
|
|
51
|
+
# @!method type?(name)
|
|
52
|
+
# @api public
|
|
53
|
+
# @see Contract::Base#type?
|
|
54
|
+
# @!method union(name, **options, &block)
|
|
55
|
+
# @api public
|
|
56
|
+
# @see Contract::Base#union
|
|
57
|
+
delegate :api_class,
|
|
58
|
+
:contract_for,
|
|
59
|
+
:enum,
|
|
60
|
+
:enum?,
|
|
61
|
+
:import,
|
|
62
|
+
:object,
|
|
63
|
+
:scoped_enum_name,
|
|
64
|
+
:scoped_type_name,
|
|
65
|
+
:type?,
|
|
66
|
+
:union,
|
|
67
|
+
to: :@contract_class
|
|
68
|
+
|
|
69
|
+
def initialize(contract_class, representation_class)
|
|
70
|
+
@contract_class = contract_class
|
|
71
|
+
@representation_class = representation_class
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @api public
|
|
75
|
+
# Builds contract-level types.
|
|
76
|
+
#
|
|
77
|
+
# Override this method to generate types based on the representation.
|
|
78
|
+
# @return [void]
|
|
79
|
+
def build
|
|
80
|
+
raise NotImplementedError
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Capability
|
|
6
|
+
module API
|
|
7
|
+
# @api public
|
|
8
|
+
# Base class for capability API builders.
|
|
9
|
+
#
|
|
10
|
+
# Provides access to capability options and aggregated configuration
|
|
11
|
+
# across all representations.
|
|
12
|
+
class Base < Builder::API::Base
|
|
13
|
+
# @!attribute [r] scope
|
|
14
|
+
# @api public
|
|
15
|
+
# The scope for this API.
|
|
16
|
+
#
|
|
17
|
+
# @return [Scope]
|
|
18
|
+
# @!attribute [r] options
|
|
19
|
+
# @api public
|
|
20
|
+
# The options for this API.
|
|
21
|
+
#
|
|
22
|
+
# @return [Configuration]
|
|
23
|
+
attr_reader :options,
|
|
24
|
+
:scope
|
|
25
|
+
|
|
26
|
+
def initialize(api_class, capability_name: nil, options: nil)
|
|
27
|
+
super(api_class)
|
|
28
|
+
@capability_name = capability_name
|
|
29
|
+
@scope = Scope.new(api_class)
|
|
30
|
+
@options = options
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @api public
|
|
34
|
+
# The configured values for a key.
|
|
35
|
+
#
|
|
36
|
+
# @param key [Symbol]
|
|
37
|
+
# The configuration key.
|
|
38
|
+
# @return [Set]
|
|
39
|
+
#
|
|
40
|
+
# @example Check if any representation uses cursor pagination
|
|
41
|
+
# if configured(:strategy).include?(:cursor)
|
|
42
|
+
# # build cursor pagination schema
|
|
43
|
+
# end
|
|
44
|
+
def configured(key)
|
|
45
|
+
scope.configured(@capability_name, key)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Capability
|
|
6
|
+
module API
|
|
7
|
+
# @api public
|
|
8
|
+
# Aggregated scope for capability API builders.
|
|
9
|
+
#
|
|
10
|
+
# Provides access to data collected across all representations in the API.
|
|
11
|
+
# Use this to query API-wide state when building shared types.
|
|
12
|
+
class Scope
|
|
13
|
+
def initialize(api_class)
|
|
14
|
+
@representation_registry = api_class.representation_registry
|
|
15
|
+
@root_resource = api_class.root_resource
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @!method index_actions?
|
|
19
|
+
# @api public
|
|
20
|
+
# Returns whether any resource has index actions.
|
|
21
|
+
# @return [Boolean]
|
|
22
|
+
delegate :index_actions?, to: :@root_resource
|
|
23
|
+
|
|
24
|
+
# @!method filter_types
|
|
25
|
+
# @api public
|
|
26
|
+
# Returns all filterable types across representations.
|
|
27
|
+
# @return [Set<Symbol>]
|
|
28
|
+
#
|
|
29
|
+
# @!method nullable_filter_types
|
|
30
|
+
# @api public
|
|
31
|
+
# Returns filterable types that can be null.
|
|
32
|
+
# @return [Set<Symbol>]
|
|
33
|
+
#
|
|
34
|
+
# @!method filterable?
|
|
35
|
+
# @api public
|
|
36
|
+
# Returns whether any representation has filterable attributes.
|
|
37
|
+
# @return [Boolean]
|
|
38
|
+
#
|
|
39
|
+
# @!method sortable?
|
|
40
|
+
# @api public
|
|
41
|
+
# Returns whether any representation has sortable attributes.
|
|
42
|
+
# @return [Boolean]
|
|
43
|
+
delegate :filter_types,
|
|
44
|
+
:filterable?,
|
|
45
|
+
:nullable_filter_types,
|
|
46
|
+
:sortable?,
|
|
47
|
+
to: :@representation_registry
|
|
48
|
+
|
|
49
|
+
# @api public
|
|
50
|
+
# The configured values for a capability.
|
|
51
|
+
#
|
|
52
|
+
# @param capability [Symbol]
|
|
53
|
+
# The capability name.
|
|
54
|
+
# @param key [Symbol]
|
|
55
|
+
# The configuration key.
|
|
56
|
+
# @return [Set]
|
|
57
|
+
def configured(capability, key)
|
|
58
|
+
@representation_registry.options_for(capability, key)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Capability
|
|
6
|
+
# @api public
|
|
7
|
+
# Base class for adapter capabilities.
|
|
8
|
+
#
|
|
9
|
+
# A capability encapsulates a specific feature (filtering, pagination, sorting)
|
|
10
|
+
# with its own configuration, transformers, builders, and operations. While each
|
|
11
|
+
# capability is self-contained, all capabilities operate on the same response data
|
|
12
|
+
# in sequence, so their effects combine.
|
|
13
|
+
#
|
|
14
|
+
# @see Adapter::Base#capability
|
|
15
|
+
# @see Configurable#option
|
|
16
|
+
#
|
|
17
|
+
# @example Filtering capability
|
|
18
|
+
# class Filtering < Adapter::Capability::Base
|
|
19
|
+
# capability_name :filtering
|
|
20
|
+
#
|
|
21
|
+
# option :strategy, type: :symbol, default: :simple
|
|
22
|
+
#
|
|
23
|
+
# request_transformer RequestTransformer
|
|
24
|
+
# api_builder APIBuilder
|
|
25
|
+
# contract_builder ContractBuilder
|
|
26
|
+
# operation Operation
|
|
27
|
+
# end
|
|
28
|
+
class Base
|
|
29
|
+
include Configurable
|
|
30
|
+
|
|
31
|
+
class_attribute :_api_builder
|
|
32
|
+
class_attribute :_api_builder_block
|
|
33
|
+
class_attribute :_contract_builder
|
|
34
|
+
class_attribute :_contract_builder_block
|
|
35
|
+
class_attribute :_operation_class
|
|
36
|
+
class_attribute :_operation_block
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
# @api public
|
|
40
|
+
# The name for this capability.
|
|
41
|
+
#
|
|
42
|
+
# Used for configuration options, translation keys, and {Adapter::Base.skip_capability}.
|
|
43
|
+
#
|
|
44
|
+
# @param value [Symbol, nil] (nil)
|
|
45
|
+
# The capability name.
|
|
46
|
+
# @return [Symbol, nil]
|
|
47
|
+
def capability_name(value = nil)
|
|
48
|
+
@capability_name = value.to_sym if value
|
|
49
|
+
@capability_name
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @api public
|
|
53
|
+
# Registers a request transformer for this capability.
|
|
54
|
+
#
|
|
55
|
+
# @param transformer_class [Class<Transformer::Request::Base>]
|
|
56
|
+
# The transformer class.
|
|
57
|
+
# @return [void]
|
|
58
|
+
# @see Transformer::Request::Base
|
|
59
|
+
def request_transformer(transformer_class)
|
|
60
|
+
@request_transformers ||= []
|
|
61
|
+
@request_transformers << transformer_class
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @api public
|
|
65
|
+
# Registers a response transformer for this capability.
|
|
66
|
+
#
|
|
67
|
+
# @param transformer_class [Class<Transformer::Response::Base>]
|
|
68
|
+
# The transformer class.
|
|
69
|
+
# @return [void]
|
|
70
|
+
# @see Transformer::Response::Base
|
|
71
|
+
def response_transformer(transformer_class)
|
|
72
|
+
@response_transformers ||= []
|
|
73
|
+
@response_transformers << transformer_class
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api public
|
|
77
|
+
# Registers an API builder for this capability.
|
|
78
|
+
#
|
|
79
|
+
# API builders run once per API at initialization time to register
|
|
80
|
+
# shared types used across all contracts.
|
|
81
|
+
#
|
|
82
|
+
# @param klass [Class<Builder::API::Base>, nil] (nil)
|
|
83
|
+
# The builder class.
|
|
84
|
+
# @yield block evaluated in {Builder::API::Base} context
|
|
85
|
+
# @return [void]
|
|
86
|
+
# @see Builder::API::Base
|
|
87
|
+
def api_builder(klass = nil, &block)
|
|
88
|
+
if klass
|
|
89
|
+
self._api_builder = klass
|
|
90
|
+
elsif block
|
|
91
|
+
self._api_builder_block = block
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @api public
|
|
96
|
+
# Registers a contract builder for this capability.
|
|
97
|
+
#
|
|
98
|
+
# Contract builders run per contract to add capability-specific
|
|
99
|
+
# parameters and response shapes.
|
|
100
|
+
#
|
|
101
|
+
# @param klass [Class<Builder::Contract::Base>, nil] (nil)
|
|
102
|
+
# The builder class.
|
|
103
|
+
# @yield block evaluated in {Builder::Contract::Base} context
|
|
104
|
+
# @return [void]
|
|
105
|
+
# @see Builder::Contract::Base
|
|
106
|
+
def contract_builder(klass = nil, &block)
|
|
107
|
+
if klass
|
|
108
|
+
self._contract_builder = klass
|
|
109
|
+
elsif block
|
|
110
|
+
self._contract_builder_block = block
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# @api public
|
|
115
|
+
# Registers an operation for this capability.
|
|
116
|
+
#
|
|
117
|
+
# Operations run at request time to process data based on
|
|
118
|
+
# request parameters.
|
|
119
|
+
#
|
|
120
|
+
# @param klass [Class<Operation::Base>, nil] (nil)
|
|
121
|
+
# The operation class.
|
|
122
|
+
# @yield block evaluated in {Operation::Base} context
|
|
123
|
+
# @return [void]
|
|
124
|
+
# @see Operation::Base
|
|
125
|
+
def operation(klass = nil, &block)
|
|
126
|
+
if klass
|
|
127
|
+
self._operation_class = klass
|
|
128
|
+
elsif block
|
|
129
|
+
self._operation_block = block
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def request_transformers
|
|
134
|
+
@request_transformers || []
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def response_transformers
|
|
138
|
+
@response_transformers || []
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def wrap_api_builder_block(callable)
|
|
142
|
+
Class.new(Builder::API::Base) do
|
|
143
|
+
define_method(:build) do
|
|
144
|
+
callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def wrap_contract_builder_block(callable)
|
|
150
|
+
Class.new(Builder::Contract::Base) do
|
|
151
|
+
define_method(:build) do
|
|
152
|
+
callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def wrap_operation_block(callable)
|
|
158
|
+
Class.new(Operation::Base) do
|
|
159
|
+
define_method(:apply) do
|
|
160
|
+
callable.arity.positive? ? callable.call(self) : instance_exec(&callable)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def api_builder_class
|
|
166
|
+
return _api_builder if _api_builder
|
|
167
|
+
return wrap_api_builder_block(_api_builder_block) if _api_builder_block
|
|
168
|
+
|
|
169
|
+
nil
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def contract_builder_class
|
|
173
|
+
return _contract_builder if _contract_builder
|
|
174
|
+
return wrap_contract_builder_block(_contract_builder_block) if _contract_builder_block
|
|
175
|
+
|
|
176
|
+
nil
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def operation_class
|
|
180
|
+
return _operation_class if _operation_class
|
|
181
|
+
return wrap_operation_block(_operation_block) if _operation_block
|
|
182
|
+
|
|
183
|
+
nil
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
attr_reader :adapter_name,
|
|
188
|
+
:config
|
|
189
|
+
|
|
190
|
+
def initialize(config = {}, adapter_name: nil)
|
|
191
|
+
merged = self.class.default_options.deep_merge(config)
|
|
192
|
+
@config = Configuration.new(self.class, merged)
|
|
193
|
+
@adapter_name = adapter_name
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def api_types(api_class)
|
|
197
|
+
builder_class = self.class.api_builder_class
|
|
198
|
+
return unless builder_class
|
|
199
|
+
|
|
200
|
+
builder_class.new(
|
|
201
|
+
api_class,
|
|
202
|
+
capability_name: self.class.capability_name,
|
|
203
|
+
options: config,
|
|
204
|
+
).build
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def contract_types(contract_class, representation_class, actions)
|
|
208
|
+
builder_class = self.class.contract_builder_class
|
|
209
|
+
return unless builder_class
|
|
210
|
+
|
|
211
|
+
builder_class.new(contract_class, representation_class, actions, merged_config(representation_class)).build
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def shape(representation_class, type)
|
|
215
|
+
klass = self.class.operation_class
|
|
216
|
+
return nil unless klass
|
|
217
|
+
|
|
218
|
+
metadata_shape_class = klass.metadata_shape
|
|
219
|
+
return nil unless metadata_shape_class
|
|
220
|
+
|
|
221
|
+
scope = klass.target
|
|
222
|
+
return nil if scope && scope != type
|
|
223
|
+
|
|
224
|
+
object = ::Apiwork::API::Object.new
|
|
225
|
+
metadata_shape_class.apply(object, merged_config(representation_class))
|
|
226
|
+
return nil if object.params.empty?
|
|
227
|
+
|
|
228
|
+
type_name = resolve_metadata_type_name(representation_class)
|
|
229
|
+
register_metadata_fragment(
|
|
230
|
+
representation_class.api_class,
|
|
231
|
+
type_name,
|
|
232
|
+
metadata_shape_class,
|
|
233
|
+
merged_config(representation_class),
|
|
234
|
+
)
|
|
235
|
+
type_name
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def apply(data, representation_class, request, wrapper_type:)
|
|
239
|
+
klass = self.class.operation_class
|
|
240
|
+
return Result.new(data:) unless klass
|
|
241
|
+
|
|
242
|
+
scope = klass.target
|
|
243
|
+
return Result.new(data:) if scope && scope != wrapper_type
|
|
244
|
+
|
|
245
|
+
klass.new(
|
|
246
|
+
data,
|
|
247
|
+
representation_class,
|
|
248
|
+
merged_config(representation_class),
|
|
249
|
+
request,
|
|
250
|
+
translation_context: build_translation_context(representation_class),
|
|
251
|
+
).apply
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
private
|
|
255
|
+
|
|
256
|
+
def merged_config(representation_class)
|
|
257
|
+
capability_name = self.class.capability_name
|
|
258
|
+
return config unless capability_name
|
|
259
|
+
|
|
260
|
+
representation_config = representation_class.adapter_config.public_send(capability_name).to_h
|
|
261
|
+
config.merge(representation_config)
|
|
262
|
+
rescue ConfigurationError
|
|
263
|
+
config
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def resolve_metadata_type_name(representation_class)
|
|
267
|
+
prefix = representation_class.name.demodulize.delete_suffix('Representation').underscore
|
|
268
|
+
[prefix, self.class.capability_name, 'metadata'].join('_').to_sym
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def register_metadata_fragment(api_class, type_name, metadata_shape_class, config)
|
|
272
|
+
return if api_class.type_registry.key?(type_name)
|
|
273
|
+
|
|
274
|
+
api_class.type_registry.register(type_name, fragment: true, kind: :object) do |shape|
|
|
275
|
+
metadata_shape_class.apply(shape, config)
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def build_translation_context(representation_class)
|
|
280
|
+
locale_key = representation_class.api_class.locale_key
|
|
281
|
+
|
|
282
|
+
{
|
|
283
|
+
locale_key:,
|
|
284
|
+
adapter_name: adapter_name,
|
|
285
|
+
capability_name: self.class.capability_name,
|
|
286
|
+
}
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Capability
|
|
6
|
+
module Contract
|
|
7
|
+
# @api public
|
|
8
|
+
# Base class for capability Contract phase.
|
|
9
|
+
#
|
|
10
|
+
# Contract phase runs once per contract with representation at registration time.
|
|
11
|
+
# Use it to generate contract-specific types based on the representation.
|
|
12
|
+
class Base < Builder::Contract::Base
|
|
13
|
+
# @!attribute [r] scope
|
|
14
|
+
# @api public
|
|
15
|
+
# The scope for this contract.
|
|
16
|
+
#
|
|
17
|
+
# @return [Scope]
|
|
18
|
+
# @!attribute [r] options
|
|
19
|
+
# @api public
|
|
20
|
+
# The options for this contract.
|
|
21
|
+
#
|
|
22
|
+
# @return [Configuration]
|
|
23
|
+
attr_reader :options,
|
|
24
|
+
:scope
|
|
25
|
+
|
|
26
|
+
delegate :action, to: :@contract_class
|
|
27
|
+
|
|
28
|
+
def initialize(contract_class, representation_class, actions, options)
|
|
29
|
+
super(contract_class, representation_class)
|
|
30
|
+
@scope = Scope.new(representation_class, actions)
|
|
31
|
+
@options = options
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module Adapter
|
|
5
|
+
module Capability
|
|
6
|
+
module Contract
|
|
7
|
+
# @api public
|
|
8
|
+
# Scope for capability contract builders.
|
|
9
|
+
#
|
|
10
|
+
# Provides access to the representation and actions for this contract.
|
|
11
|
+
# Use this to query contract-specific state when building types.
|
|
12
|
+
class Scope
|
|
13
|
+
# @api public
|
|
14
|
+
# The actions for this scope.
|
|
15
|
+
#
|
|
16
|
+
# @return [Hash{Symbol => Resource::Action}]
|
|
17
|
+
attr_reader :actions
|
|
18
|
+
|
|
19
|
+
attr_reader :representation_class
|
|
20
|
+
|
|
21
|
+
def initialize(representation_class, actions)
|
|
22
|
+
@representation_class = representation_class
|
|
23
|
+
@actions = actions
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @api public
|
|
27
|
+
# The collection actions for this scope.
|
|
28
|
+
#
|
|
29
|
+
# @return [Hash{Symbol => Resource::Action}]
|
|
30
|
+
def collection_actions
|
|
31
|
+
@collection_actions ||= actions.select { |_name, action| action.collection? }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api public
|
|
35
|
+
# The member actions for this scope.
|
|
36
|
+
#
|
|
37
|
+
# @return [Hash{Symbol => Resource::Action}]
|
|
38
|
+
def member_actions
|
|
39
|
+
@member_actions ||= actions.select { |_name, action| action.member? }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @api public
|
|
43
|
+
# The CRUD actions for this scope.
|
|
44
|
+
#
|
|
45
|
+
# @return [Hash{Symbol => Resource::Action}]
|
|
46
|
+
def crud_actions
|
|
47
|
+
@crud_actions ||= actions.select { |_name, action| action.crud? }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @api public
|
|
51
|
+
# Whether this scope includes the given action.
|
|
52
|
+
#
|
|
53
|
+
# @param name [Symbol]
|
|
54
|
+
# The action name.
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
def action?(name)
|
|
57
|
+
actions.key?(name.to_sym)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api public
|
|
61
|
+
# The filterable attributes for this scope.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<Representation::Attribute>]
|
|
64
|
+
def filterable_attributes
|
|
65
|
+
@filterable_attributes ||= attributes.values.select(&:filterable?)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @api public
|
|
69
|
+
# The sortable attributes for this scope.
|
|
70
|
+
#
|
|
71
|
+
# @return [Array<Representation::Attribute>]
|
|
72
|
+
def sortable_attributes
|
|
73
|
+
@sortable_attributes ||= attributes.values.select(&:sortable?)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @api public
|
|
77
|
+
# The writable attributes for this scope.
|
|
78
|
+
#
|
|
79
|
+
# @return [Array<Representation::Attribute>]
|
|
80
|
+
def writable_attributes
|
|
81
|
+
@writable_attributes ||= attributes.values.select(&:writable?)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @!method associations
|
|
85
|
+
# @api public
|
|
86
|
+
# The associations for this scope.
|
|
87
|
+
#
|
|
88
|
+
# @return [Hash{Symbol => Representation::Association}]
|
|
89
|
+
#
|
|
90
|
+
# @!method attributes
|
|
91
|
+
# @api public
|
|
92
|
+
# The attributes for this scope.
|
|
93
|
+
#
|
|
94
|
+
# @return [Hash{Symbol => Representation::Attribute}]
|
|
95
|
+
#
|
|
96
|
+
# @!method root_key
|
|
97
|
+
# @api public
|
|
98
|
+
# The root key for this scope.
|
|
99
|
+
#
|
|
100
|
+
# @return [Representation::RootKey]
|
|
101
|
+
delegate :adapter_config,
|
|
102
|
+
:associations,
|
|
103
|
+
:attributes,
|
|
104
|
+
:root_key,
|
|
105
|
+
to: :representation_class
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|