message_event.rb

  1# frozen_string_literal: true
  2
  3require "json"
  4require "value_semantics"
  5
  6module MessageEvent
  7	module Emittable
  8		def emit(redis=LazyObject.new { REDIS }, stream: "messages")
  9			args = to_redis_fields.flat_map { |k, v| [k, v] }
 10			redis.xadd(stream, "*", *args).catch do |e|
 11				puts "WARN Failed to emit message event: #{e.message}"
 12			end
 13		end
 14	end
 15
 16	module ValidTel
 17		def self.===(value)
 18			return value.match?(/\A\+1\d{10}\z/) if value.is_a?(String)
 19			return value.to_s.match?(/\A\+1\d{10}\z/) if value.respond_to?(:to_s)
 20
 21			false
 22		end
 23	end
 24
 25	class MessageFields
 26		include ValueSemantics.for_attributes {
 27			event String
 28			timestamp Time, coerce: true
 29			from ValidTel
 30			to ArrayOf(ValidTel)
 31			message_id String
 32			body String
 33			media_urls ArrayOf(String), coerce: true
 34		}
 35
 36		def self.coerce_timestamp(value)
 37			value.is_a?(String) ? Time.parse(value) : value
 38		end
 39
 40		def self.coerce_media_urls(value)
 41			value.is_a?(String) ? JSON.parse(value) : value
 42		end
 43
 44		def to_redis_fields
 45			{
 46				"event" => event,
 47				"timestamp" => timestamp.iso8601,
 48				"from" => from,
 49				"to" => JSON.dump(to),
 50				"message_id" => message_id,
 51				"has_media" => media_urls.any?.to_s,
 52				"body" => body,
 53				"media_urls" => JSON.dump(media_urls)
 54			}
 55		end
 56	end
 57
 58	%w[In Out Thru].each { |n|
 59		const_set(n, Class.new(MessageFields) {
 60			include Emittable
 61
 62			const_set(n, lambda { |v|
 63				v == n.downcase
 64			})
 65
 66			define_method(:initialize) { |**a|
 67				super(**a.merge(event: n.downcase))
 68			}
 69		})
 70	}
 71
 72	class Delivered
 73		include Emittable
 74		Delivered = lambda { |v| v == "delivered" }
 75		include ValueSemantics.for_attributes {
 76			event Delivered
 77			timestamp Either(Time, String)
 78			message_id String
 79		}
 80
 81		def initialize(**a)
 82			super(**a.merge(event: "delivered"))
 83		end
 84
 85		def to_redis_fields = {
 86			"event" => event,
 87			"timestamp" => timestamp.iso8601,
 88			"message_id" => message_id
 89		}
 90	end
 91
 92	class Failed
 93		include Emittable
 94		Failed = lambda { |v| v == "failed" }
 95		include ValueSemantics.for_attributes {
 96			event Failed
 97			timestamp Either(Time, String)
 98			message_id String
 99			error_code String
100			error_description String
101		}
102
103		def initialize(**a)
104			super(**a.merge(event: "failed"))
105		end
106
107		def to_redis_fields = {
108			"event" => event,
109			"timestamp" => timestamp.is_a?(Time) ? timestamp.iso8601 : timestamp,
110			"message_id" => message_id,
111			"error_code" => error_code,
112			"error_description" => error_description
113		}
114	end
115end