ncco 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 83ee03788109717640e74920bb21cc2fbc07dd2d27ec8850bde4ad0002ae94a7
4
- data.tar.gz: 625ca24611c7640f53e7bdfbae9758a7a13f74df4f61248977d8f44363baa49b
3
+ metadata.gz: 8e630a1ba1f9ba6e1d6e7e77a733ae22e28e699d49bfdb594ab5a450817f2575
4
+ data.tar.gz: 1bfda713d0bf605db1565781aaff68971d4056f73bccb9b745f1a3b4c313452f
5
5
  SHA512:
6
- metadata.gz: e31008d689272f5ef55f293670fc160f7dd466288ea57bca7b38bc92cb39c0b69cb9259b2104154de5ba722ed938485566d0e9ee73d9d303b6ade9772ffe3969
7
- data.tar.gz: 17b0a41431b94d9840972d06ec6792f0325f66645162946e86a02eb1831ed899f0226ac9fb6715ec5914dac5348155b3630f2c3756ac0b247723cd439e53f651
6
+ metadata.gz: 4fa63d3d0c007fcd89b991494f3c0b3790e30d16852669a705f277500a86a82e9e1b4a96f00887af762fcfaa5d717ef998b6178e6881b389ad4b3180eabd2069
7
+ data.tar.gz: f6b5d7979e7d1d2321c289a45fe150c26937f5bc85dc43c4ed434a34097cfc1e5627676d4d3102d4aa4b34027ac5e042a067fdd2da9b8b7bf5b45f80c132c9e3
data/.rubocop.yml CHANGED
@@ -5,4 +5,4 @@ AllCops:
5
5
  TargetRubyVersion: 2.5
6
6
 
7
7
  RSpec/NestedGroups:
8
- Max: 5
8
+ Enabled: false
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ncco (0.1.1)
4
+ ncco (0.2.0)
5
5
  dry-validation (~> 0.12.2)
6
6
 
7
7
  GEM
@@ -92,4 +92,4 @@ DEPENDENCIES
92
92
  rubocop-rspec (~> 1.30.1)
93
93
 
94
94
  BUNDLED WITH
95
- 1.17.0.pre.2
95
+ 1.17.1
data/lib/ncco.rb CHANGED
@@ -10,6 +10,7 @@ require "ncco/schemas/input"
10
10
  require "ncco/schemas/record"
11
11
  require "ncco/schemas/stream"
12
12
  require "ncco/schemas/talk"
13
+ require "ncco/utils"
13
14
 
14
15
  module NCCO
15
16
  class InvalidActionError < StandardError; end
@@ -42,7 +43,7 @@ module NCCO
42
43
  # invalid and why.
43
44
  def self.build(actions)
44
45
  actions.
45
- map { |action| action.transform_keys(&:to_sym) }.
46
+ map { |action| Utils.deep_transform_keys_to_symbols(action) }.
46
47
  each_with_index { |action, index| validate_action!(action, index: index) }
47
48
 
48
49
  actions
@@ -65,9 +66,9 @@ module NCCO
65
66
  end
66
67
 
67
68
  result = schema.call(action)
68
- error_messages = get_error_messages(result)
69
+ error_message = get_error_message_from_result(result)
69
70
 
70
- raise_invalid_error(error_messages.join(", "), index: index) if error_messages.any?
71
+ raise_invalid_error(error_message, index: index) if error_message
71
72
  end
72
73
 
73
74
  # Raises an InvalidActionError, featuring the human-readable index of the action
@@ -78,7 +79,7 @@ module NCCO
78
79
  # object
79
80
  # @raise [InvalidActionError]
80
81
  def raise_invalid_error(error_message, index:)
81
- raise InvalidActionError, "The #{ordinalized_number(index + 1)} action is " \
82
+ raise InvalidActionError, "The #{ordinalised_number(index + 1)} action is " \
82
83
  "invalid: #{error_message}"
83
84
  end
84
85
 
@@ -89,8 +90,8 @@ module NCCO
89
90
  # @param number [Integer] the number to fetch the ordinal string for
90
91
  # @return [String] the number, followed by the ordinal string corresponding to the
91
92
  # number
92
- def ordinalized_number(number)
93
- "#{number}#{ordinalize(number)}"
93
+ def ordinalised_number(number)
94
+ "#{number}#{ordinal_string_for_number(number)}"
94
95
  end
95
96
 
96
97
  # Turns a number into an "ordinal string" used to denote its position in an ordered
@@ -98,7 +99,7 @@ module NCCO
98
99
  #
99
100
  # @param number [Integer] the number to fetch the ordinal string for
100
101
  # @return [String] the ordinal string corresponding to the number
101
- def ordinalize(number)
102
+ def ordinal_string_for_number(number)
102
103
  case number.digits.last
103
104
  when 0 then "th"
104
105
  when 1 then "st"
@@ -108,19 +109,26 @@ module NCCO
108
109
  end
109
110
  end
110
111
 
111
- # Gets the error messages from `Dry::Validation::Result`s in a consistent format. For
112
- # field-level errors, we get back a `Hash` mapping the attribute name to an array of
113
- # `String` error messages, whereas for unrecognised attributes (which is a bit of a
114
- # hack), we just get back an array. This handles either gracefully.
112
+ # Gets the error messages from a `Dry::Validation::Result`, if there is one, dealing
113
+ # with the slightly mad `Result` API
115
114
  #
116
- # @param result [Dry::Valiation::Result] the result from validating an action against
115
+ # @param result [Dry::Validation::Result] the result from validating an action against
117
116
  # a schema
118
- # @return [Array<String>] a list of error messages which is guaranteed to be an array
119
- def get_error_messages(result)
117
+ # @return [String, nil] an error message to display, if there was an error
118
+ def get_error_message_from_result(result)
120
119
  error_messages = result.messages(full: true)
121
- return error_messages if error_messages.is_a?(Array)
120
+ return if error_messages.none?
122
121
 
123
- error_messages.values.flatten
122
+ transform_error_message(error_messages)
123
+ end
124
+
125
+ def transform_error_message(error_messages)
126
+ case error_messages
127
+ when String then errror_messages
128
+ when Array then error_messages.first
129
+ when Hash then transform_error_message(error_messages.values.first)
130
+ else raise ArgumentError, "Unable to parse error message"
131
+ end
124
132
  end
125
133
  end
126
134
  end
@@ -7,4 +7,6 @@ en:
7
7
  phone_keypad_digits?: must be a series of one or more digits from a phone keypad, as a string
8
8
  e164?: must be a valid E.164-formatted phone number
9
9
  websocket_url?: must be a valid Websocket URL
10
- hash_with_string_keys_and_values?: must be a hash with String keys and values
10
+ sip_uri?: must be a valid SIP URI
11
+ hash_with_string_keys_and_values?: must be a hash with String keys and values
12
+ anything?: can be anything
@@ -19,6 +19,12 @@ module NCCO
19
19
  false
20
20
  end
21
21
 
22
+ SIP_URI_REGEX = /\Asip:/i.freeze
23
+
24
+ predicate(:sip_uri?) do |value|
25
+ value =~ SIP_URI_REGEX
26
+ end
27
+
22
28
  # TODO: Check what HTTP methods are supported by Nexmo - presumably not the full set?
23
29
  # <https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods>
24
30
  predicate(:supported_http_method?) do |value|
@@ -49,5 +55,9 @@ module NCCO
49
55
  value.keys.map(&:class).uniq == [String] &&
50
56
  value.values.map(&:class).uniq == [String]
51
57
  end
58
+
59
+ predicate(:anything?) do |_|
60
+ true
61
+ end
52
62
  end
53
63
  end
@@ -13,7 +13,11 @@ module NCCO
13
13
 
14
14
  # Used to validate that the input only includes keys that are defined in
15
15
  # the schema, implementing a slightly hacky "whitelisting" behaviour (which
16
- # for some reason isn't include in `dry-validations`!)
16
+ # for some reason isn't included in `dry-validations`!).
17
+ #
18
+ # In some places, we have to hack around this a bit by using our special
19
+ # `:anything?` predicate to whitelist an attribute which we have no rules
20
+ # to define about.
17
21
  def strict_keys?(input)
18
22
  (input.keys - rules.keys).empty?
19
23
  end
@@ -2,21 +2,55 @@
2
2
 
3
3
  module NCCO
4
4
  module Schemas
5
- # TODO: Validate these attributes depending on the specified endpoint type
6
- ConnectEndpoint = Dry::Validation.Schema(BaseSchema) do
7
- required(:type).value(included_in?: %w[phone websocket sip])
5
+ ConnectPhoneEndpoint = Dry::Validation.Schema(BaseSchema) do
6
+ required(:type).value(eql?: "phone")
8
7
 
9
- # Phone endpoint attributes
10
- optional(:number).value(:e164?)
8
+ required(:number).value(:e164?)
11
9
  optional(:onAnswer).value(:http_or_https_url?)
12
10
  optional(:dtmfAnswer).value(:phone_keypad_digits?)
11
+ end
12
+
13
+ ConnectSipEndpoint = Dry::Validation.Schema(BaseSchema) do
14
+ required(:type).value(eql?: "sip")
13
15
 
14
- # WebSocket endpoint attributes
15
- optional(:uri).value(:websocket_url?) # Also used for SIP endpoints
16
+ required(:uri).value(:sip_uri?)
17
+ end
18
+
19
+ ConnectWebSocketEndpoint = Dry::Validation.Schema(BaseSchema) do
20
+ required(:type).value(eql?: "websocket")
21
+
22
+ required(:uri).value(:websocket_url?)
16
23
  optional("content-type").value(eql?: "audio/l16;rate=16000")
17
24
  optional(:headers).value(:hash_with_string_keys_and_values?)
18
25
  end
19
26
 
27
+ ConnectEndpoint = Dry::Validation.Schema(BaseSchema) do
28
+ required(:type).value(included_in?: %w[phone websocket sip])
29
+
30
+ # How we validate the endpoint (i.e. what schema we should use) depends on the type
31
+ rule(phone_endpoint: [:type]) do |type|
32
+ type.eql?("phone") > schema(ConnectPhoneEndpoint)
33
+ end
34
+
35
+ rule(sip_endpoint: [:type]) do |type|
36
+ type.eql?("sip") > schema(ConnectSipEndpoint)
37
+ end
38
+
39
+ rule(websocket_endpoint: [:type]) do |type|
40
+ type.eql?("websocket") > schema(ConnectWebSocketEndpoint)
41
+ end
42
+
43
+ # We use this special `anything?` predicate to declare and whitelist the
44
+ # attribute without setting any rules for they must look like. The values
45
+ # are validated by our endpoint-specific schemas.
46
+ optional(:number).value(:anything?)
47
+ optional(:onAnswer).value(:anything?)
48
+ optional(:dtmfAnswer).value(:anything?)
49
+ optional(:uri).value(:anything?)
50
+ optional("content-type").value(:anything?)
51
+ optional(:headers).value(:anything?)
52
+ end
53
+
20
54
  Connect = Dry::Validation.Schema(BaseSchema) do
21
55
  required(:action).value(eql?: "connect")
22
56
  required(:endpoint).schema(ConnectEndpoint)
data/lib/ncco/utils.rb ADDED
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NCCO
4
+ module Utils
5
+ # Transforms the keys of a Hash with the provided block recursively, walking through
6
+ # nested hashes
7
+ #
8
+ # @param hash [Hash] the hash to transform
9
+ # @yieldparam the key to transform
10
+ # @return [Hash] the transformed hash, with the block recursively applied to its keys
11
+ def self.deep_transform_keys(hash, &block)
12
+ result = {}
13
+
14
+ hash.each do |key, value|
15
+ result[yield(key)] = if value.is_a?(Hash)
16
+ deep_transform_keys(value, &block)
17
+ else
18
+ value
19
+ end
20
+ end
21
+
22
+ result
23
+ end
24
+
25
+ # Transforms the keys of Hash into symbols recursively, walking through nested hashes
26
+ #
27
+ # @param hash [Hash] the hash to transform
28
+ def self.deep_transform_keys_to_symbols(hash)
29
+ deep_transform_keys(hash, &:to_sym)
30
+ end
31
+ end
32
+ end
data/lib/ncco/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NCCO
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ncco
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rogers
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-25 00:00:00.000000000 Z
11
+ date: 2018-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-validation
@@ -153,6 +153,7 @@ files:
153
153
  - lib/ncco/schemas/record.rb
154
154
  - lib/ncco/schemas/stream.rb
155
155
  - lib/ncco/schemas/talk.rb
156
+ - lib/ncco/utils.rb
156
157
  - lib/ncco/version.rb
157
158
  - ncco.gemspec
158
159
  homepage: https://github.com/timrogers/ncco-ruby