json_rpc_kit 0.9.0.rc1
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/lib/json_rpc_kit/endpoint.rb +758 -0
- data/lib/json_rpc_kit/errors.rb +82 -0
- data/lib/json_rpc_kit/helpers.rb +81 -0
- data/lib/json_rpc_kit/service.rb +725 -0
- data/lib/json_rpc_kit/transport_options.rb +220 -0
- data/lib/json_rpc_kit/version.rb +5 -0
- data/lib/json_rpc_kit.rb +28 -0
- metadata +62 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonRpcKit
|
|
4
|
+
# Handles transformation, filtering, and merging of transport options
|
|
5
|
+
#
|
|
6
|
+
# ## Transport options
|
|
7
|
+
#
|
|
8
|
+
# A typical transport will provide wrappers for {Endpoint} or {Service} that configures what arbitrary options
|
|
9
|
+
# may be relevant for the transport. e.g. HTTP headers, MQTT Quality of Service etc...
|
|
10
|
+
#
|
|
11
|
+
# The transport can provide a {#prefix} to distinguish its options from other transports, a {#filter} to select
|
|
12
|
+
# which option keys it accepts, and a {#merge} proc to describe how options from multiple sources should be
|
|
13
|
+
# combined.
|
|
14
|
+
#
|
|
15
|
+
# The toolkit will then ensure that the transport only receives valid options.
|
|
16
|
+
#
|
|
17
|
+
# ## User space options
|
|
18
|
+
#
|
|
19
|
+
# Users of {Endpoint}, and {Service} will send and receive options with the {#prefix},
|
|
20
|
+
# and errors will be raised if invalid options are used.
|
|
21
|
+
#
|
|
22
|
+
# In cases where an {Endpoint} or {Service} may be used with code supporting multiple transports,
|
|
23
|
+
# users can also provide a means to {#ignore} certain options so they are not seen by the transport but do not raise
|
|
24
|
+
# errors.
|
|
25
|
+
class TransportOptions
|
|
26
|
+
# rubocop:disable Style/RescueModifier
|
|
27
|
+
|
|
28
|
+
# Default {#merge} proc
|
|
29
|
+
# - Hashes and Sets are merged
|
|
30
|
+
# - Arrays are concatenated and deduplicated
|
|
31
|
+
# - Other values are replaced
|
|
32
|
+
DEFAULT_MERGE = proc do |_key, old, new|
|
|
33
|
+
case old
|
|
34
|
+
when Hash, Set
|
|
35
|
+
old.merge(new) rescue new
|
|
36
|
+
when Array
|
|
37
|
+
(old + Array(new)).uniq rescue nil
|
|
38
|
+
else
|
|
39
|
+
new
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
# rubocop:enable Style/RescueModifier
|
|
43
|
+
|
|
44
|
+
# Options managed by JsonRpcKit
|
|
45
|
+
RESERVED_OPTIONS = %i[async timeout converter].freeze
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
# @!visibility private
|
|
49
|
+
# Create a TransportOptions from opts hash, extracting and removing prefix/filter/merge
|
|
50
|
+
# @param opts [Hash] options hash to extract from (mutated - config keys removed)
|
|
51
|
+
# @return [TransportOptions] new instance
|
|
52
|
+
def create_from_opts(opts)
|
|
53
|
+
config = opts.delete(:options_config)
|
|
54
|
+
return config if config
|
|
55
|
+
|
|
56
|
+
config_opts = opts.slice(:prefix, :filter, :merge, :ignore)
|
|
57
|
+
opts.replace(opts.except(:prefix, :filter, :merge, :ignore))
|
|
58
|
+
new(**config_opts)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [String, nil] an optional prefix so user space can distinguish options from different transports
|
|
63
|
+
attr_reader :prefix
|
|
64
|
+
|
|
65
|
+
# Permitted transport space option keys
|
|
66
|
+
#
|
|
67
|
+
# @return [Array<Symbol>] list for 'Hash#slice'
|
|
68
|
+
# @return [Proc] 'Hash#filter |key, value|' block
|
|
69
|
+
# @return [nil] allow all options, transport is responsible for handling unsupported options
|
|
70
|
+
attr_reader :filter
|
|
71
|
+
|
|
72
|
+
# @return [Proc] 'Hash#merge block: |key, old, new|' for combining options (in transport space)
|
|
73
|
+
# @return [nil] where a transport does not support any options at all
|
|
74
|
+
attr_reader :merge
|
|
75
|
+
|
|
76
|
+
# @return [Proc] (Hash#reject block |key,value|) to ignore options (user space keys)
|
|
77
|
+
# @return [Array<String>] list of prefixes to ignore
|
|
78
|
+
# @return [nil] do not ignore any options
|
|
79
|
+
attr_reader :ignore
|
|
80
|
+
|
|
81
|
+
# @!visibility private
|
|
82
|
+
def initialize(prefix: nil, filter: nil, ignore: nil, merge: DEFAULT_MERGE)
|
|
83
|
+
raise ArgumentError, "merge(#{merge.class}): must be Proc or nil" unless merge.nil? || merge.respond_to?(:call)
|
|
84
|
+
|
|
85
|
+
@merge = merge
|
|
86
|
+
@prefix = prefix
|
|
87
|
+
# no merge proc means the transport does not support any options
|
|
88
|
+
raise ArgumentError, 'filter: not relevant without merge:' if !merge && filter
|
|
89
|
+
|
|
90
|
+
@filter = build_filter(merge ? filter : [])
|
|
91
|
+
@ignore = build_ignore(ignore)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @!visibility private
|
|
95
|
+
# Add prefix to option keys
|
|
96
|
+
# @param opts [Hash] transport space options
|
|
97
|
+
# @return [Hash] user_space options
|
|
98
|
+
def to_user_space(opts)
|
|
99
|
+
opts.transform_keys { |k| prefix_key(k) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# @!visibility private
|
|
103
|
+
# Remove prefix from option keys
|
|
104
|
+
# @param opts [Hash] user space options
|
|
105
|
+
# @return [Hash] transport space options
|
|
106
|
+
def to_transport_space(opts)
|
|
107
|
+
return opts unless prefix
|
|
108
|
+
|
|
109
|
+
opts.transform_keys { |k| de_prefix_key(k) }
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @!visibility private
|
|
113
|
+
# Filter option keys to only those relevant for the transport
|
|
114
|
+
#
|
|
115
|
+
# removes explicitly ignored options then applies the filter
|
|
116
|
+
#
|
|
117
|
+
# @param opts [Hash] options to filter (user space)
|
|
118
|
+
# @return [Hash] filtered options (user space)
|
|
119
|
+
# @raise [ArgumentError] if opts contains unsupported options
|
|
120
|
+
def filter_opts(opts)
|
|
121
|
+
# Silently discard ignored options, but never ignore RESERVED_OPTIONS. Note ignore is uer_space keys!!
|
|
122
|
+
opts = opts.reject { |k, v| !RESERVED_OPTIONS.include?(k) && ignore.call(k, v) } if ignore
|
|
123
|
+
return opts unless filter
|
|
124
|
+
return filter_list(opts) if filter.is_a?(Array)
|
|
125
|
+
|
|
126
|
+
opts.filter { |k, v| RESERVED_OPTIONS.include?(k) || filter.call(de_prefix_key(k), v) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @!visibility private
|
|
130
|
+
# @param list [Array<Hash>] list of option hashes to merge (user space)
|
|
131
|
+
# @return [Hash] merged and filtered options (transport space)
|
|
132
|
+
def reduce_to_transport_space(*list)
|
|
133
|
+
# called: only for a Service response_options
|
|
134
|
+
# called: <not implemented> for Endpoint::Batch to reduce per-request request-options
|
|
135
|
+
# Both cases - list is user space (prefixed and should have been filtered), result is transport space.
|
|
136
|
+
|
|
137
|
+
to_transport_space(list.reduce({}) { |old, new| merge_opts(old, new) })
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @!visibility private
|
|
141
|
+
# @param old_hash [Hash] user space, filtered
|
|
142
|
+
# @param new_hash [Hash] user space, unfiltered
|
|
143
|
+
# @param filtered [Boolean] set true if new_hash has already been filtered.
|
|
144
|
+
# @return [Hash] the merged, user space, result
|
|
145
|
+
def merge_opts(old_hash, new_hash, filtered: false)
|
|
146
|
+
new_hash = filter_opts(new_hash) unless filtered
|
|
147
|
+
old_hash.merge(new_hash) { |key, old_val, new_val| merge_key(key, old_val, new_val) }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private
|
|
151
|
+
|
|
152
|
+
# Call merge proc with key transformation (removes prefix from key before calling)
|
|
153
|
+
# @param key [Symbol] key to merge (user_space)
|
|
154
|
+
# @param old [Object] old value
|
|
155
|
+
# @param new [Object] new value
|
|
156
|
+
# @return [Object] merged value
|
|
157
|
+
def merge_key(key, old, new)
|
|
158
|
+
return new if RESERVED_OPTIONS.include?(key)
|
|
159
|
+
return nil unless merge
|
|
160
|
+
|
|
161
|
+
merge.call(de_prefix_key(key), old, new)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def prefix_key(key)
|
|
165
|
+
key = key.to_sym
|
|
166
|
+
return key unless prefix
|
|
167
|
+
return key if RESERVED_OPTIONS.include?(key)
|
|
168
|
+
|
|
169
|
+
:"#{prefix}#{key}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# @param key [Symbol] user space key
|
|
173
|
+
# @return [Symbol] transport space key
|
|
174
|
+
def de_prefix_key(key)
|
|
175
|
+
key = key.to_sym
|
|
176
|
+
return key unless prefix
|
|
177
|
+
return key if RESERVED_OPTIONS.include?(key)
|
|
178
|
+
|
|
179
|
+
key.start_with?(prefix) ? key.to_s[prefix.length..].to_sym : key
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def build_filter(filter)
|
|
183
|
+
case filter
|
|
184
|
+
when nil
|
|
185
|
+
filter
|
|
186
|
+
when Symbol, String
|
|
187
|
+
[prefix_key(filter)]
|
|
188
|
+
when Array
|
|
189
|
+
filter.map { |k| prefix_key(k) }
|
|
190
|
+
else
|
|
191
|
+
raise ArgumentError, "filter(#{filter.class}): must be Array<Symbol> or Proc" unless filter.respond_to?(:call)
|
|
192
|
+
|
|
193
|
+
filter
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# user space perpective!!
|
|
198
|
+
def build_ignore(ignore)
|
|
199
|
+
case ignore
|
|
200
|
+
when String
|
|
201
|
+
->(k, _v) { k.start_with?(ignore) }
|
|
202
|
+
when Array
|
|
203
|
+
->(k, _v) { ignore.any? { k.to_s.start_with?(it) } }
|
|
204
|
+
when nil
|
|
205
|
+
nil
|
|
206
|
+
else
|
|
207
|
+
raise ArgumentError, "ignore:(#{ignore.class}) must be Array, Proc, or nil" unless ignore.respond_to?(:call)
|
|
208
|
+
|
|
209
|
+
ignore
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def filter_list(opts)
|
|
214
|
+
invalid_opts = opts.except(*filter, *RESERVED_OPTIONS)
|
|
215
|
+
raise ArgumentError, "Unsupported options: #{invalid_opts.keys}" unless invalid_opts.empty?
|
|
216
|
+
|
|
217
|
+
opts
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
data/lib/json_rpc_kit.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'json_rpc_kit/helpers'
|
|
4
|
+
require_relative 'json_rpc_kit/version'
|
|
5
|
+
require_relative 'json_rpc_kit/errors'
|
|
6
|
+
require_relative 'json_rpc_kit/service'
|
|
7
|
+
require_relative 'json_rpc_kit/endpoint'
|
|
8
|
+
|
|
9
|
+
# A toolkit for nd making JSON-RPC requests (see {Endpoint} and building JSON-RPC services (see {Service}).
|
|
10
|
+
module JsonRpcKit
|
|
11
|
+
CONTENT_TYPE = 'application/json'
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Create an endpoint that can invoke JSON-RPC methods as natural ruby method calls.
|
|
15
|
+
# Shortcut for {Endpoint.initialize Endpoint.new}
|
|
16
|
+
# @return [Endpoint]
|
|
17
|
+
def endpoint(...)
|
|
18
|
+
Endpoint.new(...)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Create a service transport handler for processing JSON-RPC requests.
|
|
22
|
+
# Shortcut for {Service.transport}
|
|
23
|
+
# @return [Proc] Handler proc for processing requests
|
|
24
|
+
def service_transport(...)
|
|
25
|
+
Service.transport(...)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: json_rpc_kit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.9.0.rc1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Grant Gardner
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: json
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: A Ruby toolkit for JSON-RPC 2.0 that provides both client, server and
|
|
27
|
+
transport infrastructure components
|
|
28
|
+
email:
|
|
29
|
+
- grant@lastweekend.com.au
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- lib/json_rpc_kit.rb
|
|
35
|
+
- lib/json_rpc_kit/endpoint.rb
|
|
36
|
+
- lib/json_rpc_kit/errors.rb
|
|
37
|
+
- lib/json_rpc_kit/helpers.rb
|
|
38
|
+
- lib/json_rpc_kit/service.rb
|
|
39
|
+
- lib/json_rpc_kit/transport_options.rb
|
|
40
|
+
- lib/json_rpc_kit/version.rb
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata:
|
|
44
|
+
rubygems_mfa_required: 'true'
|
|
45
|
+
rdoc_options: []
|
|
46
|
+
require_paths:
|
|
47
|
+
- lib
|
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '3.4'
|
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
requirements: []
|
|
59
|
+
rubygems_version: 3.6.9
|
|
60
|
+
specification_version: 4
|
|
61
|
+
summary: JSON-RPC 2.0 client and server toolkit
|
|
62
|
+
test_files: []
|