x-streams 1.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.
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module X
4
+ module Streams
5
+ # Checks the settings of a streaming client and the parsing classes of a stream before a stream is opened, and the
6
+ # identifiers of rules
7
+ #
8
+ # Internal to x-streams: StreamingClient and ReconnectHandler check what they are given with it, with the
9
+ # messages X::Client checks its own settings with, and StreamRule and StreamRules read identifiers with it, as
10
+ # x-resources reads them.
11
+ #
12
+ # @api private
13
+ module Validator
14
+ extend self
15
+
16
+ # The message of the error raised for a count that is neither an Integer of at least 0 nor Float::INFINITY
17
+ INVALID_COUNT_OR_INFINITY = "%s must be an Integer of at least 0, or Float::INFINITY for no limit, not %s"
18
+ # The fewest seconds a stream may wait for a read: the interval of the keep-alive X sends a quiet stream, and five
19
+ # seconds more, so that a keep-alive that arrives a little late does not time the read out
20
+ MINIMUM_READ_TIMEOUT = 25 # seconds
21
+ # The message of the error raised for a read timeout that is neither a finite number of seconds of at least
22
+ # MINIMUM_READ_TIMEOUT nor nil
23
+ INVALID_READ_TIMEOUT = "%s must be a finite number of seconds of at least #{MINIMUM_READ_TIMEOUT}, five more than the " \
24
+ "20-second interval of the keep-alive X sends a quiet stream, or nil for no timeout, not %s"
25
+ # The message of the error raised for an array_class that is not a Class
26
+ INVALID_ARRAY_CLASS = "%s must be a Class that JSON.parse builds each array into, such as Array, not %s"
27
+ # The message of the error raised for an object_class that is neither a Class nor responds to from_response
28
+ INVALID_OBJECT_CLASS = "%s must be a Class that JSON.parse builds each object into, such as Hash, or respond to " \
29
+ "from_response, as the resource classes of x-resources do, not %s"
30
+ # The message of the error raised for a callback that neither responds to call nor is nil
31
+ INVALID_CALLABLE = "%s must respond to call, as a Proc or a lambda does, or be nil, not %s %s"
32
+ # The pattern of the name of a class that an takes the place of a before, as that of an Integer
33
+ VOWEL = /\A[AEIOU]/
34
+ # The pattern of an identifier given as a String: digits alone, with no sign, underscore, or whitespace
35
+ IDENTIFIER = /\A\d+\z/
36
+ # The message of the error raised for an identifier that is neither an Integer that is not negative nor a String
37
+ # of digits, as Integer() words it
38
+ INVALID_IDENTIFIER = "invalid value for Integer(): %s"
39
+ private_constant :MINIMUM_READ_TIMEOUT, :INVALID_COUNT_OR_INFINITY, :INVALID_READ_TIMEOUT, :INVALID_ARRAY_CLASS, :INVALID_OBJECT_CLASS,
40
+ :INVALID_CALLABLE, :VOWEL, :IDENTIFIER, :INVALID_IDENTIFIER
41
+
42
+ # Check that a count is an Integer of at least 0, or Float::INFINITY for no limit
43
+ #
44
+ # @api private
45
+ # @param name [Symbol] the name of the setting, which the error names
46
+ # @param value [Object] the value of the setting
47
+ # @return [Integer, Float] the value
48
+ # @raise [ArgumentError] if the value is neither an Integer of at least 0 nor Float::INFINITY
49
+ def count_or_infinity!(name, value)
50
+ return value if (value.instance_of?(Integer) && !value.negative?) || Float::INFINITY.eql?(value)
51
+
52
+ raise ArgumentError, format(INVALID_COUNT_OR_INFINITY, name, value.inspect)
53
+ end
54
+
55
+ # Check that a read timeout is a finite number of seconds of at least 25, or nil
56
+ #
57
+ # X sends a quiet stream a keep-alive every 20 seconds, so a read timeout no longer than that drops a stream that
58
+ # is quiet but connected whenever a keep-alive arrives a little late, and one shorter drops it every time, and
59
+ # reconnects it without end, delivering nothing, where the read timeout of a request may be as short as 0.
60
+ #
61
+ # @api private
62
+ # @param name [Symbol] the name of the setting, which the error names
63
+ # @param value [Object] the value of the setting
64
+ # @return [Integer, Float, nil] the value
65
+ # @raise [ArgumentError] if the value is neither a finite real number of at least MINIMUM_READ_TIMEOUT nor nil
66
+ def read_timeout!(name, value)
67
+ return value if value.nil? || (value.is_a?(Numeric) && value.real? && value.finite? && value >= MINIMUM_READ_TIMEOUT)
68
+
69
+ raise ArgumentError, format(INVALID_READ_TIMEOUT, name, value.inspect)
70
+ end
71
+
72
+ # Check that a callback responds to call, or is nil
73
+ #
74
+ # @api private
75
+ # @param name [Symbol] the name of the setting, which the error names
76
+ # @param value [Object] the value of the setting
77
+ # @return [#call, nil] the value
78
+ # @raise [ArgumentError] if the value neither responds to call nor is nil
79
+ def callable!(name, value)
80
+ return value if value.nil? || value.respond_to?(:call)
81
+
82
+ raise ArgumentError, format(INVALID_CALLABLE, name, value.class.to_s.match?(VOWEL) ? "an" : "a", value.class)
83
+ end
84
+
85
+ # Read the identifier of a rule as an Integer
86
+ #
87
+ # It is read as strictly as x-resources reads an identifier: an Integer that is not negative, or a String of digits
88
+ # alone, as the API sends one, with no sign, underscore, or whitespace, which Integer() would take, so that
89
+ # " 1_0 " is not read as 10, nor "-1" as -1.
90
+ #
91
+ # @api private
92
+ # @param value [Object] the identifier
93
+ # @return [Integer] the identifier
94
+ # @raise [ArgumentError] if the identifier is neither an Integer that is not negative nor a String of digits
95
+ def identifier!(value)
96
+ return value if value.instance_of?(Integer) && !value.negative?
97
+ raise ArgumentError, format(INVALID_IDENTIFIER, value.to_s.inspect) unless value.instance_of?(String) && IDENTIFIER.match?(value)
98
+
99
+ Integer(value, 10)
100
+ end
101
+
102
+ # Check the classes a stream parses its objects into
103
+ #
104
+ # @api private
105
+ # @param array_class [Object] the class for parsing JSON arrays
106
+ # @param object_class [Object] the class for parsing JSON objects, or one that responds to from_response
107
+ # @return [void]
108
+ # @raise [ArgumentError] if array_class is not a Class, or object_class is neither a Class nor responds to
109
+ # from_response
110
+ def parsing_classes!(array_class:, object_class:)
111
+ raise ArgumentError, format(INVALID_ARRAY_CLASS, :array_class, array_class.inspect) unless array_class.instance_of?(Class)
112
+ return if object_class.instance_of?(Class) || object_class.respond_to?(:from_response)
113
+
114
+ raise ArgumentError, format(INVALID_OBJECT_CLASS, :object_class, object_class.inspect)
115
+ end
116
+ end
117
+ private_constant :Validator
118
+ end
119
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems/version"
4
+
5
+ module X
6
+ # Streams from the X API, and the rules of its filtered stream
7
+ # @api public
8
+ module Streams
9
+ # The current version of the x-streams gem
10
+ VERSION = "1.0.0"
11
+
12
+ # The version as a Gem::Version, which compares one release with another
13
+ #
14
+ # VERSION is a String, as a version constant is throughout Ruby, so that what reads it can split it, match it,
15
+ # or send it wherever a String belongs. This builds the Gem::Version that compares it with another version,
16
+ # which a String compares by character rather than by segment.
17
+ #
18
+ # @api public
19
+ # @return [Gem::Version] the version
20
+ # @example Take a path that a later release opened
21
+ # X::Streams.gem_version >= Gem::Version.new("1.1")
22
+ def self.gem_version = Gem::Version.new(VERSION)
23
+ end
24
+ end
data/lib/x/streams.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "streams/version"
4
+ require_relative "streams/api"
5
+ require_relative "streams/error"
6
+ require_relative "streams/rules_rejected"
7
+ require_relative "streams/stream_error"
8
+ require_relative "streams/stream_rule"
9
+ require_relative "streams/streaming_client"
data/sig/manifest.yaml ADDED
@@ -0,0 +1,6 @@
1
+ # The standard libraries the signatures of x-streams refer to, which rbs collection loads for code that depends on it
2
+ #
3
+ # rbs collection reads every library named here as a standard library, so the gems x-streams depends on are left to
4
+ # its gemspec, from which it installs their signatures.
5
+ dependencies:
6
+ - name: uri
data/sig/x-streams.rbs ADDED
@@ -0,0 +1,87 @@
1
+ # Signatures of the public API of x-streams, which the gem ships; sig/internal declares its internals for Steep alone
2
+ module X
3
+ module Streams
4
+ VERSION: String
5
+
6
+ def self.gem_version: () -> Gem::Version
7
+
8
+ class Error < X::Error
9
+ end
10
+
11
+ # The streaming method mixed into a client
12
+ module API
13
+ def streaming: (?read_timeout: Numeric?, ?max_reconnects: Float | Integer, ?on_reconnect: StreamingClient::_ReconnectHook?) -> StreamingClient
14
+ end
15
+ end
16
+
17
+ class RulesRejected < Streams::Error
18
+ attr_reader problems: Array[Problem]
19
+ attr_reader added: Array[StreamRule]?
20
+ attr_reader deleted_count: Integer?
21
+
22
+ def initialize: (?String? message, ?problems: Array[Problem], ?added: Array[StreamRule]?, ?deleted_count: Integer?) -> void
23
+ end
24
+
25
+ class StreamError < Streams::Error
26
+ attr_reader problems: Array[Problem]
27
+ attr_reader http_method: Symbol?
28
+ attr_reader uri: URI::Generic?
29
+
30
+ def initialize: (?String? message, ?problems: Array[Problem], ?http_method: (Symbol | String)?, ?uri: URI::Generic?) -> void
31
+ end
32
+
33
+ class StreamRule
34
+ attr_reader id: Integer?
35
+ attr_reader value: String
36
+ attr_reader tag: String?
37
+ def initialize: (value: String, ?tag: String?, ?id: (Integer | String)?) -> void
38
+ def to_h: () -> {id: Integer?, value: String, tag: String?}
39
+ def deconstruct_keys: (Array[Symbol]? _keys) -> {id: Integer?, value: String, tag: String?}
40
+ def ==: (untyped other) -> bool
41
+ def eql?: (untyped other) -> bool
42
+ def hash: () -> Integer
43
+ def inspect: () -> String
44
+ def marshal_dump: () -> untyped
45
+ def marshal_load: (untyped state) -> void
46
+ def encode_with: (untyped coder) -> void
47
+ def init_with: (untyped coder) -> void
48
+ end
49
+
50
+ class StreamingClient
51
+ DEFAULT_READ_TIMEOUT: Float | Integer
52
+ DEFAULT_MAX_RECONNECTS: Float | Integer
53
+
54
+ type addable_rule = StreamRule | Hash[untyped, untyped] | String
55
+ type rule = addable_rule | Integer | _MatchingRule
56
+
57
+ # The X::MatchingRule of x-resources, which x-streams does not depend on, and which names a rule by its identifier;
58
+ # anything else with an id raises ArgumentError
59
+ interface _MatchingRule
60
+ def id: () -> Integer
61
+ def tag: () -> String?
62
+ end
63
+
64
+ # The callable passed the error that dropped a stream and its wait to reconnect, before each wait; every release
65
+ # of 1.x passes these two arguments and no others
66
+ interface _ReconnectHook
67
+ def call: (StandardError error, Integer | Float wait) -> untyped
68
+ end
69
+
70
+ attr_reader client: Client
71
+ attr_reader on_reconnect: _ReconnectHook?
72
+ def initialize: (Client client, ?read_timeout: Numeric?, ?max_reconnects: Integer | Float, ?on_reconnect: _ReconnectHook?) -> void
73
+ def inspect: () -> String
74
+ def stream: (String endpoint, ?params: params?, ?headers: headers, ?array_class: Class, ?object_class: object_class) { (untyped) -> void } -> untyped
75
+ def stop: () -> nil
76
+ def stopped?: () -> bool
77
+ def read_timeout: () -> Numeric?
78
+ def max_reconnects: () -> (Integer | Float)
79
+ def rules: (?params: params?) -> Array[StreamRule]
80
+ def add_rules: (Array[addable_rule] | addable_rule rules, ?dry_run: bool) ?{ (Problem problem) -> void } -> Array[StreamRule]
81
+ def delete_rules: (Array[rule] | rule rules, ?dry_run: bool) ?{ (Problem problem) -> void } -> Integer
82
+ def marshal_dump: () -> bot
83
+ def encode_with: (untyped coder) -> bot
84
+ def as_json: (*untyped) -> bot
85
+ def to_json: (?JSON::State? state) -> bot
86
+ end
87
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: x-streams
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Erik Berlin
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: x-core
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 1.0.0
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 1.0.0
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '2'
32
+ email:
33
+ - sferik@gmail.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - ".yardopts"
39
+ - CHANGELOG.md
40
+ - LICENSE.txt
41
+ - README.md
42
+ - lib/x/streams.rb
43
+ - lib/x/streams/api.rb
44
+ - lib/x/streams/callback_error.rb
45
+ - lib/x/streams/error.rb
46
+ - lib/x/streams/reconnect_handler.rb
47
+ - lib/x/streams/rules_rejected.rb
48
+ - lib/x/streams/stopper.rb
49
+ - lib/x/streams/stream_error.rb
50
+ - lib/x/streams/stream_parser.rb
51
+ - lib/x/streams/stream_rule.rb
52
+ - lib/x/streams/stream_rules.rb
53
+ - lib/x/streams/streaming_client.rb
54
+ - lib/x/streams/validator.rb
55
+ - lib/x/streams/version.rb
56
+ - sig/manifest.yaml
57
+ - sig/x-streams.rbs
58
+ homepage: https://sferik.github.io/x-ruby
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ allowed_push_host: https://rubygems.org
63
+ bug_tracker_uri: https://github.com/sferik/x-ruby/issues
64
+ changelog_uri: https://github.com/sferik/x-ruby/blob/main/x-streams/CHANGELOG.md
65
+ documentation_uri: https://rubydoc.info/gems/x-streams/
66
+ funding_uri: https://github.com/sponsors/sferik/
67
+ homepage_uri: https://sferik.github.io/x-ruby
68
+ rubygems_mfa_required: 'true'
69
+ source_code_uri: https://github.com/sferik/x-ruby/tree/main/x-streams
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '3.4'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 4.0.21
85
+ specification_version: 4
86
+ summary: 'Streaming for the X gem: the sample and filtered streams, reconnected as
87
+ X recommends, and the rules of the filtered stream.'
88
+ test_files: []