From 130a2f6b0fff2131e518305cb7670676f4ae1b5c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:18 +0000 Subject: [PATCH 1/4] Remove dead api-key check from bulkLookup `buildURL` adds the `api-key` query item to every URL it builds, so the `if !url.absoluteString.contains("api-key")` branch in `bulkLookup` could never run. Drop it together with `appendAPIKey(to:)`, whose only caller it was. The bulk URL is unchanged; a new test pins that it carries exactly one `api-key` query item next to the `fields` item. Co-Authored-By: Claude Fable 5.1 --- Sources/IPData/IPDataClient.swift | 16 +++------------- Tests/IPDataTests/IPDataClientTests.swift | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Sources/IPData/IPDataClient.swift b/Sources/IPData/IPDataClient.swift index 5963b00..35fd4de 100644 --- a/Sources/IPData/IPDataClient.swift +++ b/Sources/IPData/IPDataClient.swift @@ -134,11 +134,9 @@ public final class IPDataClient: @unchecked Sendable { /// - fields: Optional list of fields to return. /// - Returns: An array of ``IPResponse`` results, one per IP. public func bulkLookup(_ ips: [String], fields: [String]? = nil) async throws -> [IPResponse] { - var url = buildURL(path: "/bulk", fields: fields) - // For bulk, the api-key must be a query parameter - if !url.absoluteString.contains("api-key") { - url = appendAPIKey(to: url) - } + // For bulk, the api-key must be a query parameter; `buildURL` adds it to + // every URL, so nothing else is needed here. + let url = buildURL(path: "/bulk", fields: fields) var request = URLRequest(url: url) request.httpMethod = "POST" @@ -177,14 +175,6 @@ public final class IPDataClient: @unchecked Sendable { return components.url! } - private func appendAPIKey(to url: URL) -> URL { - var components = URLComponents(url: url, resolvingAgainstBaseURL: false)! - var items = components.queryItems ?? [] - items.append(URLQueryItem(name: "api-key", value: apiKey)) - components.queryItems = items - return components.url! - } - private func fetch(_ url: URL) async throws -> T { var request = URLRequest(url: url) request.setValue(apiKey, forHTTPHeaderField: "api-key") diff --git a/Tests/IPDataTests/IPDataClientTests.swift b/Tests/IPDataTests/IPDataClientTests.swift index 2cdf3a5..7684bae 100644 --- a/Tests/IPDataTests/IPDataClientTests.swift +++ b/Tests/IPDataTests/IPDataClientTests.swift @@ -222,6 +222,21 @@ final class IPDataClientTests: XCTestCase { XCTAssertEqual(results[1].ip, "8.8.8.8") } + func testBulkLookupSendsTheAPIKeyOnceAsAQueryItem() async throws { + MockURLProtocol.requestHandler = { request in + let components = URLComponents(url: request.url!, resolvingAgainstBaseURL: false)! + let items = components.queryItems ?? [] + let apiKeyItems = items.filter { $0.name == "api-key" } + XCTAssertEqual(apiKeyItems.count, 1, "Query: \(components.query ?? "")") + XCTAssertEqual(apiKeyItems.first?.value, "test-api-key") + XCTAssertTrue(items.contains(where: { $0.name == "fields" && $0.value == "ip" })) + return okResponse(for: request, json: #"[{"ip": "1.1.1.1"}]"#) + } + + _ = try await client.bulkLookup(["1.1.1.1"], fields: ["ip"]) + XCTAssertEqual(MockURLProtocol.requestCount, 1) + } + // MARK: - Connection Reuse func testConsecutiveRequestsGoThroughTheSameInjectedSession() async throws { From 60c6638ad666a9e54dc73c7f024e53938f0f664b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:18 +0000 Subject: [PATCH 2/4] Compile the Linux URLSession shim only below Swift 6 swift-corelibs-foundation ships the async `URLSession.data(for:delegate:)` from Swift 6.0 on, so the package's own `data(for:)` shim is only needed by the 5.9 and 5.10 toolchains on Linux. Guard it with `compiler(<6.0)` so that Swift 6 toolchains use the native method and the two declarations never coexist. Co-Authored-By: Claude Fable 5.1 --- Sources/IPData/IPDataClient.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Sources/IPData/IPDataClient.swift b/Sources/IPData/IPDataClient.swift index 35fd4de..f86b750 100644 --- a/Sources/IPData/IPDataClient.swift +++ b/Sources/IPData/IPDataClient.swift @@ -244,7 +244,11 @@ public final class IPDataClient: @unchecked Sendable { // MARK: - Linux Async Compatibility -#if canImport(FoundationNetworking) +// swift-corelibs-foundation ships the async `URLSession.data(for:delegate:)` from +// Swift 6.0 on; the 5.9 and 5.10 toolchains on Linux lack it and use this shim instead. +// It is compiled only for those toolchains so that it never sits next to the native +// method. +#if canImport(FoundationNetworking) && compiler(<6.0) extension URLSession { func data(for request: URLRequest) async throws -> (Data, URLResponse) { try await withCheckedThrowingContinuation { continuation in From a6439c6f12f7c98063f9702fc4d6a241035345de Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:18 +0000 Subject: [PATCH 3/4] Fix the README IPResponse table The table listed `timeZone` as `TimeZone?` although the model type is `TimeZoneInfo`, and it had no rows for `regionType` and `emojiUnicode`. Correct the type, add the two rows and a short TimeZoneInfo section. Co-Authored-By: Claude Fable 5.1 --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d6bbce..5c8fc52 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ The primary response type for IP lookups. | `city` | `String?` | City name | | `region` | `String?` | Region / state | | `regionCode` | `String?` | Region code | +| `regionType` | `String?` | Kind of region (for example `state` or `province`) | | `countryName` | `String?` | Country name | | `countryCode` | `String?` | ISO 3166-1 alpha-2 code | | `continentName` | `String?` | Continent name | @@ -205,12 +206,13 @@ The primary response type for IP lookups. | `callingCode` | `String?` | International calling code | | `flag` | `String?` | Flag image URL | | `emojiFlag` | `String?` | Flag emoji | +| `emojiUnicode` | `String?` | Flag emoji as Unicode code points (for example `U+1F1FA U+1F1F8`) | | `asn` | `ASN?` | Autonomous system info | | `company` | `Company?` | Organization that owns the IP | | `carrier` | `Carrier?` | Mobile carrier info | | `languages` | `[Language]?` | Languages spoken in the country | | `currency` | `Currency?` | Local currency | -| `timeZone` | `TimeZone?` | Time zone details | +| `timeZone` | `TimeZoneInfo?` | Time zone details | | `threat` | `Threat?` | Threat intelligence data | | `count` | `Int?` | API requests in last 24 hours | @@ -218,6 +220,10 @@ The primary response type for IP lookups. Detailed ASN data from the dedicated `/asn` endpoint. Includes `ipv4Prefixes`, `ipv6Prefixes`, `numIPs`, `upstream`, `downstream`, and `peers`. +### TimeZoneInfo + +Time zone of the IP address location: `name`, `abbr`, `offset`, `isDST` and `currentTime`. The type is called `TimeZoneInfo` so that it does not shadow Foundation's `TimeZone`. + ### Threat Threat intelligence flags: `isTor`, `isVPN`, `isICloudRelay`, `isProxy`, `isDatacenter`, `isAnonymous`, `isKnownAttacker`, `isKnownAbuser`, `isThreat`, `isBogon`. Includes `blocklists` and reputation `scores` (0-100 for VPN, proxy, threat, and trust). From f033e21c9bb20faec6b80af4b7bdfd5d76261560 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 12:50:18 +0000 Subject: [PATCH 4/4] Update CI runner images, checkout action and Swift matrix - actions/checkout v4 targets Node.js 20, which GitHub Actions has deprecated; the runner already forces it onto Node.js 24 and prints a warning on every job. Move to v7, which runs on Node.js 24 natively. - The macos-14 image is deprecated in actions/runner-images. Test on macos-15 and macos-26 instead. - Add Swift 6.0 to 6.4 to the Linux matrix next to 5.9 and 5.10, and let the matrix finish every job (fail-fast off) so that one failing version does not cancel the others. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/ci.yml | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 972693f..df2fdb7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,22 +11,27 @@ jobs: name: Swift ${{ matrix.swift }} on Ubuntu runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - swift: ["5.9", "5.10"] + swift: ["5.9", "5.10", "6.0", "6.1", "6.2", "6.3", "6.4"] container: image: swift:${{ matrix.swift }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Build run: swift build - name: Test run: swift test test-macos: - name: Swift on macOS - runs-on: macos-14 + name: Swift on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-15, macos-26] steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - name: Build run: swift build - name: Test