gcpc-interceptors 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d912277cfed4f48ca432e81df7009dab03fe82ec588ef16caa335bbf152e55aa
4
+ data.tar.gz: cc61dfbae21ee3aba534c9da50f933993520040152483fd8c4b954e056bfb3b3
5
+ SHA512:
6
+ metadata.gz: 1d1603e37ddd88dfff50ae5274a32212261c69ea69567efc50e1281c5ebce6ce8832b6caede36d9455334a190206993176b6244b342435ade05a41532f041768
7
+ data.tar.gz: 77c336c0bd1ca8a1c0485250f0bad572fd33db2194a7bda0d3dd33e271949a2a84e1e9163e11aed945dd86bd202b11f903b47be4fcc81e794050b679a2fe3252
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.2
7
+ before_install: gem install bundler -v 1.17.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in gcpc-interceptors.gemspec
6
+ gemspec
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gcpc-interceptors (0.0.1)
5
+ gcpc
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ addressable (2.6.0)
11
+ public_suffix (>= 2.0.2, < 4.0)
12
+ coderay (1.1.2)
13
+ concurrent-ruby (1.1.5)
14
+ diff-lcs (1.3)
15
+ faraday (0.15.4)
16
+ multipart-post (>= 1.2, < 3)
17
+ gcpc (0.0.3)
18
+ google-cloud-pubsub (~> 0.34)
19
+ google-cloud-core (1.3.0)
20
+ google-cloud-env (~> 1.0)
21
+ google-cloud-env (1.0.5)
22
+ faraday (~> 0.11)
23
+ google-cloud-pubsub (0.36.0)
24
+ concurrent-ruby (~> 1.0)
25
+ google-cloud-core (~> 1.2)
26
+ google-gax (~> 1.3)
27
+ googleapis-common-protos (>= 1.3.9, < 2.0)
28
+ grpc-google-iam-v1 (~> 0.6.9)
29
+ google-gax (1.5.0)
30
+ google-protobuf (~> 3.2)
31
+ googleapis-common-protos (>= 1.3.5, < 2.0)
32
+ googleauth (>= 0.6.2, < 0.10.0)
33
+ grpc (>= 1.7.2, < 2.0)
34
+ rly (~> 0.2.3)
35
+ google-protobuf (3.7.1)
36
+ googleapis-common-protos (1.3.9)
37
+ google-protobuf (~> 3.0)
38
+ googleapis-common-protos-types (~> 1.0)
39
+ grpc (~> 1.0)
40
+ googleapis-common-protos-types (1.0.4)
41
+ google-protobuf (~> 3.0)
42
+ googleauth (0.8.1)
43
+ faraday (~> 0.12)
44
+ jwt (>= 1.4, < 3.0)
45
+ memoist (~> 0.16)
46
+ multi_json (~> 1.11)
47
+ os (>= 0.9, < 2.0)
48
+ signet (~> 0.7)
49
+ grpc (1.20.0)
50
+ google-protobuf (~> 3.7)
51
+ googleapis-common-protos-types (~> 1.0.0)
52
+ grpc-google-iam-v1 (0.6.9)
53
+ googleapis-common-protos (>= 1.3.1, < 2.0)
54
+ grpc (~> 1.0)
55
+ jwt (2.1.0)
56
+ memoist (0.16.0)
57
+ method_source (0.9.2)
58
+ mock_redis (0.20.0)
59
+ multi_json (1.13.1)
60
+ multipart-post (2.1.1)
61
+ os (1.0.1)
62
+ pry (0.12.2)
63
+ coderay (~> 1.1.0)
64
+ method_source (~> 0.9.0)
65
+ public_suffix (3.1.0)
66
+ rake (10.5.0)
67
+ rly (0.2.3)
68
+ rspec (3.8.0)
69
+ rspec-core (~> 3.8.0)
70
+ rspec-expectations (~> 3.8.0)
71
+ rspec-mocks (~> 3.8.0)
72
+ rspec-core (3.8.0)
73
+ rspec-support (~> 3.8.0)
74
+ rspec-expectations (3.8.3)
75
+ diff-lcs (>= 1.2.0, < 2.0)
76
+ rspec-support (~> 3.8.0)
77
+ rspec-mocks (3.8.0)
78
+ diff-lcs (>= 1.2.0, < 2.0)
79
+ rspec-support (~> 3.8.0)
80
+ rspec-support (3.8.0)
81
+ signet (0.11.0)
82
+ addressable (~> 2.3)
83
+ faraday (~> 0.9)
84
+ jwt (>= 1.5, < 3.0)
85
+ multi_json (~> 1.10)
86
+ timecop (0.9.1)
87
+
88
+ PLATFORMS
89
+ ruby
90
+
91
+ DEPENDENCIES
92
+ bundler (~> 1.17)
93
+ gcpc-interceptors!
94
+ mock_redis (~> 0.20)
95
+ pry
96
+ rake (~> 10.0)
97
+ rspec (~> 3.2)
98
+ timecop
99
+
100
+ BUNDLED WITH
101
+ 1.17.2
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Nao Minami
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,108 @@
1
+ # Gcpc::Interceptors
2
+
3
+ Gcpc::Interceptors is a collection of interceptors for [gcpc](https://github.com/south37/gcpc).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'gcpc-interceptors'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install gcpc-interceptors
20
+
21
+ ## Usage
22
+
23
+ ### Interceptors for Publisher
24
+
25
+ ```ruby
26
+ require "gcpc"
27
+ require "gcpc-interceptors"
28
+
29
+ publisher = Gcpc::Publisher.new(
30
+ project_id: "project-example-1",
31
+ topic: "topic-example-1",
32
+ interceptors: [
33
+ Gcpc::Publisher::Interceptors::EncodeInterceptor.new(
34
+ content_type: :json
35
+ ),
36
+ Gcpc::Interceptors::Publisher::IdInterceptor.new,
37
+ Gcpc::Interceptors::Publisher::PublisherNameInterceptor.new(
38
+ publisher: "publisher-A"
39
+ ),
40
+ Gcpc::Interceptors::Publisher::TimestampInterceptor.new,
41
+ ]
42
+ emulator_host: "localhost:8085",
43
+ )
44
+
45
+ jsondata = { key: :value }
46
+ attributes = {}
47
+ publisher.publish(jsondata, attributes) # published as `{"key":"value"}`, {"published_by":"publisher-A","published_at":"2019-03-01T00:00:00+00:00"}
48
+ ```
49
+
50
+ ### Interceptors for Subscriber
51
+
52
+ ```ruby
53
+ require "gcpc"
54
+ require "gcpc-interceptors"
55
+
56
+ class LogHandler < Gcpc::Subscriber::BaseHandler
57
+ LOGGER = Logger.new(STDOUT)
58
+
59
+ def handle(data, attributes, message)
60
+ LOGGER.info "#{message.inspect}"
61
+ LOGGER.info "data: #{data}"
62
+ LOGGER.info "attributes: #{attributes}"
63
+ end
64
+ end
65
+
66
+ redis_store = Gcpc::Interceptors::Utils::RedisStore.new(redis: Redis.new(ENV["REDIS_URL"]))
67
+
68
+ class RaiseExceptionStrategy < Gcpc::Interceptors::Subscriber::CheckOrderInterceptor::BaseStrategy
69
+ def on_swapped(data, attributes, message, &block)
70
+ raise "Swapped!"
71
+ end
72
+ end
73
+
74
+ subscriber = Gcpc::Subscriber.new(
75
+ project_id: "project-example-1",
76
+ subscription: "topic-example-1",
77
+ interceptors: [
78
+ Gcpc::Interceptors::Subscriber::DecodeInterceptor.new(
79
+ strategy: Gcpc::Interceptors::Subscriber::DecodeInterceptor::JSONStrategy.new,
80
+ ),
81
+ Gcpc::Interceptors::Subscriber::CheckDupInterceptor.new(
82
+ store: redis_store,
83
+ ),
84
+ Gcpc::Interceptors::Subscriber::CheckOrderInterceptor.new(
85
+ store: redis_store,
86
+ strategy: RaiseExceptionStrategy.new,
87
+ ),
88
+ ],
89
+ emulator_host: "localhost:8085",
90
+ )
91
+ subscriber.handle(LogHandler)
92
+
93
+ subscriber.run
94
+ ```
95
+
96
+ ## Development
97
+
98
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
99
+
100
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
101
+
102
+ ## Contributing
103
+
104
+ Bug reports and pull requests are welcome on GitHub at https://github.com/south37/gcpc-interceptors.
105
+
106
+ ## License
107
+
108
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "gcpc/interceptors"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ require "pry"
10
+ Pry.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,33 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "gcpc/interceptors/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "gcpc-interceptors"
8
+ spec.version = Gcpc::Interceptors::VERSION
9
+ spec.authors = ["Nao Minami"]
10
+ spec.email = ["south37777@gmail.com"]
11
+
12
+ spec.summary = %q{The collection of interceptors for gcpc (Google Cloud Pub/Sub Client for Ruby)}
13
+ spec.description = %q{The collection of interceptors for gcpc (Google Cloud Pub/Sub Client for Ruby)}
14
+ spec.homepage = "https://github.com/south37/gcpc-interceptors"
15
+ spec.license = "MIT"
16
+
17
+ # Specify which files should be added to the gem when it is released.
18
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.17"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+ spec.add_development_dependency "rspec", "~> 3.2"
29
+ spec.add_development_dependency "pry"
30
+ spec.add_development_dependency "timecop"
31
+ spec.add_development_dependency "mock_redis", "~> 0.20"
32
+ spec.add_runtime_dependency "gcpc"
33
+ end
@@ -0,0 +1,10 @@
1
+ require "gcpc"
2
+ require "gcpc/interceptors/publisher"
3
+ require "gcpc/interceptors/subscriber"
4
+ require "gcpc/interceptors/utils"
5
+ require "gcpc/interceptors/version"
6
+
7
+ module Gcpc
8
+ module Interceptors
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require "gcpc/interceptors/publisher/encode_interceptor"
2
+ require "gcpc/interceptors/publisher/id_interceptor"
3
+ require "gcpc/interceptors/publisher/publisher_name_interceptor"
4
+ require "gcpc/interceptors/publisher/timestamp_interceptor"
5
+
6
+ module Gcpc
7
+ module Interceptors
8
+ module Publisher
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ module Gcpc
2
+ module Interceptors
3
+ module Publisher
4
+ # `EncodeInterceptor` encodes a data and publish it as a String.
5
+ class EncodeInterceptor < Gcpc::Publisher::BaseInterceptor
6
+ VALID_CONTENT_TYPES = [
7
+ "json",
8
+ "protobuf",
9
+ ]
10
+
11
+ def initialize(content_type:)
12
+ @content_type = content_type.to_s
13
+ validate!
14
+ end
15
+
16
+ # @param [#to_json, #to_proto] data
17
+ # @param [Hash] attributes
18
+ def publish(data, attributes)
19
+ d = encode(data)
20
+ a = attributes.merge("content_type" => @content_type)
21
+ yield d, a
22
+ end
23
+
24
+ private
25
+
26
+ def validate!
27
+ if !VALID_CONTENT_TYPES.include?(@content_type)
28
+ raise "invalid content_type: #{@content_type}"
29
+ end
30
+ end
31
+
32
+ # @param [#to_json, #to_proto] data
33
+ # @return [String]
34
+ def encode(data)
35
+ case @content_type
36
+ when "json"
37
+ data.to_json
38
+ when "protobuf"
39
+ data.to_proto
40
+ else
41
+ raise "invalid content_type: \"#{@content_type}\""
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ require "securerandom"
2
+
3
+ module Gcpc
4
+ module Interceptors
5
+ module Publisher
6
+ # `IdInterceptor` adds an unique id to a message's attributes
7
+ class IdInterceptor < Gcpc::Publisher::BaseInterceptor
8
+ DEFAULT_ID_KEY = "message_id"
9
+
10
+ def initialize(id_key: DEFAULT_ID_KEY)
11
+ @id_key = id_key
12
+ end
13
+
14
+ # @param [#to_json, #to_proto] data
15
+ # @param [Hash] attributes
16
+ def publish(data, attributes)
17
+ a = attributes.merge(@id_key => id)
18
+ yield data, a
19
+ end
20
+
21
+ private
22
+
23
+ def id
24
+ SecureRandom.uuid
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ module Gcpc
2
+ module Interceptors
3
+ module Publisher
4
+ # `PublisherNameInterceptor` adds a publisher name to a message's
5
+ # attributes
6
+ class PublisherNameInterceptor < Gcpc::Publisher::BaseInterceptor
7
+ # @param [String] publisher
8
+ def initialize(publisher:)
9
+ @publisher = publisher
10
+ end
11
+
12
+ # @param [String] data
13
+ # @param [Hash] attributes
14
+ def publish(data, attributes)
15
+ a = attributes.merge("published_by" => @publisher)
16
+ yield data, a
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ require "date"
2
+
3
+ module Gcpc
4
+ module Interceptors
5
+ module Publisher
6
+ # `TimestampInterceptor` adds a timestamp to a message's attributes in
7
+ # RFC3339 format.
8
+ class TimestampInterceptor < Gcpc::Publisher::BaseInterceptor
9
+ DEFAULT_TIMESTAMP_KEY = "published_at"
10
+
11
+ def initialize(timestamp_key: DEFAULT_TIMESTAMP_KEY)
12
+ @timestamp_key = timestamp_key
13
+ end
14
+
15
+ # @param [String] data
16
+ # @param [Hash] attributes
17
+ def publish(data, attributes)
18
+ a = attributes.merge(@timestamp_key => DateTime.now.rfc3339)
19
+ yield data, a
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,10 @@
1
+ require "gcpc/interceptors/subscriber/check_dup_interceptor"
2
+ require "gcpc/interceptors/subscriber/check_order_interceptor"
3
+ require "gcpc/interceptors/subscriber/decode_interceptor"
4
+
5
+ module Gcpc
6
+ module Interceptors
7
+ module Subscriber
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module Gcpc
2
+ module Interceptors
3
+ module Subscriber
4
+ # `CheckOrderInterceptor` checks the duplication of messages.
5
+ class CheckDupInterceptor < Gcpc::Subscriber::BaseInterceptor
6
+ class BaseStrategy
7
+ DEFAULT_ID_KEY = "message_id"
8
+ DEFAULT_TTL = 7 * 24 * 3600 # 7 days
9
+
10
+ def initialize(ttl: DEFAULT_TTL, id_key: DEFAULT_ID_KEY)
11
+ @ttl = ttl
12
+ @id_key = id_key
13
+ end
14
+
15
+ attr_reader :ttl
16
+
17
+ def id(data, attributes, message)
18
+ attributes[@id_key]
19
+ end
20
+
21
+ def on_dup(data, attributes, message, &block)
22
+ # Do nothing. Just ignore the duplicated message.
23
+ end
24
+ end
25
+
26
+ # @param [#exists, #set] store
27
+ # @param [BaseStrategy] strategy
28
+ def initialize(store:, strategy: BaseStrategy.new)
29
+ @store = store
30
+ @strategy = strategy
31
+ end
32
+
33
+ # @param [String] data
34
+ # @param [Hash] attributes
35
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
36
+ # @param [Proc] block
37
+ def handle(data, attributes, message, &block)
38
+ id = @strategy.id(data, attributes, message)
39
+
40
+ if !id.nil? && duplicated?(id)
41
+ @strategy.on_dup(data, attributes, message, &block)
42
+ return
43
+ end
44
+
45
+ yield data, attributes, message
46
+ end
47
+
48
+ private
49
+
50
+ # @param [String] id
51
+ # @param [bool] true if id is already stored on `store`.
52
+ def duplicated?(id)
53
+ k = key(id)
54
+ r = @store.exists(k)
55
+ @store.set(k, "exists", ttl: @strategy.ttl) if !r
56
+ r
57
+ end
58
+
59
+ def key(id)
60
+ "CheckDupInterceptor:key:#{id}"
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,134 @@
1
+ require "date"
2
+
3
+ module Gcpc
4
+ module Interceptors
5
+ module Subscriber
6
+ # `CheckOrderInterceptor` checks the order of messages in each group.
7
+ class CheckOrderInterceptor < Gcpc::Subscriber::BaseInterceptor
8
+ class BaseStrategy
9
+ DEFAULT_TIMESTAMP_KEY = "published_at"
10
+ DEFAULT_TTL = 7 * 24 * 3600 # 7 days
11
+
12
+ def initialize(ttl: DEFAULT_TTL, timestamp_key: DEFAULT_TIMESTAMP_KEY)
13
+ @ttl = ttl
14
+ @timestamp_key = DEFAULT_TIMESTAMP_KEY
15
+ end
16
+
17
+ attr_reader :ttl
18
+
19
+ # @param [String] data
20
+ # @param [Hash] attributes
21
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
22
+ # @return [String, nil]
23
+ def timestamp(data, attributes, message)
24
+ attributes[@timestamp_key]
25
+ end
26
+
27
+ # @param [String] str
28
+ # @return [DateTime, nil]
29
+ def decode_timestamp(str)
30
+ DateTime.parse(str)
31
+ rescue ArgumentError, TypeError
32
+ nil
33
+ end
34
+
35
+ # @param [String] data
36
+ # @param [Hash] attributes
37
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
38
+ # @return [String]
39
+ def group_id(data, attributes, message)
40
+ # The default behavior is to always return the same key. This means
41
+ # that all messages should be in order. If you want to change the
42
+ # set of messages you want to order, please override this method.
43
+ "same-id"
44
+ end
45
+
46
+ # @param [String] data
47
+ # @param [Hash] attributes
48
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
49
+ # @param [Proc] block
50
+ def on_swapped(data, attributes, message, &block)
51
+ # By default, simply yield
52
+ yield data, attributes, message
53
+ end
54
+ end
55
+
56
+ # @param [#get, #set] store
57
+ # @param [Logger] logger
58
+ # @param [BaseStrategy] strategy
59
+ def initialize(store:, logger: Logger.new(STDOUT), strategy: BaseStrategy.new)
60
+ @store = store
61
+ @logger = logger
62
+ @strategy = strategy
63
+ end
64
+
65
+ # @param [String] data
66
+ # @param [Hash] attributes
67
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
68
+ # @param [Proc] block
69
+ def handle(data, attributes, message, &block)
70
+ timestamp = @strategy.timestamp(data, attributes, message)
71
+
72
+ if timestamp.nil?
73
+ yield data, attributes, message
74
+ return
75
+ end
76
+
77
+ group_id = @strategy.group_id(data, attributes, message)
78
+ if swapped?(group_id, timestamp)
79
+ @strategy.on_swapped(data, attributes, message, &block)
80
+ return
81
+ end
82
+
83
+ yield data, attributes, message
84
+ end
85
+
86
+ private
87
+
88
+ # @param [String] group_id
89
+ # @param [String] timestamp represents the published timestamp in
90
+ # RFC3339 format.
91
+ def swapped?(group_id, message_timestamp)
92
+ message_t = @strategy.decode_timestamp(message_timestamp)
93
+ return false if message_t.nil? # Decode error occured
94
+
95
+ k = key(group_id)
96
+
97
+ stored_timestamp = @store.get(k)
98
+ if stored_timestamp.nil? # stored_timestamp does not exist in store
99
+ set(k, message_timestamp)
100
+ return false
101
+ end
102
+
103
+ stored_t = @strategy.decode_timestamp(stored_timestamp)
104
+ if stored_t.nil? # Decode error occured
105
+ @logger.error("Failed to decode a stored timestamp! The stored value is \"#{stored_timestamp}\"!")
106
+ set(k, message_timestamp)
107
+
108
+ # Since it is not possible to confirm the occurrence of swap,
109
+ # we treat decode errors as swap.
110
+ return true
111
+ end
112
+
113
+ # The message's timestamp should be larger than stored timestamp
114
+ # (last message's timestamp). If not, the order of messages are
115
+ # swapped.
116
+ r = message_t < stored_t
117
+ if !r
118
+ # Store the closest timestamp
119
+ set(k, message_timestamp)
120
+ end
121
+ r
122
+ end
123
+
124
+ def key(group_id)
125
+ "CheckOrderInterceptor:key:#{group_id}"
126
+ end
127
+
128
+ def set(key, timestamp)
129
+ @store.set(key, timestamp, ttl: @strategy.ttl)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,37 @@
1
+ require "json"
2
+
3
+ module Gcpc
4
+ module Interceptors
5
+ module Subscriber
6
+ # `DecodeInterceptor` decodes the message according to the strategy and
7
+ # sets it in the attributes.
8
+ class DecodeInterceptor < Gcpc::Subscriber::BaseInterceptor
9
+ class BaseStrategy
10
+ def decode(data, attributes, message)
11
+ raise NotImplementedError.new("You must implement #{self.class}##{__method__}")
12
+ end
13
+ end
14
+
15
+ class JSONStrategy < BaseStrategy
16
+ def decode(data, attributes, message)
17
+ JSON.parse(data)
18
+ end
19
+ end
20
+
21
+ # @param [BaseStrategy] strategy
22
+ def initialize(strategy:)
23
+ @strategy = strategy
24
+ end
25
+
26
+ # @param [String] data
27
+ # @param [Hash] attributes
28
+ # @param [Google::Cloud::Pubsub::ReceivedMessage] message
29
+ # @param [Proc] block
30
+ def handle(data, attributes, message, &block)
31
+ m = @strategy.decode(data, attributes, message)
32
+ yield m, attributes, message
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,8 @@
1
+ require "gcpc/interceptors/utils/redis_store"
2
+
3
+ module Gcpc
4
+ module Interceptors
5
+ module Utils
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,39 @@
1
+ module Gcpc
2
+ module Interceptors
3
+ module Utils
4
+ class RedisStore
5
+ def initialize(redis:)
6
+ @redis = redis
7
+ end
8
+
9
+ # @param [String] key
10
+ # @return [String]
11
+ # @raise Redis::BaseConnectionError
12
+ def get(key)
13
+ @redis.get(key)
14
+ end
15
+
16
+ # @param [String] key
17
+ # @param [String] val
18
+ # @param [Integer, nil] ttl
19
+ # @return [bool]
20
+ # @raise Redis::BaseConnectionError
21
+ def set(key, val, ttl: nil)
22
+ if ttl
23
+ @redis.setex(key, ttl, val)
24
+ else
25
+ @redis.set(key, val)
26
+ end
27
+ true
28
+ end
29
+
30
+ # @param [String] key
31
+ # @return [bool]
32
+ # @raise Redis::BaseConnectionError
33
+ def exists(key)
34
+ @redis.exists(key)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,5 @@
1
+ module Gcpc
2
+ module Interceptors
3
+ VERSION = "0.0.1"
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gcpc-interceptors
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Nao Minami
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-06-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: timecop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: mock_redis
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.20'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.20'
97
+ - !ruby/object:Gem::Dependency
98
+ name: gcpc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: The collection of interceptors for gcpc (Google Cloud Pub/Sub Client
112
+ for Ruby)
113
+ email:
114
+ - south37777@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - ".gitignore"
120
+ - ".rspec"
121
+ - ".travis.yml"
122
+ - Gemfile
123
+ - Gemfile.lock
124
+ - LICENSE.txt
125
+ - README.md
126
+ - Rakefile
127
+ - bin/console
128
+ - bin/setup
129
+ - gcpc-interceptors.gemspec
130
+ - lib/gcpc/interceptors.rb
131
+ - lib/gcpc/interceptors/publisher.rb
132
+ - lib/gcpc/interceptors/publisher/encode_interceptor.rb
133
+ - lib/gcpc/interceptors/publisher/id_interceptor.rb
134
+ - lib/gcpc/interceptors/publisher/publisher_name_interceptor.rb
135
+ - lib/gcpc/interceptors/publisher/timestamp_interceptor.rb
136
+ - lib/gcpc/interceptors/subscriber.rb
137
+ - lib/gcpc/interceptors/subscriber/check_dup_interceptor.rb
138
+ - lib/gcpc/interceptors/subscriber/check_order_interceptor.rb
139
+ - lib/gcpc/interceptors/subscriber/decode_interceptor.rb
140
+ - lib/gcpc/interceptors/utils.rb
141
+ - lib/gcpc/interceptors/utils/redis_store.rb
142
+ - lib/gcpc/interceptors/version.rb
143
+ homepage: https://github.com/south37/gcpc-interceptors
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 3.0.3
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: The collection of interceptors for gcpc (Google Cloud Pub/Sub Client for
166
+ Ruby)
167
+ test_files: []