cent 3.0.0 → 5.0.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.
data/lib/cent/wire.rb ADDED
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cent
4
+ # Encoding requests and decoding replies, for the generated code: each field
5
+ # is put or got by its kind - :str, :bool, :int, :float, :json (a payload),
6
+ # :bool_value and :int32_value (protobuf wrappers), or a message's class - and
7
+ # its shape - :one, :list, :map, or :presence for a message which is a
8
+ # presence wrapper, sent whenever it is set, even empty.
9
+ #
10
+ # @api private
11
+ module Wire
12
+ ZERO = { str: "", bool: false, int: 0, float: 0.0 }.freeze
13
+
14
+ module_function
15
+
16
+ # Sets +w[key]+ to the encoded field +v+, unless it is unset or at its zero
17
+ # value: nil, "", false, 0, an empty list or map, a message with nothing set
18
+ # - but a presence wrapper, sent as {} when set empty. +where+ prefixes the
19
+ # field's name in error messages.
20
+ def put(w, key, v, where, kind, shape = :one)
21
+ return if v.nil?
22
+
23
+ out =
24
+ case shape
25
+ when :list
26
+ raise InvalidRequestError, "#{where}#{key} must be an Array, not #{kind_of(v)}" unless v.is_a?(Array)
27
+ return if v.empty?
28
+
29
+ v.each_with_index.map { |item, i| value(kind, item, "#{where}#{key}[#{i}]", true) }
30
+ when :map
31
+ raise InvalidRequestError, "#{where}#{key} must be a Hash, not #{kind_of(v)}" unless v.is_a?(Hash)
32
+ return if v.empty?
33
+
34
+ v.each_with_object({}) do |(k, item), h|
35
+ name = map_key(k, "#{where}#{key}")
36
+ h[name] = value(kind, item, "#{where}#{key}[#{name.inspect}]", true)
37
+ end
38
+ else value(kind, v, "#{where}#{key}", shape == :presence)
39
+ end
40
+ w[key] = out unless out.nil?
41
+ end
42
+
43
+ # One value of a field, encoded; nil for a zero value, unless +keep+ (an
44
+ # item of a list or map, a presence wrapper).
45
+ def value(kind, v, path, keep)
46
+ case kind
47
+ when :str, :bool, :int, :float then scalar(kind, v, path, keep)
48
+ when :json then Payload.check!(v, path)
49
+ when :bool_value
50
+ raise InvalidRequestError, "#{path} must be true or false, not #{kind_of(v)}" unless [true, false].include?(v)
51
+
52
+ { "value" => v }
53
+ when :int32_value
54
+ raise InvalidRequestError, "#{path} must be an Integer, not #{kind_of(v)}" unless v.is_a?(Integer)
55
+
56
+ { "value" => v }
57
+ else
58
+ w = message(kind, v, path).to_wire("#{path}.")
59
+ w.empty? && !keep ? nil : w
60
+ end
61
+ end
62
+
63
+ def scalar(kind, v, path, keep)
64
+ case kind
65
+ when :str
66
+ s = case v
67
+ when String then v
68
+ when Symbol then v.name
69
+ else raise InvalidRequestError, "#{path} must be a String, not #{kind_of(v)}"
70
+ end
71
+ Payload.string!(s, path)
72
+ s.empty? && !keep ? nil : s
73
+ when :bool
74
+ raise InvalidRequestError, "#{path} must be true or false, not #{kind_of(v)}" unless [true, false].include?(v)
75
+
76
+ v || keep ? v : nil
77
+ when :int
78
+ raise InvalidRequestError, "#{path} must be an Integer, not #{kind_of(v)}" unless v.is_a?(Integer)
79
+
80
+ v.zero? && !keep ? nil : v
81
+ when :float
82
+ unless (v.is_a?(Float) && v.finite?) || v.is_a?(Integer)
83
+ raise InvalidRequestError, "#{path} must be a finite number, not #{kind_of(v)}"
84
+ end
85
+
86
+ v.zero? && !keep ? nil : v
87
+ end
88
+ end
89
+
90
+ # A message given as its class's instance, or as a Hash of its fields.
91
+ def message(klass, v, path)
92
+ return v if v.is_a?(klass)
93
+ unless v.is_a?(Hash)
94
+ raise InvalidRequestError, "#{path} must be a #{short(klass)} or a Hash of its fields, not #{kind_of(v)}"
95
+ end
96
+
97
+ v.each_key do |k|
98
+ next if k.is_a?(Symbol) && klass.members.include?(k)
99
+
100
+ name = k.to_s
101
+ if k.is_a?(String) && klass.members.include?(name.to_sym)
102
+ raise InvalidRequestError, "#{path}: field names are Symbols - #{name}:, not #{name.inspect}"
103
+ end
104
+
105
+ like = DidYouMean::SpellChecker.new(dictionary: klass.members.map(&:name)).correct(name).first
106
+ raise InvalidRequestError,
107
+ "#{path}.#{name} is not a field of #{short(klass)}#{": did you mean #{like}?" if like}"
108
+ end
109
+ missing = required_members(klass).find { !v.key?(it) }
110
+ raise InvalidRequestError, "#{path}.#{missing} is required" if missing
111
+
112
+ klass.new(**v)
113
+ end
114
+
115
+ # The fields a message type's initialize requires: every one when it is
116
+ # Data's own, which the generator leaves when no field has a default.
117
+ def required_members(klass)
118
+ params = klass.instance_method(:initialize).parameters
119
+ return klass.members if params == [[:rest]]
120
+
121
+ params.filter_map { |t, name| name if t == :keyreq }
122
+ end
123
+
124
+ # Raises InvalidRequestError naming the first field the proto marks
125
+ # required which the JSON +w+ of the request or message +key+ - its method
126
+ # or name in REQUIRED - leaves out: unset, or at its zero value. A nested
127
+ # message is checked when it is set, a list's each.
128
+ def check_required(w, key, path = "")
129
+ REQUIRED.fetch(key).each do |name, required, nested, list|
130
+ v = w[name]
131
+ if v.nil?
132
+ raise InvalidRequestError, "#{path}#{name} is required" if required
133
+
134
+ next
135
+ end
136
+ next unless nested
137
+
138
+ if list
139
+ v.each_with_index { |item, i| check_required(item, nested, "#{path}#{name}[#{i}].") }
140
+ else
141
+ check_required(v, nested, "#{path}#{name}.")
142
+ end
143
+ end
144
+ end
145
+
146
+ def map_key(k, path)
147
+ case k
148
+ when String
149
+ Payload.string!(k, "#{path} key #{k.inspect}")
150
+ k
151
+ when Symbol then k.name
152
+ else raise InvalidRequestError, "#{path} has a #{k.class} key, #{k.inspect}: keys are strings"
153
+ end
154
+ end
155
+
156
+ # The field +key+ of the decoded object +d+, at its zero value when the reply
157
+ # left it out; fields the client does not know are ignored.
158
+ def get(d, key, where, kind, shape = :one)
159
+ v = d[key]
160
+ case shape
161
+ when :list
162
+ return [] if v.nil?
163
+ raise DecodeError, "#{where}.#{key}: expected an array, got #{kind_of(v)}" unless v.is_a?(Array)
164
+
165
+ v.each_with_index.map { |item, i| decode(kind, item, "#{where}.#{key}[#{i}]") }
166
+ when :map
167
+ return {} if v.nil?
168
+
169
+ obj(v, "#{where}.#{key}").to_h { |k, item| [k, decode(kind, item, "#{where}.#{key}[#{k.inspect}]")] }
170
+ else
171
+ return ZERO.fetch(kind, nil) if v.nil? && kind != :json
172
+
173
+ decode(kind, v, "#{where}.#{key}")
174
+ end
175
+ end
176
+
177
+ def decode(kind, v, path)
178
+ case kind
179
+ when :str
180
+ raise DecodeError, "#{path}: expected a string, got #{kind_of(v)}" unless v.is_a?(String)
181
+ when :bool
182
+ raise DecodeError, "#{path}: expected a boolean, got #{kind_of(v)}" unless [true, false].include?(v)
183
+ when :int
184
+ raise DecodeError, "#{path}: expected an integer, got #{kind_of(v)}" unless v.is_a?(Integer)
185
+ when :float
186
+ raise DecodeError, "#{path}: expected a number, got #{kind_of(v)}" unless v.is_a?(Numeric)
187
+
188
+ return v.to_f
189
+ when :json then nil
190
+ when :bool_value, :int32_value
191
+ inner = obj(v, path)["value"]
192
+ # The value's zero, which a protobuf encoder leaves out.
193
+ return kind == :bool_value ? false : 0 if inner.nil?
194
+
195
+ return decode(kind == :bool_value ? :bool : :int, inner, "#{path}.value")
196
+ else return kind.from_wire(v, path)
197
+ end
198
+ v
199
+ end
200
+
201
+ def obj(v, where)
202
+ raise DecodeError, "#{where}: expected an object, got #{kind_of(v)}" unless v.is_a?(Hash)
203
+
204
+ v
205
+ end
206
+
207
+ def kind_of(v)
208
+ case v
209
+ when nil then "nil"
210
+ when true, false then v.to_s
211
+ when String then "a String"
212
+ when Integer then "an Integer, #{v}"
213
+ when Float then "a Float, #{v}"
214
+ when Array then "an Array"
215
+ when Hash then "a Hash"
216
+ else "a #{v.class}"
217
+ end
218
+ end
219
+
220
+ def short(klass) = klass.name.delete_prefix("Cent::")
221
+ end
222
+ end
data/lib/cent.rb CHANGED
@@ -1,10 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cent/version'
4
- require 'cent/notary'
5
- require 'cent/client'
3
+ require "json"
4
+ require "net/http"
5
+ require "openssl"
6
+ require "zlib"
6
7
 
7
- # Centrifugo Ruby Client
8
+ # Ruby SDK for Centrifugo's HTTP server API:
9
+ #
10
+ # client = Cent::Client.new(api_endpoint: "http://localhost:8000/api", api_key: ENV.fetch("CENTRIFUGO_API_KEY"))
11
+ # client.publish(channel: "news", data: { text: "hello" })
12
+ #
13
+ # See https://github.com/centrifugal/rubycent for the documentation.
8
14
  module Cent
9
- # Here will be code...
10
15
  end
16
+
17
+ require_relative "cent/version"
18
+ require_relative "cent/message"
19
+ require_relative "cent/errors"
20
+ require_relative "cent/payload"
21
+ require_relative "cent/wire"
22
+ require_relative "cent/http"
23
+ require_relative "cent/generated/types"
24
+ require_relative "cent/generated/methods"
25
+ require_relative "cent/reply"
26
+ require_relative "cent/pages"
27
+ require_relative "cent/broadcast"
28
+ require_relative "cent/retry"
29
+ require_relative "cent/batch"
30
+ require_relative "cent/command"
31
+ require_relative "cent/auto_batch"
32
+ require_relative "cent/client"
33
+ require_relative "cent/handler"
34
+ require_relative "cent/token"
35
+ require_relative "cent/shared_poll"
36
+ require_relative "cent/endpoints"
37
+ require_relative "cent/generated/proxy_types"
38
+ require_relative "cent/proxy"
metadata CHANGED
@@ -1,92 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cent
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 5.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Prikhodko
8
- autorequire:
8
+ - Centrifugal Labs
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-25 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: faraday
14
+ name: json
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "<"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.0.0
20
- - - ">"
21
- - !ruby/object:Gem::Version
22
- version: 2.0.0
19
+ version: '2.10'
23
20
  type: :runtime
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
- - - "<"
28
- - !ruby/object:Gem::Version
29
- version: 3.0.0
30
- - - ">"
31
- - !ruby/object:Gem::Version
32
- version: 2.0.0
33
- - !ruby/object:Gem::Dependency
34
- name: jwt
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - "<"
38
- - !ruby/object:Gem::Version
39
- version: 3.0.0
40
- - - ">"
41
- - !ruby/object:Gem::Version
42
- version: 2.2.0
43
- type: :runtime
44
- prerelease: false
45
- version_requirements: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: 3.0.0
50
- - - ">"
24
+ - - ">="
51
25
  - !ruby/object:Gem::Version
52
- version: 2.2.0
26
+ version: '2.10'
53
27
  description: |
54
- Provides helper classes Cent::Client and Cent::Notary.
55
-
56
- `Cent::Client` is made to communicate to the server API
57
- `Client::Notary` is a simple JWT wrapper to generate authorization tokens for the frontend
28
+ Every method of Centrifugo's HTTP server API, with typed results, batches,
29
+ automatic batching and retries; connection, subscription and API tokens,
30
+ JWKS; endpoints for client SDKs and Rack handlers for Centrifugo's proxy.
31
+ No dependencies but json.
58
32
  email:
59
33
  - prikha@gmail.com
60
34
  executables: []
61
35
  extensions: []
62
36
  extra_rdoc_files: []
63
37
  files:
64
- - ".github/workflows/main.yml"
65
- - ".github/workflows/release.yml"
66
- - ".gitignore"
67
- - ".rspec"
68
- - ".rubocop.yml"
69
38
  - CHANGELOG.md
70
- - Gemfile
71
39
  - LICENSE.txt
40
+ - MIGRATING.md
72
41
  - README.md
73
- - Rakefile
74
- - bin/console
75
- - bin/setup
76
- - cent.gemspec
77
42
  - lib/cent.rb
43
+ - lib/cent/auto_batch.rb
44
+ - lib/cent/batch.rb
45
+ - lib/cent/broadcast.rb
78
46
  - lib/cent/client.rb
79
- - lib/cent/error.rb
47
+ - lib/cent/command.rb
48
+ - lib/cent/endpoints.rb
49
+ - lib/cent/errors.rb
50
+ - lib/cent/generated/methods.rb
51
+ - lib/cent/generated/proxy_types.rb
52
+ - lib/cent/generated/types.rb
53
+ - lib/cent/handler.rb
80
54
  - lib/cent/http.rb
81
- - lib/cent/notary.rb
55
+ - lib/cent/message.rb
56
+ - lib/cent/pages.rb
57
+ - lib/cent/payload.rb
58
+ - lib/cent/proxy.rb
59
+ - lib/cent/reply.rb
60
+ - lib/cent/retry.rb
61
+ - lib/cent/shared_poll.rb
62
+ - lib/cent/token.rb
82
63
  - lib/cent/version.rb
64
+ - lib/cent/wire.rb
83
65
  homepage: https://github.com/centrifugal/rubycent
84
66
  licenses:
85
67
  - MIT
86
68
  metadata:
87
- homepage_uri: https://github.com/centrifugal/rubycent
88
69
  source_code_uri: https://github.com/centrifugal/rubycent
89
- post_install_message:
70
+ changelog_uri: https://github.com/centrifugal/rubycent/blob/master/CHANGELOG.md
71
+ bug_tracker_uri: https://github.com/centrifugal/rubycent/issues
72
+ documentation_uri: https://rubydoc.info/gems/cent
90
73
  rdoc_options: []
91
74
  require_paths:
92
75
  - lib
@@ -94,15 +77,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
77
  requirements:
95
78
  - - ">="
96
79
  - !ruby/object:Gem::Version
97
- version: 2.5.0
80
+ version: '3.4'
98
81
  required_rubygems_version: !ruby/object:Gem::Requirement
99
82
  requirements:
100
83
  - - ">="
101
84
  - !ruby/object:Gem::Version
102
85
  version: '0'
103
86
  requirements: []
104
- rubygems_version: 3.5.3
105
- signing_key:
87
+ rubygems_version: 4.0.20
106
88
  specification_version: 4
107
- summary: Centrifugo API V2 Ruby Client
89
+ summary: Ruby SDK for Centrifugo's HTTP server API
108
90
  test_files: []
@@ -1,20 +0,0 @@
1
- name: Ruby
2
-
3
- on: [push,pull_request]
4
-
5
- jobs:
6
- build:
7
- runs-on: ubuntu-latest
8
- strategy:
9
- matrix:
10
- ruby-version: ['2.7', '3.3']
11
- steps:
12
- - uses: actions/checkout@v4
13
- - name: Set up Ruby
14
- uses: ruby/setup-ruby@v1
15
- with:
16
- ruby-version: ${{ matrix.ruby-version }}
17
- - name: Run the default task
18
- run: |
19
- bundle install
20
- bundle exec rake
@@ -1,37 +0,0 @@
1
- name: Release Ruby Gem
2
-
3
- on:
4
- push:
5
- tags:
6
- - 'v[0-9].[0-9]+.[0-9]+'
7
- jobs:
8
- build-and-release:
9
- name: Release
10
- runs-on: ubuntu-latest
11
- permissions:
12
- packages: write
13
- contents: read
14
-
15
- steps:
16
- - name: Checkout
17
- uses: actions/checkout@v4
18
-
19
- - name: Set up Ruby
20
- uses: ruby/setup-ruby@v1
21
- with:
22
- ruby-version: 3.3.0
23
-
24
- - name: Bundle install
25
- run: |
26
- bundle install
27
-
28
- - name: Publish to RubyGems
29
- run: |
30
- mkdir -p $HOME/.gem
31
- touch $HOME/.gem/credentials
32
- chmod 0600 $HOME/.gem/credentials
33
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
34
- gem build *.gemspec
35
- gem push *.gem
36
- env:
37
- GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_API_KEY}}"
data/.gitignore DELETED
@@ -1,20 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
10
- *.bundle
11
- *.so
12
- *.o
13
- *.a
14
- mkmf.log
15
- .ruby-gemset
16
- .ruby-versions
17
- *.iml
18
- .idea/
19
- .rspec_status
20
-
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.rubocop.yml DELETED
@@ -1,13 +0,0 @@
1
- require:
2
- - rubocop-rspec
3
- - rubocop-rake
4
- AllCops:
5
- TargetRubyVersion: 2.5
6
- NewCops: enable
7
- RSpec/ExampleLength:
8
- Max: 15
9
- Metrics/BlockLength:
10
- Exclude:
11
- - 'spec/**/*'
12
- Gemspec/RequireMFA:
13
- Enabled: false
data/Gemfile DELETED
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source 'https://rubygems.org'
4
-
5
- # Specify your gem's dependencies in cent.gemspec
6
- gemspec
7
-
8
- gem 'rake', '~> 13.0'
9
-
10
- gem 'rspec', '~> 3.0'
11
- gem 'webmock', '~> 3.7.5'
12
-
13
- gem 'rubocop', '~> 1.7'
14
- gem 'rubocop-rake'
15
- gem 'rubocop-rspec'
16
-
17
- gem 'pry'
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/gem_tasks'
4
- require 'rspec/core/rake_task'
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
- require 'rubocop/rake_task'
9
-
10
- RuboCop::RakeTask.new
11
-
12
- task default: %i[spec rubocop]
data/bin/console DELETED
@@ -1,15 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require 'bundler/setup'
5
- require 'cent'
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require 'pry'
15
- Pry.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
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
data/cent.gemspec DELETED
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'lib/cent/version'
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = 'cent'
7
- spec.version = Cent::VERSION
8
- spec.authors = ['Sergey Prikhodko']
9
- spec.email = ['prikha@gmail.com']
10
-
11
- spec.summary = 'Centrifugo API V2 Ruby Client'
12
- spec.description = <<~DESC
13
- Provides helper classes Cent::Client and Cent::Notary.
14
-
15
- `Cent::Client` is made to communicate to the server API
16
- `Client::Notary` is a simple JWT wrapper to generate authorization tokens for the frontend
17
- DESC
18
- spec.homepage = 'https://github.com/centrifugal/rubycent'
19
- spec.license = 'MIT'
20
- spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
21
-
22
- spec.metadata['homepage_uri'] = spec.homepage
23
- spec.metadata['source_code_uri'] = 'https://github.com/centrifugal/rubycent'
24
-
25
- # Specify which files should be added to the gem when it is released.
26
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:spec|Gemfile)/}) }
29
- end
30
-
31
- spec.require_paths = ['lib']
32
-
33
- spec.add_dependency 'faraday', '<3.0.0', '> 2.0.0'
34
- spec.add_dependency 'jwt', '<3.0.0', '> 2.2.0'
35
- end
data/lib/cent/error.rb DELETED
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Cent
4
- # Cent::Error
5
- #
6
- # Wrapper for all errors(failures).
7
- #
8
- class Error < StandardError; end
9
- end