Skip to content

Comments

Add swift-configuration support#878

Merged
fabianfett merged 8 commits intoswift-server:mainfrom
hamzahrmalik:swift_config
Feb 5, 2026
Merged

Add swift-configuration support#878
fabianfett merged 8 commits intoswift-server:mainfrom
hamzahrmalik:swift_config

Conversation

@hamzahrmalik
Copy link
Contributor

Users should be able to use swift-configuration to create the http client configuration object

Changes

  • duplicate package.swift to create a separate version for 6.0 and 6.1, since Configuration is 6.2+
  • add helper to create http client configuration using ConfigReader

public init(configReader: ConfigReader) throws {
self.init()

// Each entry in the list should be a colon separated pair e.g. localhost:127.0.0.1 or localhost:::1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not comma-separated?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

colon felt more natural to me for defining key-value pairs. I don't mind though

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comma would probably make the ipv6 case slightly eaiser to parse too

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, comma might be used for splitting the pairs themselves e.g. in env vars. Hmm then comma isn't great, but colon is a bit confusing.

@czechboy0
Copy link

lgtm, I'll let the maintainers actually approve though 🙂

Comment on lines +9 to +14
/// - `dnsOverrides` (string array, optional): Colon-separated host:IP pairs for DNS overrides (e.g., "localhost:127.0.0.1").
/// - `redirect` (scoped): Redirect handling configuration read by ``RedirectConfiguration/init(configReader:)``.
/// - `timeout` (scoped): Timeout configuration read by ``Timeout/init(configReader:)``.
/// - `connectionPool` (scoped): Connection pool configuration read by ``ConnectionPool/init(configReader:)``.
/// - `httpVersion` (string, optional, default: automatic): HTTP version to use ( "automatic" or "http1Only").
/// - `maximumUsesPerConnection` (int, optional, default: nil, no limit): Maximum uses per connection.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used dots for hierarchy but camel case for words

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dns.overrides and http.version make sense in that system. But maximum.uses.per.connection does not

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be connection.limits.maximum.uses. It is not the end of the world but it would be great if we could be a bit consistent around the ecosystem. @czechboy0 WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah i like that idea

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been doing the same as Hamzah - use dots for separating components, with individual components being camelCase. So http.maxConnectionCount, for example.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we document this somewhere? It feels like that would be good to have a recommendation at least for the ecosystem.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apple/swift-configuration#145

Feel free to add more questions to the issue, will add docs on this

@czechboy0 czechboy0 added the 🆕 semver/minor Adds new public API. label Jan 21, 2026
Comment on lines +162 to +164
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["invalidentry", "localhost:127.0.0.1"]), isSecret: false)
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about spaces:

localhost: 127.0.0.1

Are we fine with the silent error here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i will make it trim spaces, i think that's going to be the least surprising behaviour

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we think it's preferable to throw an error on invalid config? It would make it much more clear i suppose

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think throwing an error if the value of a key isn't valid is very reasonable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 247 to 259
@Test
@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
func singleDnsOverride() throws {
let testProvider = InMemoryProvider(values: [
"dnsOverrides": .init(.stringArray(["api.example.com:10.0.0.1"]), isSecret: false)
])

let configReader = ConfigReader(provider: testProvider)
let config = try HTTPClient.Configuration(configReader: configReader)

#expect(config.dnsOverride["api.example.com"] == "10.0.0.1")
#expect(config.dnsOverride.count == 1)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the point of this test? what is tested here, that the other tests miss?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's not needed. i think i wrote this one first but the other ones cover this and more so i'll remove it

@fabianfett
Copy link
Member

Am I correctly assuming, that this new behavior is only available to newly created clients and not the default shared one? If yes, is this the intended behavior?

@hamzahrmalik
Copy link
Contributor Author

Am I correctly assuming, that this new behavior is only available to newly created clients and not the default shared one? If yes, is this the intended behavior?

The changes in this PR allow you to create a configuration from a configuration reader

You can then use that configuration to make a client

The shared client does not support a custom configuration. Therefore you are correct that the changes in this PR do not apply to the singleton

@FranzBusch
Copy link
Collaborator

The shared client does not support a custom configuration. Therefore you are correct that the changes in this PR do not apply to the singleton

I think this is expected for now. We could think about letting the singleton client use the EnviornmentConfigProvider in the future but I would keep that in a separate PR to think about it more.

/// - `allowCycles` (bool, optional, default: false): Allow cyclic redirects when mode is "follow".
///
/// - Throws: `HTTPClientError.invalidRedirectConfiguration` if mode is specified but invalid.
public init(configReader: ConfigReader) throws {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the developer experience, here I think it would be better to keep this method internal and doc all the keys in the OG init. WDYT?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@czechboy0 do you have any guidance for this?

Copy link

@czechboy0 czechboy0 Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just have this: https://swiftpackageindex.com/apple/swift-configuration/main/documentation/configuration/configuring-libraries

But it doesn't go into what to do with nested structs.

My rule of thumb would be: if the nested structs are public API, and you can construct them using public API, I think the config-based initializer should then also be public. Otherwise we move those docs onto a parent struct that isn't easy to find unless you already know where to find it. So I think it's just easier to follow this way.

@fabianfett fabianfett merged commit 986dc47 into swift-server:main Feb 5, 2026
62 of 63 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🆕 semver/minor Adds new public API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants