roda-endpoints 0.1.0
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 +7 -0
- data/.dockerignore +98 -0
- data/.gitignore +9 -0
- data/.gitlab-ci.yml +85 -0
- data/.rspec +3 -0
- data/.rubocop.yml +51 -0
- data/.ruby-version +1 -0
- data/.simplecov +7 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Dockerfile +11 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -0
- data/Rakefile +4 -0
- data/bin/bundle-audit +17 -0
- data/bin/console +15 -0
- data/bin/rspec +17 -0
- data/bin/rubocop +17 -0
- data/bin/setup +8 -0
- data/bin/yard +17 -0
- data/bin/yardoc +17 -0
- data/bin/yri +17 -0
- data/lib/roda/endpoints/endpoint/caching.rb +28 -0
- data/lib/roda/endpoints/endpoint/class_interface.rb +123 -0
- data/lib/roda/endpoints/endpoint/collection.rb +77 -0
- data/lib/roda/endpoints/endpoint/data.rb +29 -0
- data/lib/roda/endpoints/endpoint/item.rb +117 -0
- data/lib/roda/endpoints/endpoint/namespace.rb +48 -0
- data/lib/roda/endpoints/endpoint/operations.rb +28 -0
- data/lib/roda/endpoints/endpoint/transactions.rb +67 -0
- data/lib/roda/endpoints/endpoint/validations.rb +98 -0
- data/lib/roda/endpoints/endpoint/verbs.rb +45 -0
- data/lib/roda/endpoints/endpoint.rb +83 -0
- data/lib/roda/endpoints/functions.rb +26 -0
- data/lib/roda/endpoints/repository.rb +26 -0
- data/lib/roda/endpoints/transactions.rb +75 -0
- data/lib/roda/endpoints/types.rb +13 -0
- data/lib/roda/endpoints/version.rb +7 -0
- data/lib/roda/endpoints.rb +21 -0
- data/lib/roda/plugins/endpoints.rb +161 -0
- data/lib/rom/struct/to_json.rb +18 -0
- data/rakelib/bundle_audit.rake +4 -0
- data/rakelib/bundler.rake +2 -0
- data/rakelib/rspec.rake +6 -0
- data/rakelib/rubocop.rake +5 -0
- data/rakelib/yard.rake +5 -0
- data/roda-endpoints.gemspec +49 -0
- metadata +385 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints'
|
4
|
+
require 'roda/endpoints/transactions'
|
5
|
+
require 'inflecto'
|
6
|
+
|
7
|
+
class Roda
|
8
|
+
module Endpoints
|
9
|
+
class Endpoint
|
10
|
+
module ClassInterface
|
11
|
+
# @return [<Symbol>]
|
12
|
+
attr_accessor :attributes
|
13
|
+
|
14
|
+
# @return [{Symbol=>Object}]
|
15
|
+
attr_accessor :defaults
|
16
|
+
|
17
|
+
# @return [{Symbol=>{Symbol=>Symbol}}]
|
18
|
+
attr_accessor :statuses
|
19
|
+
|
20
|
+
# @return [<Symbol>]
|
21
|
+
attr_accessor :verbs
|
22
|
+
|
23
|
+
# @param [Symbol] key
|
24
|
+
def define_attribute(key)
|
25
|
+
self.attributes += [key]
|
26
|
+
attr_reader key
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Dry::Container]
|
30
|
+
def container
|
31
|
+
@container ||= Roda::Endpoints.container
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param [Class] child
|
35
|
+
def inherited(child)
|
36
|
+
child.attributes = attributes.dup
|
37
|
+
child.defaults = defaults.dup
|
38
|
+
child.statuses = statuses.dup
|
39
|
+
child.verbs = verbs.dup
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def ns
|
44
|
+
@ns ||= name.gsub(/^Roda::Endpoints::/, '').underscore.tr('/', '.')
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Symbol]
|
48
|
+
def type
|
49
|
+
@type ||= Inflecto.underscore(Inflecto.demodulize(name)).to_sym
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Symbol] name
|
53
|
+
# @param [Proc] block
|
54
|
+
# @return [Symbol] name of the defined method
|
55
|
+
def verb(name, rescue_from: [], &block)
|
56
|
+
rescue_from = Array(rescue_from).flatten
|
57
|
+
if rescue_from.any?
|
58
|
+
block = proc do |*args|
|
59
|
+
begin
|
60
|
+
instance_exec(*args, &block)
|
61
|
+
rescue *rescue_from
|
62
|
+
Left($ERROR_INFO)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
define_method(name, &block)
|
67
|
+
container.register "operations.#{type}.#{name}", block
|
68
|
+
self.verbs ||= superclass.verbs
|
69
|
+
(self.verbs += [name]).freeze
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [String, Symbol] key
|
73
|
+
# @param [Proc] block
|
74
|
+
#
|
75
|
+
# @example
|
76
|
+
# r.collection :articles do |articles|
|
77
|
+
# # register validation at 'validations.endpoints.articles.default'
|
78
|
+
# articles.validate do
|
79
|
+
# required(:title).filled?
|
80
|
+
# required(:contents).filled?
|
81
|
+
# end
|
82
|
+
# # redefine validation for patch method at
|
83
|
+
# # 'validations.endpoints.articles.patch'
|
84
|
+
# articles.validate(:patch) do
|
85
|
+
# required(:title).filled?
|
86
|
+
# required(:contents).filled?
|
87
|
+
# require(:confirm_changes).
|
88
|
+
# end
|
89
|
+
# end
|
90
|
+
def validate(key = :default, &block)
|
91
|
+
key = "validations.#{ns}.#{key}" if key.is_a?(Symbol)
|
92
|
+
schema = Dry::Validation.Form(&block)
|
93
|
+
container.register(key) do |params|
|
94
|
+
validation = schema.call(params)
|
95
|
+
if validation.success?
|
96
|
+
Right(validation.output)
|
97
|
+
else
|
98
|
+
Left([:unprocessable_entity, {}, validation])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
schema
|
102
|
+
end
|
103
|
+
# rubocop:enable Metrics/MethodLength
|
104
|
+
|
105
|
+
# @param [Proc] block
|
106
|
+
# @return [Proc]
|
107
|
+
def route(&block)
|
108
|
+
@route_block = block if block_given?
|
109
|
+
@route_block
|
110
|
+
end
|
111
|
+
|
112
|
+
def transaction(name, &block)
|
113
|
+
transactions << [name, block]
|
114
|
+
end
|
115
|
+
|
116
|
+
# @return [(Symbol, Hash, Proc)]
|
117
|
+
def transactions
|
118
|
+
@transactions ||= []
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints/endpoint'
|
4
|
+
require 'roda/endpoints/endpoint/data'
|
5
|
+
require 'roda/endpoints/endpoint/caching'
|
6
|
+
require 'inflecto'
|
7
|
+
require 'rom/sql'
|
8
|
+
|
9
|
+
class Roda
|
10
|
+
module Endpoints
|
11
|
+
class Endpoint
|
12
|
+
# HTTP endpoint representing a collection of items of the same type.
|
13
|
+
class Collection < Endpoint
|
14
|
+
include Data
|
15
|
+
prepend Caching
|
16
|
+
|
17
|
+
self.attributes += %i(item)
|
18
|
+
self.defaults = defaults.merge(
|
19
|
+
type: :collection,
|
20
|
+
last_modified: :last_modified
|
21
|
+
)
|
22
|
+
|
23
|
+
# @return [{Symbol=>Object}]
|
24
|
+
attr_reader :item
|
25
|
+
|
26
|
+
# @return [Time]
|
27
|
+
def last_modified
|
28
|
+
@last_modified ? repository.public_send(@last_modified) : super
|
29
|
+
end
|
30
|
+
|
31
|
+
def child(**params)
|
32
|
+
with(
|
33
|
+
type: Item,
|
34
|
+
name: resource_name,
|
35
|
+
**params
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [Symbol]
|
40
|
+
def resource_name
|
41
|
+
Inflecto.singularize(name).to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
route do |r, endpoint| # r.collection :articles do |articles|
|
45
|
+
r.last_modified endpoint.last_modified if endpoint.last_modified
|
46
|
+
|
47
|
+
endpoint.verbs.each do |verb|
|
48
|
+
# @route #{verb} /:name
|
49
|
+
r.public_send(verb, transaction: verb)
|
50
|
+
|
51
|
+
r.child **endpoint.item if endpoint.item # child by: :id
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
verb :get do |params|
|
56
|
+
Right(repository.list(**params))
|
57
|
+
end
|
58
|
+
|
59
|
+
verb :post do |params|
|
60
|
+
params = params[resource_name] || {}
|
61
|
+
Right(repository.create(**params))
|
62
|
+
end
|
63
|
+
|
64
|
+
# @route GET /{collection.name}
|
65
|
+
transaction :get do |endpoint|
|
66
|
+
step :retrieve, with: endpoint.operation_for(:get)
|
67
|
+
end
|
68
|
+
|
69
|
+
# @route POST /{collection.name}
|
70
|
+
transaction :post do |endpoint|
|
71
|
+
step :validate, with: endpoint.validation_for(:post)
|
72
|
+
step :persist, with: endpoint.operation_for(:post)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module Endpoints
|
5
|
+
# Generic HTTP endpoint abstraction.
|
6
|
+
class Endpoint
|
7
|
+
# Accessing data inside of endpoint.
|
8
|
+
module Data
|
9
|
+
# @param name [String]
|
10
|
+
# @param repository [String]
|
11
|
+
# @param attributes [{Symbol=>Object}]
|
12
|
+
def initialize(name:, repository: "repositories.#{name}", **attributes)
|
13
|
+
@repository_key = repository
|
14
|
+
super(name: name, **attributes)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [ROM::Repository]
|
18
|
+
def repository
|
19
|
+
container[@repository_key] if @repository_key
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [{Symbol=>Object}]
|
23
|
+
def to_hash
|
24
|
+
super.merge(repository: @repository_key)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints/endpoint'
|
4
|
+
require 'roda/endpoints/endpoint/data'
|
5
|
+
require 'roda/endpoints/endpoint/caching'
|
6
|
+
|
7
|
+
class Roda
|
8
|
+
module Endpoints
|
9
|
+
class Endpoint
|
10
|
+
# HTTP endpoint representing a specific item of collection uniquely
|
11
|
+
# identified by some parameter.
|
12
|
+
class Item < Endpoint
|
13
|
+
include Data
|
14
|
+
include Caching
|
15
|
+
|
16
|
+
self.attributes += %i(id by finder last_modified)
|
17
|
+
self.defaults = defaults.merge(
|
18
|
+
by: :fetch,
|
19
|
+
finder: lambda do
|
20
|
+
repository.public_send(by, id)
|
21
|
+
end,
|
22
|
+
last_modified: :updated_at
|
23
|
+
)
|
24
|
+
|
25
|
+
# @return [Symbol]
|
26
|
+
attr_reader :by
|
27
|
+
|
28
|
+
# @return [Symbol]
|
29
|
+
attr_reader :id
|
30
|
+
|
31
|
+
# @return [Symbol]
|
32
|
+
attr_reader :finder
|
33
|
+
|
34
|
+
# @return [Endpoint::Collection]
|
35
|
+
def collection
|
36
|
+
parent
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [ROM::Struct]
|
40
|
+
def entity
|
41
|
+
@entity ||= fetch_entity
|
42
|
+
end
|
43
|
+
|
44
|
+
def fetch_entity
|
45
|
+
instance_exec(&finder)
|
46
|
+
end
|
47
|
+
|
48
|
+
def last_modified
|
49
|
+
@last_modified ? entity.public_send(@last_modified) : super
|
50
|
+
end
|
51
|
+
|
52
|
+
route do |r, endpoint|
|
53
|
+
endpoint.verbs.each do |verb|
|
54
|
+
# @route #{verb} /{collection.name}/{id}
|
55
|
+
# STDOUT.puts [verb].pretty_inspect
|
56
|
+
r.public_send(verb, transaction: verb)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
transaction :get do |endpoint|
|
61
|
+
step :retrieve, with: endpoint.operation_for(:get)
|
62
|
+
end
|
63
|
+
|
64
|
+
transaction :patch do |endpoint|
|
65
|
+
step :validate, with: endpoint.validation_for(:patch)
|
66
|
+
step :persist, with: endpoint.operation_for(:patch)
|
67
|
+
end
|
68
|
+
|
69
|
+
transaction :put do |endpoint|
|
70
|
+
step :validate, with: endpoint.validation_for(:put)
|
71
|
+
# step :reset, with: 'endpoints.operations.reset'
|
72
|
+
step :persist, with: endpoint.operation_for(:put)
|
73
|
+
end
|
74
|
+
|
75
|
+
transaction :delete do |endpoint|
|
76
|
+
step :validate, with: endpoint.validation_for(:delete)
|
77
|
+
step :persist, with: endpoint.operation_for(:delete)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @route GET /{collection.name}/{id}
|
81
|
+
# @param [Hash] params
|
82
|
+
# @return [Dry::Monads::Either]
|
83
|
+
verb :get do |_params|
|
84
|
+
Right(entity)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @route PATCH /{collection.name}/{id}
|
88
|
+
# @return [Dry::Monads::Either]
|
89
|
+
verb :patch do |params|
|
90
|
+
changeset = params[name]
|
91
|
+
Right(repository.update(id, changeset))
|
92
|
+
end
|
93
|
+
|
94
|
+
# @route PUT /{collection.name}/{id}
|
95
|
+
# @return [Dry::Monads::Either]
|
96
|
+
verb :put do |params|
|
97
|
+
changeset = entity.to_hash.keys.each_with_object(
|
98
|
+
{}
|
99
|
+
) do |key, changeset|
|
100
|
+
changeset[key] = nil
|
101
|
+
end.merge(params[name] || {})
|
102
|
+
Right(repository.update(id, changeset))
|
103
|
+
end
|
104
|
+
|
105
|
+
# @route DELETE /{collection.name}/{id}
|
106
|
+
# @return [Dry::Monads::Either]
|
107
|
+
verb :delete do |_params|
|
108
|
+
if (result = repository.delete(id))
|
109
|
+
Right(nil)
|
110
|
+
else
|
111
|
+
Left(result)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module Endpoints
|
5
|
+
# Generic HTTP endpoint abstraction.
|
6
|
+
class Endpoint
|
7
|
+
# Namespacing operations, validations, etc.
|
8
|
+
module Namespace
|
9
|
+
def self.included(child)
|
10
|
+
child.attributes += %i(name ns parent)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param name [Symbol]
|
14
|
+
# @param ns [String]
|
15
|
+
# @param attributes [{Symbol=>Object}]
|
16
|
+
# @param parent [Endpoint?]
|
17
|
+
def initialize(name:, ns: name.to_s, parent: Undefined, **attributes)
|
18
|
+
@name = name
|
19
|
+
@ns = ns
|
20
|
+
unless parent == Undefined
|
21
|
+
@parent = parent
|
22
|
+
@ns = "#{parent.ns}.#{ns}"
|
23
|
+
end
|
24
|
+
super(name: name, **attributes)
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Symbol]
|
28
|
+
attr_reader :name
|
29
|
+
|
30
|
+
# @return [String]
|
31
|
+
attr_reader :ns
|
32
|
+
|
33
|
+
# @return [Endpoint]
|
34
|
+
attr_reader :parent
|
35
|
+
|
36
|
+
# @return [ROM::Repository]
|
37
|
+
def repository
|
38
|
+
container[@repository_key] if @repository_key
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [{Symbol=>Object}]
|
42
|
+
def to_hash
|
43
|
+
super.merge(repository: @repository_key)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Roda
|
4
|
+
module Endpoints
|
5
|
+
# Generic HTTP endpoint abstraction.
|
6
|
+
class Endpoint
|
7
|
+
# Parameters validations for {Endpoints} based on `Dry::Validation` gem.
|
8
|
+
module Operations
|
9
|
+
def initialize(**kwargs)
|
10
|
+
super(**kwargs)
|
11
|
+
container.merge(Endpoints.container)
|
12
|
+
self.class.verbs.each do |verb|
|
13
|
+
container.register "operations.#{ns}.#{verb}", &method(verb)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [Symbol] verb
|
18
|
+
# @return [String]
|
19
|
+
def operation_for(verb)
|
20
|
+
%W(
|
21
|
+
operations.#{ns}.#{verb}
|
22
|
+
operations.#{type}.#{verb}
|
23
|
+
).detect { |key| container.key?(key) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints/transactions'
|
4
|
+
|
5
|
+
class Roda
|
6
|
+
module Endpoints
|
7
|
+
# Generic HTTP endpoint abstraction.
|
8
|
+
class Endpoint
|
9
|
+
# Namespacing operations, validations, etc.
|
10
|
+
module Transactions
|
11
|
+
# @return [Endpoints::Transactions]
|
12
|
+
def transactions
|
13
|
+
endpoint = self
|
14
|
+
@transactions ||=
|
15
|
+
begin
|
16
|
+
transactions = Endpoints::Transactions.new(endpoint: self)
|
17
|
+
self.class.transactions.each do |(name, block)|
|
18
|
+
transactions.define(name) { instance_exec(endpoint, &block) }
|
19
|
+
end
|
20
|
+
transactions
|
21
|
+
end
|
22
|
+
yield @transaction if block_given?
|
23
|
+
@transactions
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Symbol] verb
|
27
|
+
# @return [String]
|
28
|
+
def transaction_for(verb)
|
29
|
+
%W(
|
30
|
+
transactions.#{ns}.#{verb}
|
31
|
+
transactions.#{type}.#{verb}
|
32
|
+
).detect { |key| container.key?(key) }
|
33
|
+
end
|
34
|
+
|
35
|
+
# @param [Symbol, String] name
|
36
|
+
# @param [Proc] block
|
37
|
+
def step(name, only: [], **kwargs, &block)
|
38
|
+
name = "operations.#{ns}.#{name}"
|
39
|
+
container.register(name, &block) if block_given?
|
40
|
+
verbs = Array(only).flatten
|
41
|
+
verbs.each do |verb|
|
42
|
+
result = transactions[verb].insert(container: container, **kwargs) do
|
43
|
+
step name
|
44
|
+
end
|
45
|
+
@transaction = result
|
46
|
+
end
|
47
|
+
transactions
|
48
|
+
end
|
49
|
+
|
50
|
+
def before(name, **kwargs, &block)
|
51
|
+
step "before_#{name}", before: name, **kwargs, &block
|
52
|
+
end
|
53
|
+
|
54
|
+
def after(name, **kwargs, &block)
|
55
|
+
step "after_#{name}", after: name, **kwargs, &block
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Symbol] operation
|
59
|
+
# @param [Array] args
|
60
|
+
# @return [Dry::Monads::Either]
|
61
|
+
def perform(operation, *args, **options)
|
62
|
+
transactions[operation].call(*args, **options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints'
|
4
|
+
require 'roda/endpoints/functions'
|
5
|
+
require 'dry-validation'
|
6
|
+
|
7
|
+
class Roda
|
8
|
+
module Endpoints
|
9
|
+
# Generic HTTP endpoint abstraction.
|
10
|
+
class Endpoint
|
11
|
+
# Parameters validations for {Endpoints} based on `Dry::Validation` gem.
|
12
|
+
module Validations
|
13
|
+
include Functions::Shortcut
|
14
|
+
|
15
|
+
# @param [Class] child
|
16
|
+
def self.included(child)
|
17
|
+
child.defaults = child.defaults.merge(validation: 'validation').freeze
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param [String, Symbol] key
|
22
|
+
# @param [Proc] block
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# r.collection :articles do |articles|
|
26
|
+
# # register validation at 'validations.endpoints.articles.default'
|
27
|
+
# articles.validate do
|
28
|
+
# required(:title).filled?
|
29
|
+
# required(:contents).filled?
|
30
|
+
# end
|
31
|
+
# # redefine validation for patch method at
|
32
|
+
# # 'validations.endpoints.articles.patch'
|
33
|
+
# articles.validate(:patch) do
|
34
|
+
# required(:title).filled?
|
35
|
+
# required(:contents).filled?
|
36
|
+
# require(:confirm_changes).
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
def validate(key = :default, &block)
|
40
|
+
defaults = "validations.defaults.#{ns}.#{key}"
|
41
|
+
key = "validations.#{ns}.#{key}"
|
42
|
+
schema = Dry::Validation.Form(&block)
|
43
|
+
container.register(key) do |params|
|
44
|
+
if container.key?(defaults)
|
45
|
+
params = f(:deep_merge).call(params, container[defaults])
|
46
|
+
end
|
47
|
+
validation = schema.call(params)
|
48
|
+
if validation.success?
|
49
|
+
Right(validation.output)
|
50
|
+
else
|
51
|
+
Left([:unprocessable_entity, {}, validation])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
schema
|
55
|
+
end
|
56
|
+
|
57
|
+
# rubocop:enable Metrics/MethodLength
|
58
|
+
|
59
|
+
def defaults(verb = :default, **attributes)
|
60
|
+
key = "validations.defaults.#{ns}.#{verb}" if key.is_a?(Symbol)
|
61
|
+
container.register key, attributes
|
62
|
+
end
|
63
|
+
|
64
|
+
# @param [Symbol] verb
|
65
|
+
# @return [Dry::Validation::Schema::Form]
|
66
|
+
def validation(verb)
|
67
|
+
if (validation = validation_for(verb))
|
68
|
+
container[validation]
|
69
|
+
else
|
70
|
+
# default validation requires no params and provide no results
|
71
|
+
provide_default_validation!
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def provide_default_validation!
|
76
|
+
validate {} unless container.key?("validations.#{ns}.default")
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param [Symbol] verb
|
80
|
+
# @return [String]
|
81
|
+
def validation_for(verb)
|
82
|
+
validation = %W(
|
83
|
+
validations.#{ns}.#{verb}
|
84
|
+
validations.#{ns}.default
|
85
|
+
).detect do |key|
|
86
|
+
container.key?(key)
|
87
|
+
end
|
88
|
+
validation = provide_default_validation! unless validation
|
89
|
+
validation
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate!(verb, params)
|
93
|
+
validation(verb).call(params)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
class Roda
|
6
|
+
module Endpoints
|
7
|
+
# Generic HTTP endpoint abstraction.
|
8
|
+
class Endpoint
|
9
|
+
# Accessing data inside of endpoint.
|
10
|
+
module Verbs
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
# @param [<Symbol>] only
|
14
|
+
# @param [<Symbol>] except
|
15
|
+
# @param attributes [{Symbol=>Object}]
|
16
|
+
def initialize(only: implemented_verbs, except: [], **attributes)
|
17
|
+
only = Array(only).flatten
|
18
|
+
except = Array(except).flatten
|
19
|
+
if ((unknown_only = only - implemented_verbs) +
|
20
|
+
(unknown_except = except - implemented_verbs)).any?
|
21
|
+
params = { only: unknown_only, except: unknown_except }
|
22
|
+
raise ArgumentError, "unknown verbs in params: #{params}"
|
23
|
+
end
|
24
|
+
@verbs = only - except
|
25
|
+
super(**attributes)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [<Symbol>]
|
29
|
+
def implemented_verbs
|
30
|
+
self.class.verbs.to_a
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [<Symbol>]
|
34
|
+
attr_reader :verbs
|
35
|
+
|
36
|
+
def verb(name, **kwargs, &block)
|
37
|
+
key = "operations.#{ns}.#{name}"
|
38
|
+
container.register key, &block
|
39
|
+
singleton_class.verb(name, **kwargs, &container[key])
|
40
|
+
end
|
41
|
+
def_delegator :singleton_class, :verb
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|