roda-endpoints 0.1.0 → 0.2.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 +4 -4
- data/lib/roda/endpoints/endpoint/class_interface.rb +17 -6
- data/lib/roda/endpoints/endpoint/collection.rb +15 -16
- data/lib/roda/endpoints/endpoint/data.rb +3 -0
- data/lib/roda/endpoints/endpoint/item.rb +10 -91
- data/lib/roda/endpoints/endpoint/lookup.rb +73 -0
- data/lib/roda/endpoints/endpoint/namespace.rb +1 -11
- data/lib/roda/endpoints/endpoint/singleton.rb +107 -0
- data/lib/roda/endpoints/endpoint/transactions.rb +0 -9
- data/lib/roda/endpoints/endpoint/validations.rb +18 -29
- data/lib/roda/endpoints/endpoint/verbs.rb +30 -14
- data/lib/roda/endpoints/endpoint.rb +19 -14
- data/lib/roda/endpoints/transactions.rb +1 -2
- data/lib/roda/endpoints/version.rb +1 -1
- data/lib/roda/endpoints.rb +5 -0
- data/lib/roda/plugins/endpoints.rb +118 -31
- metadata +3 -2
- data/lib/roda/endpoints/endpoint/operations.rb +0 -28
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aecfae0fdfb0156f3821d1273697d8ec24b698f6
|
4
|
+
data.tar.gz: 8218ec6e8c89ab3a3967b23f14148a14e3098fcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5d247edfad454ac8b11e196bbb1f88b91c663ea881a4241af55edb079c2f8cd9dfced72fea2f9a1cafd2ad8750b3e0d78a13b993aee7448ae29fbe7beea8e92c
|
7
|
+
data.tar.gz: a7055fce9bc21bf0bd3d5054cd6d218029c1cfba764b48ee06c0e80243f84603a256ae08202345115bcbd32ed9d2c044c930c5fae347cd38f56dfef565cad153
|
@@ -37,6 +37,15 @@ class Roda
|
|
37
37
|
child.defaults = defaults.dup
|
38
38
|
child.statuses = statuses.dup
|
39
39
|
child.verbs = verbs.dup
|
40
|
+
# child.verbs.each do |verb|
|
41
|
+
# key = "operations.#{child.type}.#{verb}"
|
42
|
+
# implementation = "operations.#{type}.#{verb}"
|
43
|
+
# container.register key do
|
44
|
+
# Endpoints.container.resolve implementation
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
child.transactions = transactions.dup
|
48
|
+
child.route(&@route_block)
|
40
49
|
super
|
41
50
|
end
|
42
51
|
|
@@ -49,10 +58,12 @@ class Roda
|
|
49
58
|
@type ||= Inflecto.underscore(Inflecto.demodulize(name)).to_sym
|
50
59
|
end
|
51
60
|
|
52
|
-
# @param [Symbol]
|
61
|
+
# @param [Symbol] verb
|
53
62
|
# @param [Proc] block
|
54
63
|
# @return [Symbol] name of the defined method
|
55
|
-
def verb(
|
64
|
+
def verb(verb, rescue_from: [], &block)
|
65
|
+
self.verbs ||= superclass.verbs
|
66
|
+
(self.verbs += [verb]).freeze
|
56
67
|
rescue_from = Array(rescue_from).flatten
|
57
68
|
if rescue_from.any?
|
58
69
|
block = proc do |*args|
|
@@ -63,10 +74,8 @@ class Roda
|
|
63
74
|
end
|
64
75
|
end
|
65
76
|
end
|
66
|
-
define_method(
|
67
|
-
container.register "operations.#{type}.#{
|
68
|
-
self.verbs ||= superclass.verbs
|
69
|
-
(self.verbs += [name]).freeze
|
77
|
+
define_method(verb, &block)
|
78
|
+
container.register "operations.#{type}.#{verb}", block
|
70
79
|
end
|
71
80
|
|
72
81
|
# @param [String, Symbol] key
|
@@ -117,6 +126,8 @@ class Roda
|
|
117
126
|
def transactions
|
118
127
|
@transactions ||= []
|
119
128
|
end
|
129
|
+
|
130
|
+
attr_writer :transactions
|
120
131
|
end
|
121
132
|
end
|
122
133
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'roda/endpoints/endpoint'
|
4
3
|
require 'roda/endpoints/endpoint/data'
|
5
4
|
require 'roda/endpoints/endpoint/caching'
|
5
|
+
require 'roda/endpoints/endpoint'
|
6
6
|
require 'inflecto'
|
7
7
|
require 'rom/sql'
|
8
8
|
|
@@ -10,13 +10,12 @@ class Roda
|
|
10
10
|
module Endpoints
|
11
11
|
class Endpoint
|
12
12
|
# HTTP endpoint representing a collection of items of the same type.
|
13
|
-
class Collection < Endpoint
|
14
|
-
|
13
|
+
class Collection < Roda::Endpoints::Endpoint
|
14
|
+
prepend Data
|
15
15
|
prepend Caching
|
16
16
|
|
17
17
|
self.attributes += %i(item)
|
18
18
|
self.defaults = defaults.merge(
|
19
|
-
type: :collection,
|
20
19
|
last_modified: :last_modified
|
21
20
|
)
|
22
21
|
|
@@ -28,28 +27,28 @@ class Roda
|
|
28
27
|
@last_modified ? repository.public_send(@last_modified) : super
|
29
28
|
end
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
**params
|
36
|
-
)
|
30
|
+
# @param [Symbol] name
|
31
|
+
# @param [Hash] params
|
32
|
+
def child(name: item_name, type: Item, **params)
|
33
|
+
super(name: name, type: type, **params)
|
37
34
|
end
|
38
35
|
|
39
36
|
# @return [Symbol]
|
40
|
-
def
|
41
|
-
Inflecto.singularize(name).to_sym
|
37
|
+
def item_name
|
38
|
+
@item_name ||= Inflecto.singularize(name).to_sym
|
42
39
|
end
|
43
40
|
|
41
|
+
# @route /{collection.name}
|
44
42
|
route do |r, endpoint| # r.collection :articles do |articles|
|
45
43
|
r.last_modified endpoint.last_modified if endpoint.last_modified
|
46
44
|
|
47
45
|
endpoint.verbs.each do |verb|
|
48
|
-
# @route #{verb}
|
46
|
+
# @route #{verb} /{collection.name}
|
49
47
|
r.public_send(verb, transaction: verb)
|
50
|
-
|
51
|
-
r.child **endpoint.item if endpoint.item # child by: :id
|
52
48
|
end
|
49
|
+
|
50
|
+
# @route #{verb} /{collection.name}/{id}
|
51
|
+
r.child **endpoint.item if endpoint.item # child by: :id
|
53
52
|
end
|
54
53
|
|
55
54
|
verb :get do |params|
|
@@ -57,7 +56,7 @@ class Roda
|
|
57
56
|
end
|
58
57
|
|
59
58
|
verb :post do |params|
|
60
|
-
params = params[
|
59
|
+
params = params[item_name] || {}
|
61
60
|
Right(repository.create(**params))
|
62
61
|
end
|
63
62
|
|
@@ -1,116 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'roda/endpoints/endpoint'
|
3
|
+
require 'roda/endpoints/endpoint/singleton'
|
4
4
|
require 'roda/endpoints/endpoint/data'
|
5
|
-
require 'roda/endpoints/endpoint/caching'
|
6
5
|
|
7
6
|
class Roda
|
8
7
|
module Endpoints
|
9
8
|
class Endpoint
|
10
9
|
# HTTP endpoint representing a specific item of collection uniquely
|
11
10
|
# identified by some parameter.
|
12
|
-
class Item <
|
13
|
-
|
14
|
-
include Caching
|
11
|
+
class Item < Singleton
|
12
|
+
prepend Data
|
15
13
|
|
16
|
-
self.attributes += %i(id by
|
14
|
+
self.attributes += %i(id by on)
|
17
15
|
self.defaults = defaults.merge(
|
18
16
|
by: :fetch,
|
19
|
-
|
20
|
-
|
21
|
-
end,
|
22
|
-
last_modified: :updated_at
|
17
|
+
on: :id,
|
18
|
+
finder: -> { repository.public_send(by, id) }
|
23
19
|
)
|
24
20
|
|
25
21
|
# @return [Symbol]
|
26
22
|
attr_reader :by
|
27
23
|
|
28
|
-
# @return [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
attr_reader :finder
|
24
|
+
# @return [Integer]
|
25
|
+
def id
|
26
|
+
captures.first
|
27
|
+
end
|
33
28
|
|
34
29
|
# @return [Endpoint::Collection]
|
35
30
|
def collection
|
36
31
|
parent
|
37
32
|
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
33
|
end
|
115
34
|
end
|
116
35
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'roda/endpoints/endpoint/namespace'
|
4
|
+
|
5
|
+
class Roda
|
6
|
+
module Endpoints
|
7
|
+
class Endpoint
|
8
|
+
# Lookup everything in the container.
|
9
|
+
module Lookup
|
10
|
+
prepend Namespace
|
11
|
+
|
12
|
+
# @return [Dry::Container::Mixin]
|
13
|
+
def container
|
14
|
+
@container || parent&.container || Roda::Endpoints.roda_class
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param [<String>] paths
|
18
|
+
def lookup(*paths, key: nil, scope: nil, default: nil)
|
19
|
+
if key && scope
|
20
|
+
paths += lookup_keys(key: key, scope: scope, default: default)
|
21
|
+
end
|
22
|
+
paths.flatten.detect { |full_key| container.key?(full_key) }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param [#to_s] key
|
26
|
+
# @param [#to_s] scope
|
27
|
+
def lookup_keys(key:, scope: nil, default: nil, lookup: lookup_path)
|
28
|
+
scope = "#{scope}." unless scope.to_s.end_with?('.')
|
29
|
+
keys = []
|
30
|
+
Array(key).flatten.map do |sect|
|
31
|
+
lookup.each do |choice|
|
32
|
+
[sect, default].compact.each do |value|
|
33
|
+
full_key = "#{scope}#{choice}.#{value}"
|
34
|
+
yield full_key if block_given?
|
35
|
+
keys << full_key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
keys
|
40
|
+
end
|
41
|
+
|
42
|
+
def lookup_path
|
43
|
+
path = [ns]
|
44
|
+
endpoint = self.class
|
45
|
+
while endpoint < Roda::Endpoints::Endpoint
|
46
|
+
path << endpoint.type
|
47
|
+
endpoint = endpoint.superclass
|
48
|
+
end
|
49
|
+
path
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Symbol] verb
|
53
|
+
# @return [String]
|
54
|
+
def operation_for(verb)
|
55
|
+
lookup key: verb, scope: 'operations.'
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Symbol] verb
|
59
|
+
# @return [String]
|
60
|
+
def validation_for(verb)
|
61
|
+
lookup(key: verb, default: 'any', scope: 'validations.') ||
|
62
|
+
provide_default_validation!
|
63
|
+
end
|
64
|
+
|
65
|
+
# @param [Symbol] verb
|
66
|
+
# @return [String]
|
67
|
+
def transaction_for(verb)
|
68
|
+
lookup key: verb, scope: 'transactions'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -19,7 +19,7 @@ class Roda
|
|
19
19
|
@ns = ns
|
20
20
|
unless parent == Undefined
|
21
21
|
@parent = parent
|
22
|
-
@ns =
|
22
|
+
@ns = [parent.ns, ns].compact.join('.')
|
23
23
|
end
|
24
24
|
super(name: name, **attributes)
|
25
25
|
end
|
@@ -32,16 +32,6 @@ class Roda
|
|
32
32
|
|
33
33
|
# @return [Endpoint]
|
34
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
35
|
end
|
46
36
|
end
|
47
37
|
end
|
@@ -0,0 +1,107 @@
|
|
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 Singleton < Roda::Endpoints::Endpoint
|
13
|
+
prepend Caching
|
14
|
+
|
15
|
+
self.attributes += %i(finder last_modified)
|
16
|
+
self.defaults = defaults.merge(last_modified: :updated_at)
|
17
|
+
|
18
|
+
# @return [Symbol]
|
19
|
+
attr_reader :finder
|
20
|
+
|
21
|
+
# @return [ROM::Struct]
|
22
|
+
def entity
|
23
|
+
@entity ||= fetch_entity
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_writer :entity
|
27
|
+
|
28
|
+
# @return [ROM::Struct]
|
29
|
+
def fetch_entity
|
30
|
+
instance_exec(&finder)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Time]
|
34
|
+
def last_modified
|
35
|
+
@last_modified ? entity.public_send(@last_modified) : super
|
36
|
+
end
|
37
|
+
|
38
|
+
route do |r, endpoint|
|
39
|
+
endpoint.verbs.each do |verb|
|
40
|
+
# @route #{verb} /{collection.name}/{id}
|
41
|
+
r.public_send(verb, transaction: verb)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
transaction :get do |endpoint|
|
46
|
+
step :retrieve, with: endpoint.operation_for(:get)
|
47
|
+
end
|
48
|
+
|
49
|
+
transaction :patch do |endpoint|
|
50
|
+
step :validate, with: endpoint.validation_for(:patch)
|
51
|
+
step :persist, with: endpoint.operation_for(:patch)
|
52
|
+
end
|
53
|
+
|
54
|
+
transaction :put do |endpoint|
|
55
|
+
step :validate, with: endpoint.validation_for(:put)
|
56
|
+
# step :reset, with: 'endpoints.operations.reset'
|
57
|
+
step :persist, with: endpoint.operation_for(:put)
|
58
|
+
end
|
59
|
+
|
60
|
+
transaction :delete do |endpoint|
|
61
|
+
step :validate, with: endpoint.validation_for(:delete)
|
62
|
+
step :persist, with: endpoint.operation_for(:delete)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @route GET /{collection.name}/{id}
|
66
|
+
# @!method get(params)
|
67
|
+
# @param [Hash] params
|
68
|
+
# @return [Dry::Monads::Either]
|
69
|
+
verb :get do |_params|
|
70
|
+
Right(entity)
|
71
|
+
end
|
72
|
+
|
73
|
+
# @route PATCH /{collection.name}/{id}
|
74
|
+
# @!method patch(params)
|
75
|
+
# @param [Hash] params
|
76
|
+
# @return [Dry::Monads::Either]
|
77
|
+
verb :patch do |params|
|
78
|
+
changeset = params[name]
|
79
|
+
Right(repository.update(id, changeset))
|
80
|
+
end
|
81
|
+
|
82
|
+
# @route PUT /{collection.name}/{id}
|
83
|
+
# @!method put(params)
|
84
|
+
# @param [Hash] params
|
85
|
+
# @return [Dry::Monads::Either]
|
86
|
+
verb :put do |params|
|
87
|
+
changeset = entity.to_hash.keys.each_with_object({}) do |key, changes|
|
88
|
+
changes[key] = nil
|
89
|
+
end.merge(params[name] || {})
|
90
|
+
Right(repository.update(id, changeset))
|
91
|
+
end
|
92
|
+
|
93
|
+
# @route DELETE /{collection.name}/{id}
|
94
|
+
# @!method delete(params)
|
95
|
+
# @param [Hash] params
|
96
|
+
# @return [Dry::Monads::Either]
|
97
|
+
verb :delete do |_params|
|
98
|
+
if (result = repository.delete(id))
|
99
|
+
Right(nil)
|
100
|
+
else
|
101
|
+
Left(result)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -23,15 +23,6 @@ class Roda
|
|
23
23
|
@transactions
|
24
24
|
end
|
25
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
26
|
# @param [Symbol, String] name
|
36
27
|
# @param [Proc] block
|
37
28
|
def step(name, only: [], **kwargs, &block)
|
@@ -12,13 +12,12 @@ class Roda
|
|
12
12
|
module Validations
|
13
13
|
include Functions::Shortcut
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
super
|
15
|
+
def defaults(verb = :any, **attributes)
|
16
|
+
key = "validations.defaults.#{ns}.#{verb}" if key.is_a?(Symbol)
|
17
|
+
container.register key, attributes
|
19
18
|
end
|
20
19
|
|
21
|
-
# @param [String, Symbol]
|
20
|
+
# @param [String, Symbol] verb
|
22
21
|
# @param [Proc] block
|
23
22
|
#
|
24
23
|
# @example
|
@@ -36,11 +35,11 @@ class Roda
|
|
36
35
|
# require(:confirm_changes).
|
37
36
|
# end
|
38
37
|
# end
|
39
|
-
def validate(
|
40
|
-
defaults = "validations.defaults.#{ns}.#{
|
41
|
-
|
38
|
+
def validate(verb = :any, &block)
|
39
|
+
defaults = "validations.defaults.#{ns}.#{verb}"
|
40
|
+
verb = "validations.#{ns}.#{verb}"
|
42
41
|
schema = Dry::Validation.Form(&block)
|
43
|
-
container.register(
|
42
|
+
container.register(verb) do |params|
|
44
43
|
if container.key?(defaults)
|
45
44
|
params = f(:deep_merge).call(params, container[defaults])
|
46
45
|
end
|
@@ -56,11 +55,6 @@ class Roda
|
|
56
55
|
|
57
56
|
# rubocop:enable Metrics/MethodLength
|
58
57
|
|
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
58
|
# @param [Symbol] verb
|
65
59
|
# @return [Dry::Validation::Schema::Form]
|
66
60
|
def validation(verb)
|
@@ -73,24 +67,19 @@ class Roda
|
|
73
67
|
end
|
74
68
|
|
75
69
|
def provide_default_validation!
|
76
|
-
validate {} unless container.key?("validations.#{ns}.
|
70
|
+
validate {} unless container.key?("validations.#{ns}.any")
|
77
71
|
end
|
78
72
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
validations.#{ns}
|
84
|
-
|
85
|
-
|
86
|
-
|
73
|
+
def prepare_validations!
|
74
|
+
return if @validations_prepared
|
75
|
+
(verbs - %i(get head options)).each do |verb|
|
76
|
+
key = "validations.#{ns}.#{verb}"
|
77
|
+
default = "validations.#{ns}.any"
|
78
|
+
unless container.key?(key) || container.key?(default)
|
79
|
+
provide_default_validation!
|
80
|
+
end
|
87
81
|
end
|
88
|
-
|
89
|
-
validation
|
90
|
-
end
|
91
|
-
|
92
|
-
def validate!(verb, params)
|
93
|
-
validation(verb).call(params)
|
82
|
+
@validations_prepared = true
|
94
83
|
end
|
95
84
|
end
|
96
85
|
end
|
@@ -1,19 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'forwardable'
|
4
|
-
|
5
3
|
class Roda
|
6
4
|
module Endpoints
|
7
5
|
# Generic HTTP endpoint abstraction.
|
8
6
|
class Endpoint
|
9
7
|
# Accessing data inside of endpoint.
|
10
8
|
module Verbs
|
11
|
-
extend Forwardable
|
12
|
-
|
13
|
-
# @param [<Symbol>] only
|
14
|
-
# @param [<Symbol>] except
|
15
9
|
# @param attributes [{Symbol=>Object}]
|
16
|
-
def initialize(
|
10
|
+
def initialize(**attributes)
|
11
|
+
@verbs = verbs_to_implement(**attributes)
|
12
|
+
super(**attributes)
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param only [<Symbol>]
|
16
|
+
# @param except [<Symbol>]
|
17
|
+
def verbs_to_implement(only: implemented_verbs, except: [], **_kwargs)
|
17
18
|
only = Array(only).flatten
|
18
19
|
except = Array(except).flatten
|
19
20
|
if ((unknown_only = only - implemented_verbs) +
|
@@ -21,8 +22,7 @@ class Roda
|
|
21
22
|
params = { only: unknown_only, except: unknown_except }
|
22
23
|
raise ArgumentError, "unknown verbs in params: #{params}"
|
23
24
|
end
|
24
|
-
|
25
|
-
super(**attributes)
|
25
|
+
only - except
|
26
26
|
end
|
27
27
|
|
28
28
|
# @return [<Symbol>]
|
@@ -33,12 +33,28 @@ class Roda
|
|
33
33
|
# @return [<Symbol>]
|
34
34
|
attr_reader :verbs
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
# @param [#to_s] verb
|
37
|
+
# @param [Hash] kwargs
|
38
|
+
# @param [Proc] block
|
39
|
+
def verb(verb, **kwargs, &block)
|
40
|
+
key = "operations.#{ns}.#{verb}"
|
41
|
+
block ||= container[key]
|
42
|
+
singleton_class.verb(verb, **kwargs, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def prepare_verbs!
|
46
|
+
return if @verbs_prepared
|
47
|
+
verbs.each do |verb|
|
48
|
+
key = "operations.#{ns}.#{verb}"
|
49
|
+
next if container.key?(key)
|
50
|
+
endpoint = self
|
51
|
+
operation = method(verb)
|
52
|
+
container.register key do |*args|
|
53
|
+
endpoint.instance_exec(*args, &operation)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
@verbs_prepared = true
|
40
57
|
end
|
41
|
-
def_delegator :singleton_class, :verb
|
42
58
|
end
|
43
59
|
end
|
44
60
|
end
|
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'roda/endpoints'
|
4
4
|
require 'roda/endpoints/endpoint/class_interface'
|
5
|
+
require 'roda/endpoints/endpoint/lookup'
|
5
6
|
require 'roda/endpoints/endpoint/namespace'
|
6
|
-
require 'roda/endpoints/endpoint/operations'
|
7
7
|
require 'roda/endpoints/endpoint/transactions'
|
8
8
|
require 'roda/endpoints/endpoint/validations'
|
9
9
|
require 'roda/endpoints/endpoint/verbs'
|
@@ -18,6 +18,7 @@ class Roda
|
|
18
18
|
|
19
19
|
autoload :Collection, 'roda/endpoints/endpoint/collection'
|
20
20
|
autoload :Item, 'roda/endpoints/endpoint/item'
|
21
|
+
autoload :Singleton, 'roda/endpoints/endpoint/singleton'
|
21
22
|
|
22
23
|
self.attributes = %i(container type).freeze
|
23
24
|
self.defaults = EMPTY_HASH
|
@@ -42,33 +43,37 @@ class Roda
|
|
42
43
|
prepend Namespace
|
43
44
|
prepend Verbs
|
44
45
|
prepend Validations
|
45
|
-
prepend Operations
|
46
46
|
include Transactions
|
47
|
+
prepend Lookup
|
47
48
|
|
48
|
-
|
49
|
-
def container
|
50
|
-
@container || parent&.container
|
51
|
-
end
|
49
|
+
attr_accessor :captures
|
52
50
|
|
53
51
|
# @return [Symbol]
|
54
|
-
|
52
|
+
def type
|
53
|
+
self.class.type
|
54
|
+
end
|
55
55
|
|
56
|
-
# @param [
|
57
|
-
# @param [
|
58
|
-
# @param [{Symbol=>Object}] attributes
|
56
|
+
# @param type [:collection, :item]
|
57
|
+
# @param attributes [{Symbol=>Object}]
|
59
58
|
def with(type: self.class, **attributes)
|
60
59
|
type.new to_hash.merge(attributes).merge(inheritable_attributes)
|
61
60
|
end
|
62
61
|
|
62
|
+
# @param [Class(Endpoint)] type
|
63
|
+
# @param [Hash] params
|
64
|
+
def child(type: Singleton, **params)
|
65
|
+
with(type: type, **params)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [{Symbol=>Object}]
|
63
69
|
def inheritable_attributes
|
64
|
-
{
|
65
|
-
parent: self,
|
66
|
-
container: container
|
67
|
-
}
|
70
|
+
{ parent: self, container: container }
|
68
71
|
end
|
69
72
|
|
70
73
|
# @return [Proc]
|
71
74
|
def route
|
75
|
+
prepare_validations!
|
76
|
+
prepare_verbs!
|
72
77
|
self.class.route
|
73
78
|
end
|
74
79
|
|
@@ -7,8 +7,7 @@ require 'forwardable'
|
|
7
7
|
|
8
8
|
class Roda
|
9
9
|
module Endpoints
|
10
|
-
# The DSL for defining {Transactions transactions}
|
11
|
-
# The {Platform}.
|
10
|
+
# The DSL for defining {Transactions transactions} for endpoints.
|
12
11
|
class Transactions
|
13
12
|
extend Dry::Configurable
|
14
13
|
extend Forwardable
|
data/lib/roda/endpoints.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'roda/endpoints/version'
|
3
3
|
require 'dry/core/constants'
|
4
|
+
require 'dry/container'
|
4
5
|
|
5
6
|
class Roda
|
6
7
|
# Endpoints abstractions
|
@@ -15,6 +16,10 @@ class Roda
|
|
15
16
|
def self.container
|
16
17
|
@container ||= Dry::Container.new
|
17
18
|
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_accessor :roda_class
|
22
|
+
end
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
@@ -11,7 +11,7 @@ class Roda
|
|
11
11
|
# Endpoints plugin for {Roda}
|
12
12
|
module Endpoints
|
13
13
|
# @param [Class(Roda)] app
|
14
|
-
def self.load_dependencies(app)
|
14
|
+
def self.load_dependencies(app, **_opts)
|
15
15
|
app.plugin :all_verbs
|
16
16
|
app.plugin :head
|
17
17
|
app.plugin :caching
|
@@ -22,19 +22,49 @@ class Roda
|
|
22
22
|
app.plugin :json_parser
|
23
23
|
app.plugin :indifferent_params
|
24
24
|
app.plugin :json, classes: [Array, Hash, ROM::Struct]
|
25
|
-
return if app.respond_to?(:[])
|
26
|
-
require 'dry-container'
|
27
|
-
app.extend Dry::Container::Mixin
|
28
25
|
app.plugin :flow
|
29
26
|
end
|
30
27
|
|
28
|
+
# @param [Class(Roda)] app
|
29
|
+
# @param [Hash] opts
|
30
|
+
def self.configure(app, container: app, **opts)
|
31
|
+
opts = (app.opts[:endpoints] || {}).merge(opts)
|
32
|
+
unless container.respond_to? :resolve
|
33
|
+
require 'dry-container'
|
34
|
+
container.extend Dry::Container::Mixin
|
35
|
+
end
|
36
|
+
app.opts[:endpoints] = opts.merge(container: container)
|
37
|
+
Roda::Endpoints.roda_class ||= app
|
38
|
+
end
|
39
|
+
|
40
|
+
# {ClassMethods#register `Roda.register`} &&
|
41
|
+
# {ClassMethods#merge `Roda.merge`}
|
42
|
+
module ClassMethods
|
43
|
+
# @param [String, Symbol] name
|
44
|
+
# @param [Array] args
|
45
|
+
# @param [Proc] block
|
46
|
+
def register(name, *args, &block)
|
47
|
+
opts[:container].register(name, *args, &block)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [String, Symbol] name
|
51
|
+
# @param [Array] args
|
52
|
+
# @param [Proc] block
|
53
|
+
def merge(name, *args, &block)
|
54
|
+
opts[:container].merge(name, *args, &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
31
58
|
# `Roda::RodaRequest` instant extensions.
|
59
|
+
# rubocop:disable Metrics/ModuleLength
|
32
60
|
module RequestMethods
|
33
61
|
# Implements collection endpoint using given args
|
34
62
|
#
|
35
63
|
# @param name [Symbol]
|
36
64
|
# @param item [{Symbol=>Object}]
|
37
65
|
# @param kwargs [{Symbol=>Object}]
|
66
|
+
# @param type [Class(Roda::Endpoints::Endpoint::Collection)]
|
67
|
+
# @param on [String, Symbol]
|
38
68
|
# @param (see Endpoint::Collection#initialize)
|
39
69
|
# @see Endpoint::Collection.defaults
|
40
70
|
# @yieldparam endpoint [Collection]
|
@@ -64,31 +94,28 @@ class Roda
|
|
64
94
|
#
|
65
95
|
# r.child :id, only: %i(get delete)
|
66
96
|
# end
|
67
|
-
def collection(name,
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
97
|
+
def collection(name,
|
98
|
+
item: { on: :id },
|
99
|
+
type: Roda::Endpoints::Endpoint::Collection,
|
100
|
+
on: name.to_s,
|
101
|
+
**kwargs)
|
102
|
+
endpoint name: name,
|
103
|
+
item: item,
|
104
|
+
type: type,
|
105
|
+
on: on,
|
106
|
+
**kwargs do |endpoint|
|
77
107
|
yield endpoint if block_given?
|
78
|
-
instance_exec(self, endpoint, &endpoint.route)
|
79
108
|
end
|
80
|
-
endpoints.pop #=> endpoint
|
81
109
|
end
|
82
110
|
|
83
111
|
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
84
112
|
|
85
|
-
# @param [Symbol]
|
86
|
-
# @param [
|
87
|
-
# @param [Hash] kwargs
|
113
|
+
# @param on [Symbol]
|
114
|
+
# @param kwargs [Hash]
|
88
115
|
#
|
89
116
|
# @example
|
90
117
|
# r.collection :articles do |articles|
|
91
|
-
# r.
|
118
|
+
# r.item :id
|
92
119
|
# end
|
93
120
|
#
|
94
121
|
# # is equivalent to
|
@@ -105,24 +132,75 @@ class Roda
|
|
105
132
|
# end
|
106
133
|
# end
|
107
134
|
# end
|
108
|
-
def
|
135
|
+
def item(on: :id,
|
136
|
+
type: Roda::Endpoints::Endpoint::Item,
|
137
|
+
**kwargs)
|
138
|
+
unless current_endpoint.is_a?(Roda::Endpoints::Endpoint::Collection)
|
139
|
+
raise ArgumentError,
|
140
|
+
"#{self.class}#item called not within a collection endpoint"
|
141
|
+
end
|
109
142
|
# @route /{collection.name}/{id}
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
143
|
+
endpoint(
|
144
|
+
on: on,
|
145
|
+
name: current_endpoint.item_name,
|
146
|
+
type: type,
|
147
|
+
**kwargs
|
148
|
+
) do |endpoint|
|
149
|
+
yield endpoint if block_given?
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# @overload singleton(name, entity, type:, **kwargs)
|
154
|
+
# @overload singleton(name:, entity:, type:, **kwargs)
|
155
|
+
def singleton(*args,
|
156
|
+
name: args.first,
|
157
|
+
entity: args.last,
|
158
|
+
type: Roda::Endpoints::Endpoint::Singleton,
|
159
|
+
**kwargs)
|
160
|
+
endpoint(
|
161
|
+
name: name,
|
162
|
+
entity: entity,
|
163
|
+
type: type,
|
164
|
+
**kwargs
|
165
|
+
) do |endpoint|
|
166
|
+
yield endpoint if block_given?
|
116
167
|
end
|
117
168
|
end
|
118
169
|
|
119
170
|
private
|
120
171
|
|
172
|
+
# @param [Class(Roda::Endpoints::Endpoint::Singleton)] type
|
173
|
+
# @param [Dry::Container::Mixin, #register, #resolve, #merge] container
|
174
|
+
# @param [Roda::Endpoints::Endpoint] parent
|
175
|
+
# @param [Hash] kwargs
|
176
|
+
# rubocop:disable Metrics/ParameterLists
|
177
|
+
def endpoint(name:,
|
178
|
+
type:,
|
179
|
+
container: roda_class.opts[:endpoints][:container],
|
180
|
+
parent: current_endpoint,
|
181
|
+
on: name,
|
182
|
+
**kwargs)
|
183
|
+
on on do |*captures|
|
184
|
+
with_current_endpoint parent.child(
|
185
|
+
name: name,
|
186
|
+
type: type,
|
187
|
+
container: container,
|
188
|
+
parent: parent,
|
189
|
+
on: on,
|
190
|
+
captures: captures,
|
191
|
+
**kwargs
|
192
|
+
) do |endpoint|
|
193
|
+
yield endpoint if block_given?
|
194
|
+
instance_exec(self, endpoint, &endpoint.route)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
121
199
|
# @param [Symbol] verb
|
122
200
|
def match_transaction(verb)
|
123
|
-
resolve
|
201
|
+
resolve current_endpoint.transactions.key_for(verb) do |transaction|
|
124
202
|
transaction.call(params) do |m|
|
125
|
-
statuses =
|
203
|
+
statuses = current_endpoint.class.statuses[verb]
|
126
204
|
|
127
205
|
m.success do |result|
|
128
206
|
response.status = statuses[:success]
|
@@ -143,17 +221,26 @@ class Roda
|
|
143
221
|
end
|
144
222
|
|
145
223
|
# @return [Endpoint]
|
146
|
-
def
|
224
|
+
def current_endpoint
|
147
225
|
endpoints.last
|
148
226
|
end
|
149
227
|
|
150
228
|
# @return [<Endpoint>]
|
151
229
|
def endpoints
|
152
230
|
@endpoints ||= [Roda::Endpoints::Endpoint.new(
|
153
|
-
name: :root,
|
231
|
+
name: :root,
|
232
|
+
ns: nil,
|
233
|
+
container: roda_class.opts[:endpoints][:container]
|
154
234
|
)]
|
155
235
|
end
|
236
|
+
|
237
|
+
def with_current_endpoint(endpoint)
|
238
|
+
endpoints.push endpoint
|
239
|
+
yield endpoint
|
240
|
+
endpoints.pop
|
241
|
+
end
|
156
242
|
end
|
243
|
+
# rubocop:enable Metrics/ModuleLength
|
157
244
|
end
|
158
245
|
|
159
246
|
register_plugin :endpoints, Endpoints
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roda-endpoints
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Semyonov
|
@@ -340,8 +340,9 @@ files:
|
|
340
340
|
- lib/roda/endpoints/endpoint/collection.rb
|
341
341
|
- lib/roda/endpoints/endpoint/data.rb
|
342
342
|
- lib/roda/endpoints/endpoint/item.rb
|
343
|
+
- lib/roda/endpoints/endpoint/lookup.rb
|
343
344
|
- lib/roda/endpoints/endpoint/namespace.rb
|
344
|
-
- lib/roda/endpoints/endpoint/
|
345
|
+
- lib/roda/endpoints/endpoint/singleton.rb
|
345
346
|
- lib/roda/endpoints/endpoint/transactions.rb
|
346
347
|
- lib/roda/endpoints/endpoint/validations.rb
|
347
348
|
- lib/roda/endpoints/endpoint/verbs.rb
|
@@ -1,28 +0,0 @@
|
|
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
|