bps 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bps.gemspec +19 -0
- data/lib/bps.rb +52 -0
- data/lib/bps/coercer.rb +64 -0
- data/lib/bps/publisher/abstract.rb +24 -0
- data/lib/bps/subscriber/abstract.rb +14 -0
- data/spec/bps/coercer_spec.rb +42 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5de5e6e993f0b96c93224163f85fc0cd3ef5a9ecf7d03487df35315ee3bd153b
|
4
|
+
data.tar.gz: cfaa3b821549d70bee5094321553123493d9e66fca4cf15fa50ccdb0f4c5a04d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c4d068aa91b285af2a124a258a13207298c3c0f9202fe39f197e21a9fd49a53f433de12cf4817b78b265b2920deeaa402a014cba0fe74da3b258570688b8fb1e
|
7
|
+
data.tar.gz: 4b7f0fa9bf89e079e05312e4d202af107b102c51079166704ca4641146942a0b3a692a0605d52bf0e46bbb8d8847b0969a8c8c20e763308d160988cff1c7d9c5
|
data/bps.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'bps'
|
3
|
+
s.version = File.read(File.expand_path('../../.version', __dir__)).strip
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
|
6
|
+
s.licenses = ['Apache-2.0']
|
7
|
+
s.summary = 'Multi-platform pubsub adapter'
|
8
|
+
s.description = 'Minimalist abstraction for publish-subscribe'
|
9
|
+
|
10
|
+
s.authors = ['Black Square Media']
|
11
|
+
s.email = 'info@blacksquaremedia.com'
|
12
|
+
s.homepage = 'https://github.com/bsm/bps'
|
13
|
+
|
14
|
+
s.executables = []
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
17
|
+
s.require_paths = ['lib']
|
18
|
+
s.required_ruby_version = '>= 2.6.0'
|
19
|
+
end
|
data/lib/bps.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module BPS
|
5
|
+
autoload :Coercer, 'bps/coercer'
|
6
|
+
|
7
|
+
module Publisher
|
8
|
+
autoload :Abstract, 'bps/publisher/abstract'
|
9
|
+
|
10
|
+
def self.register(*schemes, &resolver)
|
11
|
+
@registry ||= {}
|
12
|
+
schemes.each do |scheme|
|
13
|
+
@registry[scheme] = resolver
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.resolve(url)
|
18
|
+
url = url.is_a?(::URI) ? url.dup : URI.parse(url)
|
19
|
+
rsl = @registry[url.scheme]
|
20
|
+
raise ArgumentError, "Unable to resolve publisher #{url}, scheme #{url.scheme} is not registered" unless rsl
|
21
|
+
|
22
|
+
opts = {}
|
23
|
+
CGI.parse(url.query.to_s).each do |key, values|
|
24
|
+
opts[key.to_sym] = values.first
|
25
|
+
end
|
26
|
+
rsl.call(url, opts)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module Subscriber
|
31
|
+
autoload :Abstract, 'bps/subscriber/abstract'
|
32
|
+
|
33
|
+
def self.register(*schemes, &resolver)
|
34
|
+
@registry ||= {}
|
35
|
+
schemes.each do |scheme|
|
36
|
+
@registry[scheme] = resolver
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.resolve(url)
|
41
|
+
url = url.is_a?(::URI) ? url.dup : URI.parse(url)
|
42
|
+
rsl = @registry[url.scheme]
|
43
|
+
raise ArgumentError, "Unable to resolve subscriber #{url}, scheme #{url.scheme} is not registered" unless rsl
|
44
|
+
|
45
|
+
opts = {}
|
46
|
+
CGI.parse(url.query.to_s).each do |key, values|
|
47
|
+
opts[key.to_sym] = values.first
|
48
|
+
end
|
49
|
+
rsl.call(url, opts)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/bps/coercer.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module BPS
|
4
|
+
class Coercer
|
5
|
+
TRUE_VALUES = [true, 'TRUE', 'true', 'T', 't', 1, '1'].to_set
|
6
|
+
|
7
|
+
attr_reader :schema
|
8
|
+
|
9
|
+
def initialize(schema)
|
10
|
+
validate!(schema)
|
11
|
+
@schema = schema
|
12
|
+
end
|
13
|
+
|
14
|
+
def coerce(hash)
|
15
|
+
coerce_with(@schema, hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate!(schema)
|
21
|
+
schema.each do |key, type|
|
22
|
+
case type
|
23
|
+
when :string, :symbol, :int, :float, :bool
|
24
|
+
# OK
|
25
|
+
when Hash
|
26
|
+
validate!(type)
|
27
|
+
when Array
|
28
|
+
raise ArgumentError, "Array types must have exactly one entry, but was (#{key} => #{type.inspect})" unless type.size == 1
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Unknown type #{type.inspect}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def coerce_with(schema, hash)
|
36
|
+
clone = {}
|
37
|
+
schema.each do |key, type|
|
38
|
+
next unless hash.key?(key)
|
39
|
+
|
40
|
+
clone[key] = coerce_value(type, hash[key])
|
41
|
+
end
|
42
|
+
clone
|
43
|
+
end
|
44
|
+
|
45
|
+
def coerce_value(type, val)
|
46
|
+
case type
|
47
|
+
when :string
|
48
|
+
val&.to_s
|
49
|
+
when :symbol
|
50
|
+
val&.to_sym
|
51
|
+
when :int
|
52
|
+
val&.to_i
|
53
|
+
when :float
|
54
|
+
val&.to_f
|
55
|
+
when :bool
|
56
|
+
val.nil? ? nil : TRUE_VALUES.include?(val)
|
57
|
+
when Hash
|
58
|
+
val.is_a?(Hash) ? coerce_with(type, val) : nil
|
59
|
+
when Array
|
60
|
+
Array(val).map {|v| coerce_value(type[0], v) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module BPS
|
2
|
+
module Publisher
|
3
|
+
class Abstract
|
4
|
+
class Topic
|
5
|
+
# Publish a message.
|
6
|
+
def publish(_message, **_opts)
|
7
|
+
raise 'not implemented'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Flush any remaining buffer.
|
11
|
+
def flush(**); end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Retrieve a topic handle.
|
15
|
+
# @params [String] name the topic name.
|
16
|
+
def topic(_name)
|
17
|
+
raise 'not implemented'
|
18
|
+
end
|
19
|
+
|
20
|
+
# Close the publisher.
|
21
|
+
def close; end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe BPS::Coercer do
|
4
|
+
subject do
|
5
|
+
described_class.new(
|
6
|
+
name: :string,
|
7
|
+
codec: :symbol,
|
8
|
+
retries: :int,
|
9
|
+
backoff: :float,
|
10
|
+
idempotent: :bool,
|
11
|
+
tags: [:string],
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
let :options do
|
16
|
+
{
|
17
|
+
name: 123,
|
18
|
+
codec: 'snappy',
|
19
|
+
retries: '4',
|
20
|
+
backoff: '10',
|
21
|
+
idempotent: '1',
|
22
|
+
tags: [:foo, 33],
|
23
|
+
extra: 'foo',
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should validate' do
|
28
|
+
expect { described_class.new(name: :unknown) }.to raise_error(ArgumentError, /Unknown type :unknown/)
|
29
|
+
expect { described_class.new(bad: []) }.to raise_error(ArgumentError, /Array types must have exactly one entry/)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should coerce options' do
|
33
|
+
expect(subject.coerce(options)).to eq(
|
34
|
+
name: '123',
|
35
|
+
codec: :snappy,
|
36
|
+
retries: 4,
|
37
|
+
backoff: 10.0,
|
38
|
+
idempotent: true,
|
39
|
+
tags: %w[foo 33],
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bps
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Black Square Media
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-07-01 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Minimalist abstraction for publish-subscribe
|
14
|
+
email: info@blacksquaremedia.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- bps.gemspec
|
20
|
+
- lib/bps.rb
|
21
|
+
- lib/bps/coercer.rb
|
22
|
+
- lib/bps/publisher/abstract.rb
|
23
|
+
- lib/bps/subscriber/abstract.rb
|
24
|
+
- spec/bps/coercer_spec.rb
|
25
|
+
homepage: https://github.com/bsm/bps
|
26
|
+
licenses:
|
27
|
+
- Apache-2.0
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ">="
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.6.0
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubygems_version: 3.1.4
|
45
|
+
signing_key:
|
46
|
+
specification_version: 4
|
47
|
+
summary: Multi-platform pubsub adapter
|
48
|
+
test_files:
|
49
|
+
- spec/bps/coercer_spec.rb
|