1# frozen_string_literal: true
2
3require "json"
4
5module MessageEvent
6 module Emittable
7 def emit(redis, stream: "messages")
8 args = to_redis_fields.flat_map { |k, v| [k, v] }
9 redis.xadd(stream, "*", *args).catch do |e|
10 puts "WARN Failed to emit message event: #{e.message}"
11 end
12 end
13 end
14
15 module ValidTel
16 def self.===(value)
17 value.to_s.match?(/\A\+1\d{10}\z/)
18 end
19 end
20
21 class Base
22 include Emittable
23
24 def initialize(event:, timestamp: nil)
25 raise ArgumentError, "event must be a String" unless event.is_a?(String)
26
27 timestamp = Time.iso8601(timestamp) if timestamp.is_a?(String)
28 @event = event
29 @timestamp = timestamp
30 end
31
32 attr_reader :event, :timestamp
33
34 # We use to_redis_fields instead of to_h because we want to serialize values
35 # (e.g., Time -> ISO8601 string, arrays -> JSON). A plain to_h would return
36 # raw Ruby objects, which is less useful for parsing from other
37 # projects/languages.
38 def to_redis_fields
39 fields = {
40 "event" => @event,
41 "source" => "bwmsgsv2"
42 }
43 if @timestamp
44 fields["timestamp"] = @timestamp.is_a?(Time) ? @timestamp.iso8601 : @timestamp
45 end
46 fields
47 end
48 end
49
50 class Message < Base
51 def initialize(to:, from:, body:, owner:, bandwidth_id: nil, media_urls: [], **kwargs)
52 raise ArgumentError, "owner must be a valid US telephone number" unless ValidTel === owner
53 raise ArgumentError, "to must be an array" unless to.is_a?(Array)
54 raise ArgumentError, "body must be a String" unless body.is_a?(String)
55 raise ArgumentError, "media_urls must be an Array" unless media_urls.is_a?(Array)
56 if bandwidth_id && !bandwidth_id.is_a?(String)
57 raise ArgumentError, "bandwidth_id must be a String"
58 end
59
60 @from = from
61 @to = to
62 @body = body
63 @media_urls = media_urls
64 @owner = owner
65 @bandwidth_id = bandwidth_id if bandwidth_id
66 super(**kwargs)
67 end
68
69 attr_reader :from, :to, :body, :media_urls
70
71 def to_redis_fields
72 fields = super.merge(
73 "owner" => @owner,
74 "from" => @from,
75 "to" => JSON.dump(@to)
76 )
77 fields["body"] = @body unless @body.nil?
78 fields["bandwidth_id"] = @bandwidth_id
79 fields["media_urls"] = JSON.dump(@media_urls) unless @media_urls.empty?
80 fields
81 end
82 end
83
84 class In < Message
85 def initialize(**kwargs)
86 super(event: "in", **kwargs)
87 end
88
89 attr_reader :owner, :bandwidth_id
90 end
91
92 class Out < Message
93 def initialize(stanza_id:, **kwargs)
94 raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
95
96 @stanza_id = stanza_id
97 super(event: "out", **kwargs)
98 end
99
100 attr_reader :stanza_id, :bandwidth_id
101
102 def to_redis_fields
103 super.merge("stanza_id" => @stanza_id)
104 end
105 end
106
107 class Thru < Message
108 def initialize(stanza_id:, **kwargs)
109 raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
110
111 @stanza_id = stanza_id
112 super(event: "thru", **kwargs)
113 end
114
115 attr_reader :stanza_id
116
117 def to_redis_fields
118 super.merge("stanza_id" => @stanza_id)
119 end
120 end
121
122 class Delivered < Base
123 def initialize(stanza_id:, bandwidth_id:, **kwargs)
124 raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
125 raise ArgumentError, "bandwidth_id must be a String" unless bandwidth_id.is_a?(String)
126
127 @stanza_id = stanza_id
128 @bandwidth_id = bandwidth_id
129 super(event: "delivered", **kwargs)
130 end
131
132 attr_reader :stanza_id, :bandwidth_id
133
134 def to_redis_fields
135 super.merge(
136 "stanza_id" => @stanza_id,
137 "bandwidth_id" => @bandwidth_id
138 )
139 end
140 end
141
142 class Failed < Base
143 def initialize(stanza_id:, bandwidth_id:, error_code:, error_description:, **kwargs)
144 raise ArgumentError, "stanza_id must be a String" unless stanza_id.is_a?(String)
145 raise ArgumentError, "bandwidth_id must be a String" unless bandwidth_id.is_a?(String)
146 raise ArgumentError, "error_code must be a String" unless error_code.is_a?(String)
147 raise ArgumentError, "error_description must be a String" unless error_description.is_a?(String)
148
149 @stanza_id = stanza_id
150 @bandwidth_id = bandwidth_id
151 @error_code = error_code
152 @error_description = error_description
153 super(event: "failed", **kwargs)
154 end
155
156 attr_reader :stanza_id, :bandwidth_id, :error_code, :error_description
157
158 def to_redis_fields
159 super.merge(
160 "stanza_id" => @stanza_id,
161 "bandwidth_id" => @bandwidth_id,
162 "error_code" => @error_code,
163 "error_description" => @error_description
164 )
165 end
166 end
167end