ferryman 0.0.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ferryman.rb +141 -0
  3. metadata +100 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7d253c3ecf37cdcb02dd390586852f37feb235b9
4
+ data.tar.gz: c83d132003bfc24337c6a8e431bb94a36e75ce6f
5
+ SHA512:
6
+ metadata.gz: 814c75376361c13e49b314e1c35696d94a8eca071bb6285dea51ead10c65b72e8ea92f2f3f160bcc41ea3776c69e31f08eab5919dfb4c77e2f736b8921320bc5
7
+ data.tar.gz: cda89fb80b7b3e9912de04a9ae7633b1914ca574020a8ba629accf9c76de1fbe7d1a9347f3d2258ce75b74099ee2c6509a06945df99ec9459edf79bb07927d19
data/lib/ferryman.rb ADDED
@@ -0,0 +1,141 @@
1
+ require 'global_phone'
2
+
3
+ GlobalPhone.db_path = ENV.fetch('NUMBER_DB_FILE', 'phone_numbers.json')
4
+
5
+ module Ferryman
6
+ class ConfigError < StandardError
7
+ end
8
+
9
+ class DeliveryError < StandardError
10
+ end
11
+
12
+ class RoutingRule
13
+ attr_reader :strategy
14
+ attr_reader :providers
15
+
16
+ MATCH_ALL = lambda {|_| true}
17
+ STRATEGIES = {
18
+ 'first' => lambda {|providers| providers},
19
+ 'random' => lambda {|providers| providers.shuffle}
20
+ }
21
+
22
+ def initialize
23
+ @providers = []
24
+ @match_rules = []
25
+ @strategy = 'first'
26
+ end
27
+
28
+ def accept?(phone_number)
29
+ matched = true
30
+
31
+ @match_rules.each do |rule|
32
+ matched &&= rule.call(phone_number)
33
+ break unless matched
34
+ end
35
+
36
+ matched
37
+ end
38
+
39
+ def set_match(match)
40
+ raise ConfigError.new('match cannot be nil!') if match.nil?
41
+
42
+ if match == '*'
43
+ @match_rules << MATCH_ALL
44
+ elsif match.respond_to? 'has_key?'
45
+ if match.has_key?(:regions)
46
+ regions = Array.new(match[:regions])
47
+ @match_rules << lambda do |number|
48
+ parsed_number = GlobalPhone.parse(number)
49
+ regions.include? parsed_number.territory.name
50
+ end
51
+ end
52
+ else
53
+ raise ConfigError.new("Unexpected value for 'match'")
54
+ end
55
+
56
+ true
57
+ end
58
+
59
+ def providers
60
+ STRATEGIES[@strategy].call(@providers)
61
+ end
62
+
63
+ def add_provider(provider)
64
+ raise ConfigError.new('provider cannot be nil!') if provider.nil?
65
+ @providers << provider
66
+ end
67
+
68
+ def strategy=(strategy)
69
+ raise ConfigError.new('unknown strategy') unless STRATEGIES.has_key?(strategy)
70
+ @strategy = strategy
71
+ end
72
+
73
+ end
74
+
75
+ class Config
76
+ attr_reader :rules, :providers
77
+
78
+ def initialize
79
+ @providers = {}
80
+ @rules = []
81
+ @arity = nil
82
+ end
83
+
84
+ def add_provider(name, provider)
85
+ raise ConfigError.new("Provider #{name} already exists") if @providers.has_key?(name)
86
+ raise ConfigError.new("Provider must accept at least a phone number") if provider.arity < 1
87
+ raise ConfigError.new("Provider arguments does not match with existing") unless @arity.nil? || provider.arity == @arity
88
+
89
+ @arity = provider.arity
90
+ @providers[name] = provider
91
+
92
+ true
93
+ end
94
+
95
+ def set_routing_table(table)
96
+ table.each do |row|
97
+ raise ConfigError.new('providers cannot be nil!') if row[:providers].nil?
98
+
99
+ rule = RoutingRule.new
100
+ rule.set_match row[:match]
101
+ rule.strategy = row.fetch(:strategy, 'first')
102
+ row[:providers].each {|provider| rule.add_provider(provider)}
103
+ @rules << rule
104
+ end
105
+
106
+ true
107
+ end
108
+ end
109
+
110
+ class Dispatcher
111
+ attr_reader :config
112
+
113
+ def initialize
114
+ @config = Config.new
115
+ end
116
+
117
+ def deliver(phone_number, *args)
118
+ @config.rules.each do |rule|
119
+ next unless rule.accept?(phone_number)
120
+
121
+ rule.providers.each do |provider_name|
122
+ provider = @config.providers[provider_name]
123
+ params = [phone_number] + args
124
+ begin
125
+ return provider.call(*params)
126
+ rescue DeliveryError
127
+ next
128
+ end
129
+ end
130
+ end
131
+
132
+ nil
133
+ end
134
+ end
135
+
136
+ def Ferryman.create
137
+ dispatcher = Dispatcher.new
138
+ yield dispatcher.config
139
+ dispatcher
140
+ end
141
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ferryman
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jimmy Shen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: global_phone
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: global_phone_dbgen
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description: Ferryman allows you to de-couple routing logic and providers
70
+ email: thejimmyshen@gmail.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/ferryman.rb
76
+ homepage: https://github.com/jimmyshen/ferryman
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.2.2
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Rules-based routing of SMS messages to providers.
100
+ test_files: []