Class: JetstreamBridge::Models::Subject

Inherits:
Object
  • Object
show all
Defined in:
lib/jetstream_bridge/models/subject.rb

Overview

Value object representing a NATS subject

Examples:

Creating a subject

subject = Subject.source(app_name: "api", dest: "worker")
subject.to_s # => "api.sync.worker"

Parsing a subject string

subject = Subject.parse("api.sync.worker")
subject.source_app  # => "api"
subject.dest_app    # => "worker"

Constant Summary collapse

WILDCARD_SINGLE =
'*'
WILDCARD_MULTI =
'>'
SEPARATOR =
'.'
INVALID_CHARS =
/[#{Regexp.escape(WILDCARD_SINGLE + WILDCARD_MULTI + SEPARATOR)}]/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value) ⇒ Subject

Returns a new instance of Subject.



23
24
25
26
27
28
29
30
# File 'lib/jetstream_bridge/models/subject.rb', line 23

def initialize(value)
  @value = value.to_s
  @tokens = @value.split(SEPARATOR)
  validate!
  @value.freeze
  @tokens.freeze
  freeze
end

Instance Attribute Details

#tokensObject (readonly)

Returns the value of attribute tokens.



21
22
23
# File 'lib/jetstream_bridge/models/subject.rb', line 21

def tokens
  @tokens
end

#valueObject (readonly)

Returns the value of attribute value.



21
22
23
# File 'lib/jetstream_bridge/models/subject.rb', line 21

def value
  @value
end

Class Method Details

.destination(source:, app_name:) ⇒ Object



37
38
39
# File 'lib/jetstream_bridge/models/subject.rb', line 37

def self.destination(source:, app_name:)
  new("#{source}.sync.#{app_name}")
end

.dlq(app_name:) ⇒ Object



41
42
43
# File 'lib/jetstream_bridge/models/subject.rb', line 41

def self.dlq(app_name:)
  new("#{app_name}.sync.dlq")
end

.parse(string) ⇒ Subject

Parse a subject string into a Subject object with metadata

Parameters:

  • string (String)

    Subject string (e.g., “production.api.sync.worker”)

Returns:



49
50
51
# File 'lib/jetstream_bridge/models/subject.rb', line 49

def self.parse(string)
  new(string)
end

.source(app_name:, dest:) ⇒ Object

Factory methods



33
34
35
# File 'lib/jetstream_bridge/models/subject.rb', line 33

def self.source(app_name:, dest:)
  new("#{app_name}.sync.#{dest}")
end

.subject_matcherObject

Lazy-load SubjectMatcher to avoid circular dependency



130
131
132
133
# File 'lib/jetstream_bridge/models/subject.rb', line 130

def self.subject_matcher
  require_relative '../topology/subject_matcher' unless defined?(JetstreamBridge::SubjectMatcher)
  JetstreamBridge::SubjectMatcher
end

.validate_component!(value, name) ⇒ Object

Validate a component (env, app_name, etc.) for use in subjects

Raises:

  • (ArgumentError)


109
110
111
112
113
114
115
116
117
118
119
# File 'lib/jetstream_bridge/models/subject.rb', line 109

def self.validate_component!(value, name)
  str = value.to_s
  if str.match?(INVALID_CHARS)
    wildcards = "#{SEPARATOR}, #{WILDCARD_SINGLE}, #{WILDCARD_MULTI}"
    raise ArgumentError,
          "#{name} cannot contain NATS wildcards (#{wildcards}): #{value.inspect}"
  end
  raise ArgumentError, "#{name} cannot be empty" if str.strip.empty?

  true
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



98
99
100
# File 'lib/jetstream_bridge/models/subject.rb', line 98

def ==(other)
  @value == (other.is_a?(Subject) ? other.value : other.to_s)
end

#covered_by?(patterns) ⇒ Boolean

Check if covered by any pattern in a list

Returns:

  • (Boolean)


90
91
92
# File 'lib/jetstream_bridge/models/subject.rb', line 90

def covered_by?(patterns)
  SubjectMatcher.covered?(Array(patterns).map(&:to_s), @value)
end

#dest_appString?

Get destination application from subject

Returns:

  • (String, nil)

    Destination application



66
67
68
# File 'lib/jetstream_bridge/models/subject.rb', line 66

def dest_app
  @tokens[2]
end

#dlq?Boolean

Check if this is a DLQ subject

DLQ subjects follow the pattern: app.sync.dlq

Returns:

  • (Boolean)

    True if this is a DLQ subject



75
76
77
# File 'lib/jetstream_bridge/models/subject.rb', line 75

def dlq?
  @tokens.length == 3 && @tokens[1] == 'sync' && @tokens[2] == 'dlq'
end

#hashObject



104
105
106
# File 'lib/jetstream_bridge/models/subject.rb', line 104

def hash
  @value.hash
end

#matches?(pattern) ⇒ Boolean

Check if this subject matches a pattern

Returns:

  • (Boolean)


80
81
82
# File 'lib/jetstream_bridge/models/subject.rb', line 80

def matches?(pattern)
  SubjectMatcher.match?(pattern.to_s, @value)
end

#overlaps?(other) ⇒ Boolean

Check if this subject overlaps with another

Returns:

  • (Boolean)


85
86
87
# File 'lib/jetstream_bridge/models/subject.rb', line 85

def overlaps?(other)
  SubjectMatcher.overlap?(@value, other.to_s)
end

#source_appString?

Get source application from subject

For regular subjects: #source_app.sync.dest For DLQ subjects: app_name.sync.dlq

Returns:

  • (String, nil)

    Source application



59
60
61
# File 'lib/jetstream_bridge/models/subject.rb', line 59

def source_app
  @tokens[0]
end

#to_sObject



94
95
96
# File 'lib/jetstream_bridge/models/subject.rb', line 94

def to_s
  @value
end