polyn 0.1.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.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +39 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop.yml +44 -0
- data/.tool-versions +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +32 -0
- data/Gemfile.lock +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +160 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/docker-compose.yml +7 -0
- data/lib/cloud-event-schema.json +187 -0
- data/lib/polyn/cloud_event.rb +13 -0
- data/lib/polyn/configuration.rb +30 -0
- data/lib/polyn/errors/configuration_error.rb +10 -0
- data/lib/polyn/errors/error.rb +13 -0
- data/lib/polyn/errors/errors.rb +6 -0
- data/lib/polyn/errors/schema_error.rb +10 -0
- data/lib/polyn/errors/validation_error.rb +10 -0
- data/lib/polyn/event.rb +138 -0
- data/lib/polyn/naming.rb +100 -0
- data/lib/polyn/pull_subscriber.rb +61 -0
- data/lib/polyn/schema_store.rb +50 -0
- data/lib/polyn/serializers/json.rb +136 -0
- data/lib/polyn/utils/hash.rb +82 -0
- data/lib/polyn/utils/string.rb +49 -0
- data/lib/polyn/utils/utils.rb +21 -0
- data/lib/polyn/version.rb +5 -0
- data/lib/polyn.rb +116 -0
- data/polyn.gemspec +52 -0
- metadata +123 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"description": "CloudEvents Specification JSON Schema, extended for Polyn",
|
4
|
+
"type": "object",
|
5
|
+
"properties": {
|
6
|
+
"id": {
|
7
|
+
"description": "Identifies the event.",
|
8
|
+
"$ref": "#/definitions/iddef",
|
9
|
+
"examples": [
|
10
|
+
"A234-1234-1234"
|
11
|
+
]
|
12
|
+
},
|
13
|
+
"source": {
|
14
|
+
"description": "Identifies the context in which an event happened.",
|
15
|
+
"$ref": "#/definitions/sourcedef",
|
16
|
+
"examples" : [
|
17
|
+
"https://github.com/cloudevents",
|
18
|
+
"mailto:cncf-wg-serverless@lists.cncf.io",
|
19
|
+
"urn:uuid:6e8bc430-9c3a-11d9-9669-0800200c9a66",
|
20
|
+
"cloudevents/spec/pull/123",
|
21
|
+
"/sensors/tn-1234567/alerts",
|
22
|
+
"1-555-123-4567"
|
23
|
+
]
|
24
|
+
},
|
25
|
+
"specversion": {
|
26
|
+
"description": "The version of the CloudEvents specification which the event uses.",
|
27
|
+
"$ref": "#/definitions/specversiondef",
|
28
|
+
"examples": [
|
29
|
+
"1.0"
|
30
|
+
]
|
31
|
+
},
|
32
|
+
"type": {
|
33
|
+
"description": "Describes the type of event related to the originating occurrence.",
|
34
|
+
"$ref": "#/definitions/typedef",
|
35
|
+
"examples" : [
|
36
|
+
"com.github.pull_request.opened",
|
37
|
+
"com.example.object.deleted.v2"
|
38
|
+
]
|
39
|
+
},
|
40
|
+
"datacontenttype": {
|
41
|
+
"description": "Content type of the data value. Must adhere to RFC 2046 format.",
|
42
|
+
"$ref": "#/definitions/datacontenttypedef",
|
43
|
+
"examples": [
|
44
|
+
"text/xml",
|
45
|
+
"application/json",
|
46
|
+
"image/png",
|
47
|
+
"multipart/form-data"
|
48
|
+
]
|
49
|
+
},
|
50
|
+
"dataschema": {
|
51
|
+
"description": "Identifies the schema that data adheres to.",
|
52
|
+
"$ref": "#/definitions/dataschemadef"
|
53
|
+
},
|
54
|
+
"subject": {
|
55
|
+
"description": "Describes the subject of the event in the context of the event producer (identified by source).",
|
56
|
+
"$ref": "#/definitions/subjectdef",
|
57
|
+
"examples": [
|
58
|
+
"mynewfile.jpg"
|
59
|
+
]
|
60
|
+
},
|
61
|
+
"time": {
|
62
|
+
"description": "Timestamp of when the occurrence happened. Must adhere to RFC 3339.",
|
63
|
+
"$ref": "#/definitions/timedef",
|
64
|
+
"examples": [
|
65
|
+
"2018-04-05T17:31:00Z"
|
66
|
+
]
|
67
|
+
},
|
68
|
+
"data": {
|
69
|
+
"description": "The event payload.",
|
70
|
+
"$ref": "#/definitions/datadef",
|
71
|
+
"examples": [
|
72
|
+
"<much wow=\"xml\"/>"
|
73
|
+
]
|
74
|
+
},
|
75
|
+
"data_base64": {
|
76
|
+
"description": "Base64 encoded event payload. Must adhere to RFC4648.",
|
77
|
+
"$ref": "#/definitions/data_base64def",
|
78
|
+
"examples": [
|
79
|
+
"Zm9vYg=="
|
80
|
+
]
|
81
|
+
},
|
82
|
+
"polyndata": {
|
83
|
+
"$ref": "#/definitions/polyndatadef",
|
84
|
+
"description": "Information about the client that produced the event and additional metadata",
|
85
|
+
"examples": [
|
86
|
+
{
|
87
|
+
"clientlang": "elixir",
|
88
|
+
"clientlangversion": "1.13.2",
|
89
|
+
"clientversion": "0.1.0"
|
90
|
+
}
|
91
|
+
]
|
92
|
+
},
|
93
|
+
"polyntrace": {
|
94
|
+
"$ref": "#/definitions/polyntracedef",
|
95
|
+
"description": "Previous events that led to this one",
|
96
|
+
"examples": [
|
97
|
+
[
|
98
|
+
{
|
99
|
+
"type": "<topic>",
|
100
|
+
"time": "2018-04-05T17:31:00Z",
|
101
|
+
"id": "<uuid>"
|
102
|
+
}
|
103
|
+
]
|
104
|
+
]
|
105
|
+
}
|
106
|
+
},
|
107
|
+
"required": ["id", "source", "specversion", "type"],
|
108
|
+
"definitions": {
|
109
|
+
"iddef": {
|
110
|
+
"type": "string",
|
111
|
+
"minLength": 1
|
112
|
+
},
|
113
|
+
"sourcedef": {
|
114
|
+
"type": "string",
|
115
|
+
"format": "uri-reference",
|
116
|
+
"minLength": 1
|
117
|
+
},
|
118
|
+
"specversiondef": {
|
119
|
+
"type": "string",
|
120
|
+
"minLength": 1
|
121
|
+
},
|
122
|
+
"typedef": {
|
123
|
+
"type": "string",
|
124
|
+
"minLength": 1
|
125
|
+
},
|
126
|
+
"datacontenttypedef": {
|
127
|
+
"type": ["string", "null"],
|
128
|
+
"minLength": 1
|
129
|
+
},
|
130
|
+
"dataschemadef": {
|
131
|
+
"type": ["string", "null"],
|
132
|
+
"format": "uri",
|
133
|
+
"minLength": 1
|
134
|
+
},
|
135
|
+
"subjectdef": {
|
136
|
+
"type": ["string", "null"],
|
137
|
+
"minLength": 1
|
138
|
+
},
|
139
|
+
"timedef": {
|
140
|
+
"type": ["string", "null"],
|
141
|
+
"format": "date-time",
|
142
|
+
"minLength": 1
|
143
|
+
},
|
144
|
+
"datadef": {
|
145
|
+
"type": ["object", "string", "number", "array", "boolean", "null"]
|
146
|
+
},
|
147
|
+
"data_base64def": {
|
148
|
+
"type": ["string", "null"],
|
149
|
+
"contentEncoding": "base64"
|
150
|
+
},
|
151
|
+
"polyndatadef": {
|
152
|
+
"type": "object",
|
153
|
+
"properties": {
|
154
|
+
"clientlang": {
|
155
|
+
"type": "string"
|
156
|
+
},
|
157
|
+
"clientlangversion": {
|
158
|
+
"type": "string"
|
159
|
+
},
|
160
|
+
"clientversion": {
|
161
|
+
"type": "string"
|
162
|
+
}
|
163
|
+
},
|
164
|
+
"required": ["clientlang", "clientlangversion", "clientversion"]
|
165
|
+
},
|
166
|
+
"polyntracedef": {
|
167
|
+
"type" : "array",
|
168
|
+
"items": {
|
169
|
+
"type": "object",
|
170
|
+
"properties": {
|
171
|
+
"type": {
|
172
|
+
"type": "string"
|
173
|
+
},
|
174
|
+
"time": {
|
175
|
+
"type": "string",
|
176
|
+
"format": "date-time"
|
177
|
+
},
|
178
|
+
"id" : {
|
179
|
+
"type": "string",
|
180
|
+
"format": "uuid"
|
181
|
+
}
|
182
|
+
},
|
183
|
+
"required": ["type", "time", "id"]
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyn
|
4
|
+
##
|
5
|
+
# Access cloud event information
|
6
|
+
class CloudEvent
|
7
|
+
def self.to_h
|
8
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), "../cloud-event-schema.json"))
|
9
|
+
file = File.open(path)
|
10
|
+
JSON.parse(file.read)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyn
|
4
|
+
##
|
5
|
+
# Configuration data for Polyn
|
6
|
+
class Configuration
|
7
|
+
def initialize
|
8
|
+
@domain = nil
|
9
|
+
@source_root = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def domain
|
13
|
+
@domain ||= Polyn::Naming.validate_domain_name!(@domain)
|
14
|
+
end
|
15
|
+
|
16
|
+
def domain=(name)
|
17
|
+
Polyn::Naming.validate_domain_name!(name)
|
18
|
+
@domain = name
|
19
|
+
end
|
20
|
+
|
21
|
+
def source_root
|
22
|
+
@source_root ||= Polyn::Naming.validate_source_root!(@source_root)
|
23
|
+
end
|
24
|
+
|
25
|
+
def source_root=(name)
|
26
|
+
Polyn::Naming.validate_source_root!(name)
|
27
|
+
@source_root = name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/polyn/event.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2021-2022 Spiff, Inc.
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
6
|
+
# software and associated documentation files (the "Software"), to deal in the Software
|
7
|
+
# without restriction, including without limitation the rights to use, copy, modify, merge,
|
8
|
+
# publish, distribute, sublicense, and/or sell copies of the Software, and to permit
|
9
|
+
# persons to whom the Software is furnished to do so, subject to the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be included in all copies or
|
12
|
+
# substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
15
|
+
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
16
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
17
|
+
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
19
|
+
|
20
|
+
module Polyn
|
21
|
+
##
|
22
|
+
# Represents an event. Events follow the [Cloudevents](https://github.com/cloudevents)
|
23
|
+
# specification.
|
24
|
+
class Event
|
25
|
+
CLOUD_EVENT_VERSION = "1.0"
|
26
|
+
|
27
|
+
class UnsupportedVersionError < Errors::Error; end
|
28
|
+
|
29
|
+
##
|
30
|
+
# @return [String] the cloud event version
|
31
|
+
attr_reader :specversion
|
32
|
+
|
33
|
+
##
|
34
|
+
# @return [String] event id
|
35
|
+
attr_reader :id
|
36
|
+
|
37
|
+
##
|
38
|
+
# @return [String] event type
|
39
|
+
attr_reader :type
|
40
|
+
|
41
|
+
##
|
42
|
+
# @return [String] event source
|
43
|
+
attr_reader :source
|
44
|
+
|
45
|
+
##
|
46
|
+
# @return [String] time of event creation
|
47
|
+
attr_reader :time
|
48
|
+
|
49
|
+
##
|
50
|
+
# @return [String] the data content type
|
51
|
+
attr_accessor :datacontenttype
|
52
|
+
|
53
|
+
##
|
54
|
+
# @return [String] the data
|
55
|
+
attr_reader :data
|
56
|
+
|
57
|
+
##
|
58
|
+
# @return [Array] Previous events that led to this one
|
59
|
+
attr_reader :polyntrace
|
60
|
+
|
61
|
+
##
|
62
|
+
# @return [Hash] Represents the information about the client that published the event
|
63
|
+
# as well as additional metadata
|
64
|
+
attr_reader :polyndata
|
65
|
+
|
66
|
+
def initialize(hash)
|
67
|
+
@specversion = hash.key?(:specversion) ? hash[:specversion] : "1.0"
|
68
|
+
|
69
|
+
unless Gem::Dependency.new("", "~> #{CLOUD_EVENT_VERSION}").match?("", @specversion)
|
70
|
+
raise UnsupportedVersionError, "Unsupported version: '#{hash[:specversion]}'"
|
71
|
+
end
|
72
|
+
|
73
|
+
@id = hash.fetch(:id, SecureRandom.uuid)
|
74
|
+
@type = self.class.full_type(hash.fetch(:type))
|
75
|
+
@source = self.class.full_source(hash[:source])
|
76
|
+
@time = hash.fetch(:time, Time.now.utc.iso8601)
|
77
|
+
@data = hash.fetch(:data)
|
78
|
+
@datacontenttype = hash.fetch(:datacontenttype, "application/json")
|
79
|
+
@polyntrace = self.class.build_polyntrace(hash[:triggered_by])
|
80
|
+
@polyndata = {
|
81
|
+
clientlang: "ruby",
|
82
|
+
clientlangversion: RUBY_VERSION,
|
83
|
+
clientversion: Polyn::VERSION,
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
def to_h
|
88
|
+
{
|
89
|
+
"specversion" => specversion,
|
90
|
+
"id" => id,
|
91
|
+
"type" => type,
|
92
|
+
"source" => source,
|
93
|
+
"time" => time,
|
94
|
+
"data" => Utils::Hash.deep_stringify_keys(data),
|
95
|
+
"datacontenttype" => datacontenttype,
|
96
|
+
"polyntrace" => Utils::Hash.deep_stringify_keys(polyntrace),
|
97
|
+
"polyndata" => Utils::Hash.deep_stringify_keys(polyndata),
|
98
|
+
}
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# Get the Event `source` prefixed with reverse domain name
|
103
|
+
def self.full_source(source = nil)
|
104
|
+
root = Polyn.configuration.source_root
|
105
|
+
name = Polyn::Naming.dot_to_colon("#{domain}:#{root}")
|
106
|
+
|
107
|
+
if source
|
108
|
+
Polyn::Naming.validate_source_name!(source)
|
109
|
+
"#{name}:#{Polyn::Naming.dot_to_colon(source)}"
|
110
|
+
else
|
111
|
+
name
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
##
|
116
|
+
# Get the Event `type` prefixed with reverse domain name
|
117
|
+
def self.full_type(type)
|
118
|
+
Polyn::Naming.validate_event_type!(type)
|
119
|
+
"#{domain}.#{Polyn::Naming.trim_domain_prefix(type)}"
|
120
|
+
end
|
121
|
+
|
122
|
+
##
|
123
|
+
# Use a triggering event to build the polyntrace of a new event
|
124
|
+
def self.build_polyntrace(triggered_by)
|
125
|
+
return [] unless triggered_by
|
126
|
+
|
127
|
+
triggered_by.polyntrace.concat([{
|
128
|
+
id: triggered_by.id,
|
129
|
+
type: triggered_by.type,
|
130
|
+
time: triggered_by.time,
|
131
|
+
}])
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.domain
|
135
|
+
Polyn.configuration.domain
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/polyn/naming.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyn
|
4
|
+
##
|
5
|
+
# Methods for formatting and validating names of fields
|
6
|
+
class Naming
|
7
|
+
##
|
8
|
+
# Convert a dot separated name into a colon separated name
|
9
|
+
def self.dot_to_colon(str)
|
10
|
+
str.gsub(".", ":")
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Validate that the configured `domain` is in the correct format
|
15
|
+
def self.validate_domain_name!(name)
|
16
|
+
if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:(?:\.|:)[a-z0-9]+)*\z/)
|
17
|
+
name
|
18
|
+
else
|
19
|
+
raise Polyn::Errors::ConfigurationError,
|
20
|
+
"You must configure the `domain` for Polyn. It must be lowercase, alphanumeric and dot/colon separated, got #{name}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
##
|
25
|
+
# Validate the `source` name
|
26
|
+
def self.validate_source_name(name)
|
27
|
+
if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:(?:\.|:)[a-z0-9]+)*\z/)
|
28
|
+
true
|
29
|
+
else
|
30
|
+
"Event source must be lowercase, alphanumeric and dot/colon separated, got #{name}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Validate the `source` name and raise if invalid
|
36
|
+
def self.validate_source_name!(name)
|
37
|
+
message = validate_source_name(name)
|
38
|
+
if message == true
|
39
|
+
name
|
40
|
+
else
|
41
|
+
raise Polyn::Errors::ValidationError, message
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Validate that the configured `source_root` is in the correct format
|
47
|
+
def self.validate_source_root!(name)
|
48
|
+
message = validate_source_name(name)
|
49
|
+
if message == true
|
50
|
+
name
|
51
|
+
else
|
52
|
+
raise Polyn::Errors::ConfigurationError,
|
53
|
+
"You must configure the `source_root` for Polyn. #{message}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Validate the event type
|
59
|
+
def self.validate_event_type!(name)
|
60
|
+
if name.is_a?(String) && name.match?(/\A[a-z0-9]+(?:\.[a-z0-9]+)*\z/)
|
61
|
+
name
|
62
|
+
else
|
63
|
+
raise Polyn::Errors::ValidationError,
|
64
|
+
"Event types must be lowercase, alphanumeric and dot separated"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Remove the `domain` name from the beginning of a string
|
70
|
+
def self.trim_domain_prefix(str)
|
71
|
+
str = str.sub("#{domain}.", "")
|
72
|
+
str.sub("#{dot_to_colon(domain)}:", "")
|
73
|
+
end
|
74
|
+
|
75
|
+
##
|
76
|
+
# Create a consumer name from a source and type
|
77
|
+
def self.consumer_name(type, source = nil)
|
78
|
+
validate_event_type!(type)
|
79
|
+
type = trim_domain_prefix(type)
|
80
|
+
type = type.gsub(".", "_")
|
81
|
+
|
82
|
+
root = Polyn.configuration.source_root
|
83
|
+
root = root.gsub(".", "_")
|
84
|
+
root = root.gsub(":", "_")
|
85
|
+
|
86
|
+
if source
|
87
|
+
validate_source_name!(source)
|
88
|
+
source = source.gsub(".", "_")
|
89
|
+
source = source.gsub(":", "_")
|
90
|
+
[root, source, type].join("_")
|
91
|
+
else
|
92
|
+
[root, type].join("_")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.domain
|
97
|
+
Polyn.configuration.domain
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyn
|
4
|
+
##
|
5
|
+
# Wrapper around nats-pure that can validate polyn messages
|
6
|
+
class PullSubscriber
|
7
|
+
##
|
8
|
+
# @param fields [Object] :nats - Connected NATS instance from `NATS.connect`
|
9
|
+
# @param fields [String] :type - The type of event
|
10
|
+
# @option fields [String] :source - If the `source` portion of the consumer name
|
11
|
+
# is more than the `source_root`
|
12
|
+
def initialize(fields)
|
13
|
+
@nats = fields.fetch(:nats)
|
14
|
+
@type = fields.fetch(:type)
|
15
|
+
@type = Polyn::Naming.trim_domain_prefix(@type)
|
16
|
+
@consumer_name = Polyn::Naming.consumer_name(@type, fields[:source])
|
17
|
+
@stream = @nats.jetstream.find_stream_name_by_subject(@type)
|
18
|
+
self.class.validate_consumer_exists!(@nats, @stream, @consumer_name)
|
19
|
+
@psub = @nats.jetstream.pull_subscribe(@type, @consumer_name)
|
20
|
+
@store_name = store_name(fields)
|
21
|
+
end
|
22
|
+
|
23
|
+
# nats-pure will create a consumer if the one you passed does not exist.
|
24
|
+
# Polyn wants to avoid this functionality and instead encourage
|
25
|
+
# consumer creation in the centralized `events` codebase so that
|
26
|
+
# it's documented, discoverable, and polyn-cli can manage it
|
27
|
+
def self.validate_consumer_exists!(nats, stream, consumer_name)
|
28
|
+
nats.jetstream.consumer_info(stream, consumer_name)
|
29
|
+
rescue NATS::JetStream::Error::NotFound
|
30
|
+
raise Polyn::Errors::ValidationError,
|
31
|
+
"Consumer #{consumer_name} does not exist. Use polyn-cli to create"\
|
32
|
+
"it before attempting to subscribe"
|
33
|
+
end
|
34
|
+
|
35
|
+
# fetch makes a request to be delivered more messages from a pull consumer.
|
36
|
+
#
|
37
|
+
# @param batch [Fixnum] Number of messages to pull from the stream.
|
38
|
+
# @param params [Hash] Options to customize the fetch request.
|
39
|
+
# @option params [Float] :timeout Duration of the fetch request before it expires.
|
40
|
+
# @return [Array<NATS::Msg>]
|
41
|
+
def fetch(batch = 1, params = {})
|
42
|
+
msgs = @psub.fetch(batch, params)
|
43
|
+
msgs.map do |msg|
|
44
|
+
event = Polyn::Serializers::Json.deserialize(@nats, msg.data, store_name: @store_name)
|
45
|
+
if event.is_a?(Polyn::Errors::Error)
|
46
|
+
msg.term
|
47
|
+
raise event
|
48
|
+
end
|
49
|
+
|
50
|
+
msg.data = event
|
51
|
+
msg
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def store_name(opts)
|
58
|
+
opts.fetch(:store_name, Polyn::SchemaStore.store_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Polyn
|
4
|
+
##
|
5
|
+
# Persisting and interacting with persisted schemas
|
6
|
+
class SchemaStore
|
7
|
+
STORE_NAME = "POLYN_SCHEMAS"
|
8
|
+
|
9
|
+
##
|
10
|
+
# Persist a schema. In prod/dev schemas should have already been persisted via
|
11
|
+
# the Polyn CLI.
|
12
|
+
def self.save(nats, type, schema, **opts)
|
13
|
+
json_schema?(schema)
|
14
|
+
kv = nats.jetstream.key_value(store_name(**opts))
|
15
|
+
kv.put(type, JSON.generate(schema))
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.json_schema?(schema)
|
19
|
+
JSONSchemer.schema(schema)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.get!(nats, type, **opts)
|
23
|
+
result = get(nats, type, **opts)
|
24
|
+
raise result if result.is_a?(Polyn::Errors::SchemaError)
|
25
|
+
|
26
|
+
result
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.get(nats, type, **opts)
|
30
|
+
kv = nats.jetstream.key_value(store_name(**opts))
|
31
|
+
entry = kv.get(type)
|
32
|
+
entry.value
|
33
|
+
rescue NATS::KeyValue::BucketNotFoundError
|
34
|
+
Polyn::Errors::SchemaError.new(
|
35
|
+
"The Schema Store has not been setup on your NATS server. Make sure you use "\
|
36
|
+
"the Polyn CLI to create it",
|
37
|
+
)
|
38
|
+
rescue NATS::JetStream::Error::NotFound
|
39
|
+
Polyn::Errors::SchemaError.new(
|
40
|
+
"Schema for #{type} does not exist. Make sure it's "\
|
41
|
+
"been added to your `events` codebase and has been loaded "\
|
42
|
+
"into the schema store on your NATS server",
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.store_name(**opts)
|
47
|
+
opts.fetch(:name, STORE_NAME)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|