ncco 0.1.1 → 0.2.0

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.
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