didww-v3 6.0.0 → 6.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aa466258f09ae99e535c21caeec5d0e274fe77288dd99adb4161e426e7eb45c
4
- data.tar.gz: 8c35e66178155231aa3d3fbe8fc9907f22f6aabaacfbad406b926adb093bbb92
3
+ metadata.gz: d834eeacdcc690b1b19e390a31730342681b1efe94d1e5f124d9b8c82d9ba5ca
4
+ data.tar.gz: 694e8a57d08a8cd646d715d424a1824aff726b3af40190db4749417de158c3ca
5
5
  SHA512:
6
- metadata.gz: 8cb5d98d5d0bc28a2c082fe730f0bb314e7e812d75d5326f502149d4c6b645fd876069113f9271ee3f9ac77ac052aa49573b8e1c2d8f79ff9fefb445a8df764e
7
- data.tar.gz: ab528d899547eac431d54968f117a8fb1426a8d520fd7ce6660d9730f0f7981ba32df7ef5e2ee00c87d619be7af2d0c0b1746c20b2691266bf7c61b7f8ed885f
6
+ metadata.gz: 0565f13367337588e62fadcc5464a3aaea268567b8b7af51e1c0f1b221027be039f242d1fae74e3a1b8980edff2add2cd394055b7e4c30a4bc453c0e39e15288
7
+ data.tar.gz: 44ceb7b1488be5a77b1d1b15a3ed45fd963a09e280941a5a14a7476fa331355ac81768974149aab51d44a38fe1d0f2717fb8fd38fd456704e6e9ccbe505d9879
data/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [6.1.1] - 2026-05-04
8
+ ### Fixed
9
+ - `SipConfiguration#enabled_sip_registration = true` now always emits `host: null` and `port: null` on the wire, not only when the local attribute hash already had them set. The previous conditional cascade (introduced in 6.1.0) silently no-op'd when re-enabling SIP registration on an existing trunk through a fresh `SipConfiguration` instance: the PATCH body carried only `enabled_sip_registration: true`, the server merged it with the trunk's persisted host, and rejected with 422 (`host must be blank when the SIP registration is enabled`). Verified end-to-end against the live sandbox via the new `examples/voice_in_trunk_sip_registration.rb`.
10
+
11
+ ### Added
12
+ - `examples/voice_in_trunk_sip_registration.rb` — end-to-end create / rename / disable / re-enable flow demonstrating every cascade rule against the sandbox API.
13
+
14
+ ## [6.1.0] - 2026-05-04
15
+ ### Added
16
+ - Complete the 2026-04-16 `SipConfiguration` attribute set that was missed during the 2026-04-16 rollout (and is not present in the public Postman collection — server form is the source of truth):
17
+ - `enabled_sip_registration` (bool) — enables SIP registration; the API generates `incoming_auth_*` credentials when set true and requires `port` to be blank.
18
+ - `use_did_in_ruri` (bool) — uses DID number in R-URI; requires SIP registration to be enabled.
19
+ - `network_protocol_priority` (string enum) — `force_ipv4` / `force_ipv6` / `any` / `prefer_ipv4` / `prefer_ipv6`. Constants exposed as `NETWORK_PROTOCOL_PRIORITY_*` and `NETWORK_PROTOCOL_PRIORITIES`.
20
+ - `diversion_inject_mode` (string enum) — `none` / `did_number`. Constants exposed as `DIVERSION_INJECT_MODE_*` and `DIVERSION_INJECT_MODES`.
21
+ - `cnam_lookup` (bool) — enables CNAM resolution for inbound calls.
22
+ - `SipConfiguration#incoming_auth_username` and `#incoming_auth_password` — read-only server-generated SIP authentication credentials returned on `voice_in_trunks` when `enabled_sip_registration` is true.
23
+ - `ComplexObject::Base.property` accepts `read_only: true`. Read-only attributes are still parsed from server responses but are stripped from the JSON:API write payload, so round-tripping a loaded configuration through PATCH no longer triggers `400 Param not allowed`.
24
+ - `ComplexObject::Base.property` accepts `sensitive: true`. The default `#inspect` representation redacts those values with `[FILTERED]` so credentials never leak through default logging / REPL / unhandled exception output. Marked as sensitive: `SipConfiguration#auth_password`, `SipConfiguration#incoming_auth_username/password`, and `AuthenticationMethod::CredentialsAndIp#username/password`.
25
+ - `SipConfiguration` auto-cascades dependent fields whose constraints are server-enforced when set via property setters: `enabled_sip_registration = true` clears `host`/`port` (when previously set), `enabled_sip_registration = false` forces `use_did_in_ruri = false`, and assigning a non-blank `host` flips both flags. Constructor / `[]=` assignments bypass the cascade so server responses deserialize as-is.
26
+
7
27
  ## [6.0.0] - 2026-04-24
8
28
  Support for DIDWW API version **2026-04-16**. The gem now sends `X-DIDWW-API-Version: 2026-04-16` with every request by default. Users staying on API `2022-05-10` should pin to the [`2022-05-10`](https://github.com/didww/didww-v3-ruby/tree/2022-05-10) branch (5.x).
9
29
 
data/README.md CHANGED
@@ -205,6 +205,46 @@ trunk = DIDWW::Client.voice_in_trunks.new(
205
205
  trunk.save
206
206
  ```
207
207
 
208
+ #### SIP Registration (2026-04-16)
209
+
210
+ A Voice In Trunk can also be configured to authenticate inbound calls
211
+ via SIP registration credentials generated by DIDWW. The SDK
212
+ auto-cascades the dependent fields the server requires:
213
+
214
+ * setting `enabled_sip_registration = true` clears any previously-set
215
+ `host` / `port` (the server rejects them with 422 otherwise);
216
+ * setting `host` to a non-blank value flips `enabled_sip_registration`
217
+ back to `false` and forces `use_did_in_ruri = false` so the server
218
+ accepts the disable PATCH.
219
+
220
+ DIDWW generates `incoming_auth_username` and `incoming_auth_password`
221
+ and surfaces them in the response when SIP registration is enabled.
222
+
223
+ ```ruby
224
+ trunk = DIDWW::Client.voice_in_trunks.new(
225
+ name: 'Office (registered)',
226
+ configuration: DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
227
+ c.enabled_sip_registration = true
228
+ c.use_did_in_ruri = true
229
+ c.cnam_lookup = true
230
+ end
231
+ )
232
+ trunk.save
233
+ # trunk.configuration.incoming_auth_username -- server-generated
234
+ # trunk.configuration.incoming_auth_password -- server-generated
235
+ ```
236
+
237
+ To disable SIP registration on an existing trunk, just set `host` —
238
+ the cascade flips `enabled_sip_registration` to `false` and
239
+ `use_did_in_ruri` to `false` automatically:
240
+
241
+ ```ruby
242
+ trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
243
+ c.host = 'sip.example.com'
244
+ end
245
+ trunk.save
246
+ ```
247
+
208
248
  ### Voice Out Trunks
209
249
 
210
250
  Voice Out Trunks use a polymorphic `authentication_method` (2026-04-16). Three types are supported:
data/didww-v3.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
  spec.email = ['alex.k@didww.com']
12
12
 
13
13
  spec.summary = %q{Ruby client for DIDWW API v3}
14
- spec.description = %q{Ruby client for DIDWW API v3}
14
+ spec.description = %q{Ruby client for the DIDWW JSON:API v3, covering DID inventory, voice in/out trunks, regulatory documents, exports, and emergency calling services.}
15
15
  spec.homepage = 'https://github.com/didww/didww-v3-ruby'
16
16
  spec.license = 'MIT'
17
17
 
@@ -24,11 +24,16 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = '>= 3.3'
26
26
 
27
- spec.add_dependency 'activesupport'
28
- spec.add_dependency 'faraday'
29
- spec.add_dependency 'faraday-multipart'
30
- spec.add_dependency 'json_api_client', '1.23.0'
31
- spec.add_dependency 'http'
32
- spec.add_dependency 'down'
33
- spec.add_dependency 'openssl-oaep'
27
+ # Lower bounds match the oldest version verified by CI; upper bounds
28
+ # cap on the next known-incompatible major so consumers get a clear
29
+ # constraint failure rather than a runtime surprise. The CI matrix in
30
+ # .github/workflows/tests.yml exercises activesupport ~> 7.2 / 8.0 /
31
+ # 8.1 — declaring a wider lower bound than that would be aspirational.
32
+ spec.add_dependency 'activesupport', '>= 7.2', '< 9'
33
+ spec.add_dependency 'faraday', '~> 2.0'
34
+ spec.add_dependency 'faraday-multipart', '~> 1.0'
35
+ spec.add_dependency 'json_api_client', '1.23.0'
36
+ spec.add_dependency 'http', '~> 5.0'
37
+ spec.add_dependency 'down', '~> 5.0'
38
+ spec.add_dependency 'openssl-oaep', '~> 0.1'
34
39
  end
data/examples/README.md CHANGED
@@ -41,6 +41,7 @@ DIDWW_API_KEY=your_api_key ruby examples/orders_nanpa.rb
41
41
  | Script | Description |
42
42
  |---|---|
43
43
  | [`voice_in_trunks.rb`](voice_in_trunks.rb) | Lists voice in trunks and their configurations. |
44
+ | [`voice_in_trunk_sip_registration.rb`](voice_in_trunk_sip_registration.rb) | End-to-end SIP registration flow: create with `enabled_sip_registration: true`, rename, disable by setting `host`, re-enable by toggling the flag. The SDK keeps the dependent fields (`host`, `port`, `use_did_in_ruri`) aligned with the server's validation rules automatically. |
44
45
  | [`voice_in_trunk_groups.rb`](voice_in_trunk_groups.rb) | CRUD for trunk groups with trunk relationships. |
45
46
 
46
47
  ### Voice Out (Outbound)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # End-to-end SIP registration flow on /voice_in_trunks (API 2026-04-16):
4
+ # create with sip_registration enabled → rename → disable by setting
5
+ # `host` → re-enable by toggling the flag. The SDK keeps the
6
+ # dependent fields (`host`, `port`, `use_did_in_ruri`) aligned with
7
+ # the server's validation rules automatically. The sandbox trunk is
8
+ # left in place after the script completes so it can be inspected
9
+ # afterwards.
10
+ #
11
+ # Usage: DIDWW_API_KEY=your_sandbox_key ruby examples/voice_in_trunk_sip_registration.rb
12
+
13
+ require 'bundler/setup'
14
+ require 'didww'
15
+
16
+ DIDWW::Client.configure do |client|
17
+ client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
18
+ client.api_mode = :sandbox
19
+ end
20
+
21
+ puts "=== Ruby SDK v#{DIDWW::VERSION} — SIP registration flow ==="
22
+
23
+ # 1) Create a SIP trunk with sip_registration enabled. The server
24
+ # generates `incoming_auth_*` credentials and returns them in the
25
+ # response — they are read-only and stripped from any subsequent
26
+ # write payload by the SDK.
27
+ puts "\n[1/4] Create with sip_registration enabled..."
28
+ trunk = DIDWW::Client.voice_in_trunks.new(
29
+ name: "sip-registration-example-#{Time.now.to_i}",
30
+ priority: 1,
31
+ weight: 100,
32
+ cli_format: 'e164',
33
+ ringing_timeout: 30,
34
+ configuration: DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
35
+ c.enabled_sip_registration = true
36
+ c.use_did_in_ruri = true
37
+ c.cnam_lookup = false
38
+ c.codec_ids = [9, 7]
39
+ c.transport_protocol_id = 1
40
+ end
41
+ )
42
+ abort "create failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
43
+ puts " id=#{trunk.id}"
44
+ puts " incoming_auth_username=#{trunk.configuration.incoming_auth_username.inspect}"
45
+ puts " incoming_auth_password=#{trunk.configuration.incoming_auth_password.inspect}"
46
+ trunk_id = trunk.id
47
+
48
+ # 2) Rename — a single-field PATCH that doesn't touch SIP-registration state.
49
+ puts "\n[2/4] Rename trunk..."
50
+ trunk.name = "sip-registration-renamed-#{Time.now.to_i}"
51
+ abort "rename failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
52
+ puts " name=#{trunk.name}"
53
+
54
+ # 3) Disable sip_registration by setting `host`. The SDK flips
55
+ # `enabled_sip_registration` and `use_did_in_ruri` to false in the
56
+ # same PATCH so the server-side validators accept the change.
57
+ puts "\n[3/4] Disable by setting host..."
58
+ trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
59
+ c.host = '203.0.113.10'
60
+ end
61
+ abort "disable failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
62
+ fresh = DIDWW::Client.voice_in_trunks.find(trunk_id).first
63
+ puts " enabled_sip_registration=#{fresh.configuration.enabled_sip_registration.inspect}"
64
+ puts " use_did_in_ruri=#{fresh.configuration.use_did_in_ruri.inspect}"
65
+ puts " host=#{fresh.configuration.host.inspect}"
66
+ puts " incoming_auth_username=#{fresh.configuration.incoming_auth_username.inspect}"
67
+
68
+ # 4) Re-enable sip_registration. Setting `enabled_sip_registration = true`
69
+ # sends host=nil / port=nil on the wire so the server (which still has
70
+ # the host from step 3) is told to clear them.
71
+ puts "\n[4/4] Re-enable by toggling enabled_sip_registration..."
72
+ trunk = DIDWW::Client.voice_in_trunks.find(trunk_id).first
73
+ trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
74
+ c.enabled_sip_registration = true
75
+ c.use_did_in_ruri = true
76
+ end
77
+ abort "re-enable failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
78
+ fresh = DIDWW::Client.voice_in_trunks.find(trunk_id).first
79
+ puts " enabled_sip_registration=#{fresh.configuration.enabled_sip_registration.inspect}"
80
+ puts " host=#{fresh.configuration.host.inspect}"
81
+ puts " incoming_auth_username=#{fresh.configuration.incoming_auth_username.inspect}"
82
+
83
+ puts "\n=== PASS — trunk #{trunk_id} left in sandbox ==="
@@ -41,3 +41,68 @@ if !trunks.empty?
41
41
  puts " Description: #{specific_trunk.description}"
42
42
  puts " Created at: #{specific_trunk.created_at}"
43
43
  end
44
+
45
+ # 2026-04-16 — surface the API 2026-04-16 SIP-registration-related attributes when
46
+ # the trunk uses a SIP configuration. `incoming_auth_username` /
47
+ # `incoming_auth_password` are server-generated and only present when
48
+ # `enabled_sip_registration` is true.
49
+ sip_trunk = trunks.find { |t| t.configuration.is_a?(DIDWW::ComplexObject::SipConfiguration) }
50
+ if sip_trunk
51
+ puts "\n=== SIP Configuration (2026-04-16 attributes) ==="
52
+ config = sip_trunk.configuration
53
+ puts "Trunk: #{sip_trunk.name}"
54
+ puts " enabled_sip_registration: #{config.enabled_sip_registration.inspect}"
55
+ puts " use_did_in_ruri: #{config.use_did_in_ruri.inspect}"
56
+ puts " network_protocol_priority: #{config.network_protocol_priority.inspect}"
57
+ puts " diversion_relay_policy: #{config.diversion_relay_policy.inspect}"
58
+ puts " diversion_inject_mode: #{config.diversion_inject_mode.inspect}"
59
+ puts " cnam_lookup: #{config.cnam_lookup.inspect}"
60
+ if config.enabled_sip_registration
61
+ puts " incoming_auth_username: #{config.incoming_auth_username.inspect}"
62
+ puts " incoming_auth_password: #{config.incoming_auth_password.inspect}"
63
+ else
64
+ puts ' (incoming_auth credentials only appear when SIP registration is enabled)'
65
+ end
66
+ end
67
+
68
+ # Example: create a new SIP trunk with SIP registration enabled. The server
69
+ # generates `incoming_auth_username` / `incoming_auth_password` and returns
70
+ # them in the response. They are READ-ONLY — sending them on POST/PATCH
71
+ # returns 400 Param not allowed; the SDK strips them from write payloads.
72
+ #
73
+ # Server-side rules to keep in mind:
74
+ # * When `enabled_sip_registration: true`, `host` and `port` MUST be left
75
+ # blank (the server will register inbound and pick the host itself).
76
+ # * When DISABLING SIP registration on an existing trunk, you must
77
+ # simultaneously set `host` to a non-blank value and `use_did_in_ruri`
78
+ # to `false` in the same PATCH — otherwise the server returns 422.
79
+ #
80
+ # Uncomment to actually create a trunk in your sandbox account:
81
+ #
82
+ # new_trunk = DIDWW::Resource::VoiceInTrunk.new(
83
+ # name: "SIP-registration trunk #{Time.now.to_i}",
84
+ # priority: 1,
85
+ # weight: 100,
86
+ # cli_format: 'e164',
87
+ # ringing_timeout: 30,
88
+ # configuration: DIDWW::ComplexObject::SipConfiguration.new(
89
+ # # NOTE: do NOT set `host` here — the server requires it to be blank
90
+ # # when enabled_sip_registration is true.
91
+ # enabled_sip_registration: true,
92
+ # use_did_in_ruri: true,
93
+ # cnam_lookup: false,
94
+ # diversion_relay_policy: 'as_is',
95
+ # diversion_inject_mode: 'did_number',
96
+ # network_protocol_priority: 'prefer_ipv4',
97
+ # codec_ids: [9, 7],
98
+ # transport_protocol_id: 1
99
+ # )
100
+ # )
101
+ # created = new_trunk.save
102
+ # if created
103
+ # puts "Created trunk #{new_trunk.id}"
104
+ # puts " incoming_auth_username: #{new_trunk.configuration.incoming_auth_username}"
105
+ # puts " incoming_auth_password: #{new_trunk.configuration.incoming_auth_password}"
106
+ # else
107
+ # puts "Errors: #{new_trunk.errors.full_messages}"
108
+ # end
@@ -12,8 +12,8 @@ module DIDWW
12
12
  # only in responses; they cannot be set from the client on create.
13
13
  property :allowed_sip_ips, type: :strings
14
14
  property :tech_prefix, type: :string
15
- property :username, type: :string
16
- property :password, type: :string
15
+ property :username, type: :string, sensitive: true
16
+ property :password, type: :string, sensitive: true
17
17
  end
18
18
  end
19
19
  end
@@ -13,6 +13,8 @@ module DIDWW
13
13
 
14
14
  def property(name, options = {})
15
15
  schema.add(name, options)
16
+ read_only_attributes << name.to_s if options[:read_only]
17
+ sensitive_attributes << name.to_s if options[:sensitive]
16
18
  define_method(name.to_sym) { self[name] }
17
19
  define_method("#{name}=".to_sym) { |val| self[name] = val }
18
20
  end
@@ -21,6 +23,22 @@ module DIDWW
21
23
  @schema ||= JsonApiClient::Schema.new
22
24
  end
23
25
 
26
+ # Names of attributes that the server returns but does not accept on
27
+ # write. Excluded from `as_json` so that round-tripping a resource
28
+ # through PATCH does not echo them back and trigger 400 Param not
29
+ # allowed.
30
+ def read_only_attributes
31
+ @read_only_attributes ||= []
32
+ end
33
+
34
+ # Names of attributes whose values are credentials/secrets. The wire
35
+ # format is unchanged — `as_json` still emits the real values — but
36
+ # `#inspect` redacts them so default logging / error reports / REPL
37
+ # echoes never leak credentials downstream.
38
+ def sensitive_attributes
39
+ @sensitive_attributes ||= []
40
+ end
41
+
24
42
  # Type casting for JsonApiClient parser/setters
25
43
  def cast(value, default)
26
44
  case value
@@ -77,14 +95,29 @@ module DIDWW
77
95
  end
78
96
 
79
97
  # When we represent this resource for serialization (create/update), we do so
80
- # with this implementation
98
+ # with this implementation. Read-only attributes are excluded — the server
99
+ # rejects them with 400 Param not allowed.
81
100
  def as_json(*)
101
+ excluded = self.class.read_only_attributes
82
102
  { type: type }.with_indifferent_access.tap do |h|
83
- h[:attributes] = attributes.as_json
103
+ h[:attributes] = attributes.as_json.reject { |k, _| excluded.include?(k.to_s) }
84
104
  end
85
105
  end
86
106
 
87
107
  def as_json_api(*); as_json end
108
+
109
+ # Redacts sensitive attribute values so credentials never leak into
110
+ # default logs / REPL output / error reports. The on-the-wire payload
111
+ # is unaffected (see `#as_json`).
112
+ FILTERED = '[FILTERED]'.freeze
113
+ def inspect
114
+ sensitive = self.class.sensitive_attributes
115
+ formatted = attributes.map do |k, v|
116
+ display = sensitive.include?(k.to_s) && !v.nil? ? FILTERED : v
117
+ "#{k}=#{display.inspect}"
118
+ end
119
+ "#<#{self.class.name} #{formatted.join(' ')}>"
120
+ end
88
121
  end
89
122
  end
90
123
  end
@@ -18,7 +18,7 @@ module DIDWW
18
18
  # Nullable: No
19
19
  # Description: Optional authorization user for the SIP server
20
20
 
21
- property :auth_password, type: :string
21
+ property :auth_password, type: :string, sensitive: true
22
22
  # Type: String
23
23
  # Nullable: No
24
24
  # Description: Optional authorization password for the SIP server
@@ -180,6 +180,54 @@ module DIDWW
180
180
  # See DIVERSION_RELAY_POLICIES for available values.
181
181
  # In API v3.4 this attribute was named `diversion_relay_mode`.
182
182
 
183
+ property :diversion_inject_mode, type: :string
184
+ # Type: String
185
+ # Nullable: No
186
+ # Description: Diversion header injection mode. See
187
+ # DIVERSION_INJECT_MODES for available values. (API 2026-04-16)
188
+
189
+ property :network_protocol_priority, type: :string
190
+ # Type: String
191
+ # Nullable: No
192
+ # Description: SIP network protocol priority. See
193
+ # NETWORK_PROTOCOL_PRIORITIES for available values. (API 2026-04-16)
194
+
195
+ property :enabled_sip_registration, type: :boolean
196
+ # Type: Boolean
197
+ # Nullable: No
198
+ # Description: Enables SIP registration. When true the API generates
199
+ # `incoming_auth_username` / `incoming_auth_password`; the trunk's
200
+ # `host` and `port` must be left blank. When disabling sip
201
+ # registration on an existing trunk, the same PATCH must also set
202
+ # `host` to a non-blank value and `use_did_in_ruri` to false, or
203
+ # the server returns 422. (API 2026-04-16)
204
+
205
+ property :use_did_in_ruri, type: :boolean
206
+ # Type: Boolean
207
+ # Nullable: No
208
+ # Description: When true, the trunk's R-URI uses the DID number.
209
+ # Requires `enabled_sip_registration` to be true. (API 2026-04-16)
210
+
211
+ property :cnam_lookup, type: :boolean
212
+ # Type: Boolean
213
+ # Nullable: No
214
+ # Description: Enables CNAM resolution for inbound calls on this
215
+ # trunk. (API 2026-04-16)
216
+
217
+ property :incoming_auth_username, type: :string, read_only: true, sensitive: true
218
+ # Type: String
219
+ # Nullable: Yes
220
+ # Description: Server-generated SIP authentication username, returned in
221
+ # responses when `enabled_sip_registration` is true. Read-only; the API
222
+ # rejects any write attempt with 400 Param not allowed. (API 2026-04-16)
223
+
224
+ property :incoming_auth_password, type: :string, read_only: true, sensitive: true
225
+ # Type: String
226
+ # Nullable: Yes
227
+ # Description: Server-generated SIP authentication password, returned in
228
+ # responses when `enabled_sip_registration` is true. Read-only; the API
229
+ # rejects any write attempt with 400 Param not allowed. (API 2026-04-16)
230
+
183
231
  DIVERSION_RELAY_POLICY_NONE = 'none'
184
232
  DIVERSION_RELAY_POLICY_AS_IS = 'as_is'
185
233
  DIVERSION_RELAY_POLICY_SIP = 'sip'
@@ -192,6 +240,28 @@ module DIDWW
192
240
  DIVERSION_RELAY_POLICY_TEL
193
241
  ].freeze
194
242
 
243
+ DIVERSION_INJECT_MODE_NONE = 'none'
244
+ DIVERSION_INJECT_MODE_DID_NUMBER = 'did_number'
245
+
246
+ DIVERSION_INJECT_MODES = [
247
+ DIVERSION_INJECT_MODE_NONE,
248
+ DIVERSION_INJECT_MODE_DID_NUMBER
249
+ ].freeze
250
+
251
+ NETWORK_PROTOCOL_PRIORITY_FORCE_IPV4 = 'force_ipv4'
252
+ NETWORK_PROTOCOL_PRIORITY_FORCE_IPV6 = 'force_ipv6'
253
+ NETWORK_PROTOCOL_PRIORITY_ANY = 'any'
254
+ NETWORK_PROTOCOL_PRIORITY_PREFER_IPV4 = 'prefer_ipv4'
255
+ NETWORK_PROTOCOL_PRIORITY_PREFER_IPV6 = 'prefer_ipv6'
256
+
257
+ NETWORK_PROTOCOL_PRIORITIES = [
258
+ NETWORK_PROTOCOL_PRIORITY_FORCE_IPV4,
259
+ NETWORK_PROTOCOL_PRIORITY_FORCE_IPV6,
260
+ NETWORK_PROTOCOL_PRIORITY_ANY,
261
+ NETWORK_PROTOCOL_PRIORITY_PREFER_IPV4,
262
+ NETWORK_PROTOCOL_PRIORITY_PREFER_IPV6
263
+ ].freeze
264
+
195
265
  MEDIA_ENCRYPTION_MODES = [
196
266
  'disabled',
197
267
  'srtp_sdes',
@@ -257,6 +327,60 @@ module DIDWW
257
327
  def transport_protocol
258
328
  TRANSPORT_PROTOCOLS[transport_protocol_id]
259
329
  end
330
+
331
+ # Auto-cascade for server-enforced field dependencies (2026-04-16).
332
+ #
333
+ # The server validates these combinations and rejects mismatches with
334
+ # 422; the SDK fixes them up at assignment time so user code never has
335
+ # to track the full server-side rule set. Future server-required
336
+ # cascades are added here without the caller needing to know.
337
+ #
338
+ # Rules:
339
+ # * `enabled_sip_registration = true` => host = nil, port = nil
340
+ # (server requires both blank when registration is enabled)
341
+ # * `enabled_sip_registration = false` => use_did_in_ruri = false
342
+ # (server requires use_did_in_ruri disabled when sip_registration is)
343
+ # * `host = <non-nil>` => enabled_sip_registration = false,
344
+ # use_did_in_ruri = false (host requires sip_registration disabled,
345
+ # which in turn requires use_did_in_ruri disabled)
346
+ #
347
+ # Constructor-time `[]=` assignments bypass these setters intentionally
348
+ # so that responses returned from the server (e.g. a fixture with
349
+ # `enabled_sip_registration: true` AND `host: nil`) deserialize as-is.
350
+ def enabled_sip_registration=(val)
351
+ case val
352
+ when true
353
+ # Always emit host: null and port: null on the wire when
354
+ # enabling sip_registration. The server requires both blank
355
+ # (returns 422 otherwise) AND a PATCH against an existing
356
+ # trunk that already has host/port set on the server side
357
+ # MUST explicitly nullify them — the SDK's local attributes
358
+ # hash starts empty, so there's nothing to "preserve".
359
+ self[:host] = nil
360
+ self[:port] = nil
361
+ when false
362
+ # Server requires use_did_in_ruri = false whenever sip_registration
363
+ # is disabled. Always emit it on the wire so the server's check
364
+ # passes regardless of the prior state of the field.
365
+ self[:use_did_in_ruri] = false
366
+ else
367
+ # `nil` (or any non-bool) — caller is unsetting the field. No
368
+ # cascade: dependent fields stay as the caller left them.
369
+ end
370
+ self[:enabled_sip_registration] = val
371
+ end
372
+
373
+ def host=(val)
374
+ unless val.nil?
375
+ # Setting host implies sip_registration is disabled (server-side
376
+ # validation), which in turn implies use_did_in_ruri = false.
377
+ # Always emit both on the wire so the server's checks pass
378
+ # without the caller having to know the rule set.
379
+ self[:enabled_sip_registration] = false
380
+ self[:use_did_in_ruri] = false
381
+ end
382
+ self[:host] = val
383
+ end
260
384
  end
261
385
  end
262
386
  end
data/lib/didww/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module DIDWW
3
- VERSION = '6.0.0'.freeze
3
+ VERSION = '6.1.1'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: didww-v3
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.0.0
4
+ version: 6.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Korobeinikov
@@ -15,42 +15,48 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: '7.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9'
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
22
25
  requirements:
23
26
  - - ">="
24
27
  - !ruby/object:Gem::Version
25
- version: '0'
28
+ version: '7.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9'
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: faraday
28
34
  requirement: !ruby/object:Gem::Requirement
29
35
  requirements:
30
- - - ">="
36
+ - - "~>"
31
37
  - !ruby/object:Gem::Version
32
- version: '0'
38
+ version: '2.0'
33
39
  type: :runtime
34
40
  prerelease: false
35
41
  version_requirements: !ruby/object:Gem::Requirement
36
42
  requirements:
37
- - - ">="
43
+ - - "~>"
38
44
  - !ruby/object:Gem::Version
39
- version: '0'
45
+ version: '2.0'
40
46
  - !ruby/object:Gem::Dependency
41
47
  name: faraday-multipart
42
48
  requirement: !ruby/object:Gem::Requirement
43
49
  requirements:
44
- - - ">="
50
+ - - "~>"
45
51
  - !ruby/object:Gem::Version
46
- version: '0'
52
+ version: '1.0'
47
53
  type: :runtime
48
54
  prerelease: false
49
55
  version_requirements: !ruby/object:Gem::Requirement
50
56
  requirements:
51
- - - ">="
57
+ - - "~>"
52
58
  - !ruby/object:Gem::Version
53
- version: '0'
59
+ version: '1.0'
54
60
  - !ruby/object:Gem::Dependency
55
61
  name: json_api_client
56
62
  requirement: !ruby/object:Gem::Requirement
@@ -69,45 +75,46 @@ dependencies:
69
75
  name: http
70
76
  requirement: !ruby/object:Gem::Requirement
71
77
  requirements:
72
- - - ">="
78
+ - - "~>"
73
79
  - !ruby/object:Gem::Version
74
- version: '0'
80
+ version: '5.0'
75
81
  type: :runtime
76
82
  prerelease: false
77
83
  version_requirements: !ruby/object:Gem::Requirement
78
84
  requirements:
79
- - - ">="
85
+ - - "~>"
80
86
  - !ruby/object:Gem::Version
81
- version: '0'
87
+ version: '5.0'
82
88
  - !ruby/object:Gem::Dependency
83
89
  name: down
84
90
  requirement: !ruby/object:Gem::Requirement
85
91
  requirements:
86
- - - ">="
92
+ - - "~>"
87
93
  - !ruby/object:Gem::Version
88
- version: '0'
94
+ version: '5.0'
89
95
  type: :runtime
90
96
  prerelease: false
91
97
  version_requirements: !ruby/object:Gem::Requirement
92
98
  requirements:
93
- - - ">="
99
+ - - "~>"
94
100
  - !ruby/object:Gem::Version
95
- version: '0'
101
+ version: '5.0'
96
102
  - !ruby/object:Gem::Dependency
97
103
  name: openssl-oaep
98
104
  requirement: !ruby/object:Gem::Requirement
99
105
  requirements:
100
- - - ">="
106
+ - - "~>"
101
107
  - !ruby/object:Gem::Version
102
- version: '0'
108
+ version: '0.1'
103
109
  type: :runtime
104
110
  prerelease: false
105
111
  version_requirements: !ruby/object:Gem::Requirement
106
112
  requirements:
107
- - - ">="
113
+ - - "~>"
108
114
  - !ruby/object:Gem::Version
109
- version: '0'
110
- description: Ruby client for DIDWW API v3
115
+ version: '0.1'
116
+ description: Ruby client for the DIDWW JSON:API v3, covering DID inventory, voice
117
+ in/out trunks, regulatory documents, exports, and emergency calling services.
111
118
  email:
112
119
  - alex.k@didww.com
113
120
  executables: []
@@ -158,6 +165,7 @@ files:
158
165
  - examples/regions.rb
159
166
  - examples/shared_capacity_groups.rb
160
167
  - examples/voice_in_trunk_groups.rb
168
+ - examples/voice_in_trunk_sip_registration.rb
161
169
  - examples/voice_in_trunks.rb
162
170
  - examples/voice_out_trunks.rb
163
171
  - lib/didww.rb