Skip to content

Commit 9e2bab9

Browse files
authored
Deprecate ambiguous IPv4 and IPv6 target APIs (#117)
Motivation: The API for creating IPv4/IPv6 resolvable targets is a bit ambiguous: it's quite easy to assume they will only resolve IPv4 or IPv6 addresses. Instead they expect to be provided with resolved IPv4 or IPv6 addresses. Modifications: - Deprecate these APIs and replace them with more obvious spellings - Add a debug-only check to validate that the target is a valid IPv4/IPv6 address Result: - Harder to make mistakes - Easier to diagnose mistakes
1 parent ddeefaa commit 9e2bab9

File tree

8 files changed

+112
-23
lines changed

8 files changed

+112
-23
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ let dependencies: [Package.Dependency] = [
7171

7272
// This adds some build settings which allow us to map "@available(gRPCSwiftNIOTransport 2.x, *)" to
7373
// the appropriate OS platforms.
74-
let nextMinorVersion = 0
74+
let nextMinorVersion = 1
7575
let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in
7676
let name = "gRPCSwiftNIOTransport"
7777
let version = "2.\(minor)"

Sources/GRPCNIOTransportCore/Client/Resolver/NameResolver+IPv4.swift

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
internal import GRPCCore
18+
internal import NIOCore
1819

1920
@available(gRPCSwiftNIOTransport 2.0, *)
2021
extension ResolvableTargets {
@@ -29,6 +30,24 @@ extension ResolvableTargets {
2930
/// Create a new IPv4 target.
3031
/// - Parameter addresses: The IPv4 addresses.
3132
public init(addresses: [SocketAddress.IPv4]) {
33+
debugOnly {
34+
for address in addresses {
35+
do {
36+
switch try? NIOCore.SocketAddress(ipAddress: address.host, port: address.port) {
37+
case .v4:
38+
()
39+
default:
40+
assertionFailure(
41+
"""
42+
\(address.host):\(address.port) isn't a valid IPv4 address, did you mean to \
43+
use 'dns(host:port:)' instead?
44+
"""
45+
)
46+
}
47+
}
48+
}
49+
}
50+
3251
self.addresses = addresses
3352
}
3453
}
@@ -38,14 +57,26 @@ extension ResolvableTargets {
3857
extension ResolvableTarget where Self == ResolvableTargets.IPv4 {
3958
/// Creates a new resolvable IPv4 target for a single address.
4059
/// - Parameters:
41-
/// - host: The host address.
60+
/// - host: The resolved host address.
4261
/// - port: The port on the host.
4362
/// - Returns: A ``ResolvableTarget``.
63+
@available(*, deprecated, renamed: "ipv4(address:port:)")
4464
public static func ipv4(host: String, port: Int = 443) -> Self {
4565
let address = SocketAddress.IPv4(host: host, port: port)
4666
return Self(addresses: [address])
4767
}
4868

69+
/// Creates a new resolvable IPv4 target for a single address.
70+
/// - Parameters:
71+
/// - address: The resolved host address.
72+
/// - port: The port on the host.
73+
/// - Returns: A ``ResolvableTarget``.
74+
@available(gRPCSwiftNIOTransport 2.1, *)
75+
public static func ipv4(address: String, port: Int = 443) -> Self {
76+
let address = SocketAddress.IPv4(host: address, port: port)
77+
return Self(addresses: [address])
78+
}
79+
4980
/// Creates a new resolvable IPv4 target from the provided host-port pairs.
5081
///
5182
/// - Parameter pairs: An array of host-port pairs.

Sources/GRPCNIOTransportCore/Client/Resolver/NameResolver+IPv6.swift

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
internal import GRPCCore
18+
internal import NIOCore
1819

1920
@available(gRPCSwiftNIOTransport 2.0, *)
2021
extension ResolvableTargets {
@@ -29,6 +30,23 @@ extension ResolvableTargets {
2930
/// Create a new IPv6 target.
3031
/// - Parameter addresses: The IPv6 addresses.
3132
public init(addresses: [SocketAddress.IPv6]) {
33+
debugOnly {
34+
for address in addresses {
35+
do {
36+
switch try? NIOCore.SocketAddress(ipAddress: address.host, port: address.port) {
37+
case .v6:
38+
()
39+
default:
40+
assertionFailure(
41+
"""
42+
\(address.host):\(address.port) isn't a valid IPv6 address, did you mean to \
43+
use 'dns(host:port:)' instead?
44+
"""
45+
)
46+
}
47+
}
48+
}
49+
}
3250
self.addresses = addresses
3351
}
3452
}
@@ -38,14 +56,26 @@ extension ResolvableTargets {
3856
extension ResolvableTarget where Self == ResolvableTargets.IPv6 {
3957
/// Creates a new resolvable IPv6 target for a single address.
4058
/// - Parameters:
41-
/// - host: The host address.
59+
/// - host: The resolved host address.
4260
/// - port: The port on the host.
4361
/// - Returns: A ``ResolvableTarget``.
62+
@available(*, deprecated, renamed: "ipv6(address:port:)")
4463
public static func ipv6(host: String, port: Int = 443) -> Self {
4564
let address = SocketAddress.IPv6(host: host, port: port)
4665
return Self(addresses: [address])
4766
}
4867

68+
/// Creates a new resolvable IPv6 target for a single address.
69+
/// - Parameters:
70+
/// - address: The resolved host address.
71+
/// - port: The port on the host.
72+
/// - Returns: A ``ResolvableTarget``.
73+
@available(gRPCSwiftNIOTransport 2.1, *)
74+
public static func ipv6(address: String, port: Int = 443) -> Self {
75+
let address = SocketAddress.IPv6(host: address, port: port)
76+
return Self(addresses: [address])
77+
}
78+
4979
/// Creates a new resolvable IPv6 target from the provided host-port pairs.
5080
///
5181
/// - Parameter pairs: An array of host-port pairs.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2025, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
@inlinable
18+
internal func debugOnly(_ body: () -> Void) {
19+
assert(
20+
{
21+
body()
22+
return true
23+
}()
24+
)
25+
}

Tests/GRPCNIOTransportCoreTests/Client/Resolver/NameResolverRegistryTests.swift

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ final class NameResolverRegistryTests: XCTestCase {
142142

143143
func testMakeResolver() {
144144
let resolvers = NameResolverRegistry()
145-
XCTAssertNil(resolvers.makeResolver(for: .ipv4(host: "foo")))
145+
XCTAssertNil(resolvers.makeResolver(for: .ipv4(address: "127.0.0.1")))
146146
}
147147

148148
func testCustomResolver() async throws {
@@ -192,22 +192,25 @@ final class NameResolverRegistryTests: XCTestCase {
192192

193193
func testIPv4ResolverForSingleHost() async throws {
194194
let factory = NameResolvers.IPv4()
195-
let resolver = factory.resolver(for: .ipv4(host: "foo", port: 1234))
195+
let resolver = factory.resolver(for: .ipv4(address: "127.0.0.1", port: 1234))
196196

197197
XCTAssertEqual(resolver.updateMode, .pull)
198198

199199
// The IPv4 resolver always returns the same values.
200200
var iterator = resolver.names.makeAsyncIterator()
201201
for _ in 0 ..< 1000 {
202202
let result = try await XCTUnwrapAsync { try await iterator.next() }
203-
XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.ipv4(host: "foo", port: 1234)])])
203+
XCTAssertEqual(
204+
result.endpoints,
205+
[Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 1234)])]
206+
)
204207
XCTAssertNil(result.serviceConfig)
205208
}
206209
}
207210

208211
func testIPv4ResolverForMultipleHosts() async throws {
209212
let factory = NameResolvers.IPv4()
210-
let resolver = factory.resolver(for: .ipv4(pairs: [("foo", 443), ("bar", 444)]))
213+
let resolver = factory.resolver(for: .ipv4(pairs: [("127.0.0.1", 443), ("127.0.0.1", 444)]))
211214

212215
XCTAssertEqual(resolver.updateMode, .pull)
213216

@@ -218,8 +221,8 @@ final class NameResolverRegistryTests: XCTestCase {
218221
XCTAssertEqual(
219222
result.endpoints,
220223
[
221-
Endpoint(addresses: [.ipv4(host: "foo", port: 443)]),
222-
Endpoint(addresses: [.ipv4(host: "bar", port: 444)]),
224+
Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 443)]),
225+
Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 444)]),
223226
]
224227
)
225228
XCTAssertNil(result.serviceConfig)
@@ -228,22 +231,22 @@ final class NameResolverRegistryTests: XCTestCase {
228231

229232
func testIPv6ResolverForSingleHost() async throws {
230233
let factory = NameResolvers.IPv6()
231-
let resolver = factory.resolver(for: .ipv6(host: "foo", port: 1234))
234+
let resolver = factory.resolver(for: .ipv6(address: "::1", port: 1234))
232235

233236
XCTAssertEqual(resolver.updateMode, .pull)
234237

235238
// The IPv6 resolver always returns the same values.
236239
var iterator = resolver.names.makeAsyncIterator()
237240
for _ in 0 ..< 1000 {
238241
let result = try await XCTUnwrapAsync { try await iterator.next() }
239-
XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.ipv6(host: "foo", port: 1234)])])
242+
XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.ipv6(host: "::1", port: 1234)])])
240243
XCTAssertNil(result.serviceConfig)
241244
}
242245
}
243246

244247
func testIPv6ResolverForMultipleHosts() async throws {
245248
let factory = NameResolvers.IPv6()
246-
let resolver = factory.resolver(for: .ipv6(pairs: [("foo", 443), ("bar", 444)]))
249+
let resolver = factory.resolver(for: .ipv6(pairs: [("::1", 443), ("::1", 444)]))
247250

248251
XCTAssertEqual(resolver.updateMode, .pull)
249252

@@ -254,8 +257,8 @@ final class NameResolverRegistryTests: XCTestCase {
254257
XCTAssertEqual(
255258
result.endpoints,
256259
[
257-
Endpoint(addresses: [.ipv6(host: "foo", port: 443)]),
258-
Endpoint(addresses: [.ipv6(host: "bar", port: 444)]),
260+
Endpoint(addresses: [.ipv6(host: "::1", port: 443)]),
261+
Endpoint(addresses: [.ipv6(host: "::1", port: 444)]),
259262
]
260263
)
261264
XCTAssertNil(result.serviceConfig)

Tests/GRPCNIOTransportHTTP2Tests/HTTP2ServerTransport+DebugTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ struct ChannelDebugCallbackTests {
7474
try await withGRPCClient(
7575
transport: self.makeClientTransport(
7676
kind: clientKind,
77-
target: .ipv4(host: address.host, port: address.port),
77+
target: .ipv4(address: address.host, port: address.port),
7878
debug: clientDebug
7979
)
8080
) { client in

Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportTLSEnabledTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ struct HTTP2TransportTLSEnabledTests {
673673
throw TLSEnabledTestsError.unexpectedListeningAddress
674674
}
675675

676-
let target: any ResolvableTarget = .ipv4(host: address.host, port: address.port)
676+
let target: any ResolvableTarget = .ipv4(address: address.host, port: address.port)
677677
let clientTransport: NIOClientTransport
678678
switch clientConfig {
679679
case .posix(let config):

Tests/GRPCNIOTransportHTTP2Tests/HTTP2TransportTests.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ final class HTTP2TransportTests: XCTestCase {
6060

6161
let target: any ResolvableTarget
6262
if let ipv4 = address.ipv4 {
63-
target = .ipv4(host: ipv4.host, port: ipv4.port)
63+
target = .ipv4(address: ipv4.host, port: ipv4.port)
6464
} else if let ipv6 = address.ipv6 {
65-
target = .ipv6(host: ipv6.host, port: ipv6.port)
65+
target = .ipv6(address: ipv6.host, port: ipv6.port)
6666
} else if let uds = address.unixDomainSocket {
6767
target = .unixDomainSocket(path: uds.path)
6868
} else {
@@ -108,7 +108,7 @@ final class HTTP2TransportTests: XCTestCase {
108108
let address = try await server.listeningAddress
109109
let client = try self.makeClient(
110110
kind: clientKind,
111-
target: .ipv4(host: address.host, port: address.port),
111+
target: .ipv4(address: address.host, port: address.port),
112112
compression: .none,
113113
enabledCompression: .none
114114
)
@@ -1602,7 +1602,7 @@ final class HTTP2TransportTests: XCTestCase {
16021602

16031603
func testAuthorityIPv4() async throws {
16041604
try await self.testAuthority(serverAddress: .ipv4(host: "127.0.0.1", port: 0)) { address in
1605-
return .ipv4(host: "127.0.0.1", port: address.ipv4!.port)
1605+
return .ipv4(address: "127.0.0.1", port: address.ipv4!.port)
16061606
} expectedAuthority: { address in
16071607
return "127.0.0.1:\(address.ipv4!.port)"
16081608
}
@@ -1613,15 +1613,15 @@ final class HTTP2TransportTests: XCTestCase {
16131613
serverAddress: .ipv4(host: "127.0.0.1", port: 0),
16141614
authorityOverride: "respect-my-authority"
16151615
) { address in
1616-
return .ipv4(host: "127.0.0.1", port: address.ipv4!.port)
1616+
return .ipv4(address: "127.0.0.1", port: address.ipv4!.port)
16171617
} expectedAuthority: { _ in
16181618
return "respect-my-authority"
16191619
}
16201620
}
16211621

16221622
func testAuthorityIPv6() async throws {
16231623
try await self.testAuthority(serverAddress: .ipv6(host: "::1", port: 0)) { address in
1624-
return .ipv6(host: "::1", port: address.ipv6!.port)
1624+
return .ipv6(address: "::1", port: address.ipv6!.port)
16251625
} expectedAuthority: { address in
16261626
return "[::1]:\(address.ipv6!.port)"
16271627
}
@@ -1632,7 +1632,7 @@ final class HTTP2TransportTests: XCTestCase {
16321632
serverAddress: .ipv6(host: "::1", port: 0),
16331633
authorityOverride: "respect-my-authority"
16341634
) { address in
1635-
return .ipv6(host: "::1", port: address.ipv6!.port)
1635+
return .ipv6(address: "::1", port: address.ipv6!.port)
16361636
} expectedAuthority: { _ in
16371637
return "respect-my-authority"
16381638
}

0 commit comments

Comments
 (0)