Skip to content

Commit a1e1bb2

Browse files
committed
feat: Switch default features so a user must select SSL backend
1 parent dd58147 commit a1e1bb2

43 files changed

Lines changed: 668 additions & 1434 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/samples-rust-server.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ jobs:
6464
if cargo read-manifest | grep -q '"validate"'; then
6565
cargo build --features validate --all-targets
6666
fi
67-
# Client without TLS (HTTP-only)
67+
# Test TLS features if they exist
6868
if cargo read-manifest | grep -q '"client-tls"'; then
69+
# Client without TLS (HTTP-only)
6970
cargo build --no-default-features --features=client --lib
70-
# Client with selective TLS backend
71+
# Client with TLS (using native-tls)
7172
cargo build --no-default-features --features=client,client-tls --lib
73+
# Server without TLS
7274
cargo build --no-default-features --features=server --lib
7375
fi
7476
cargo fmt

modules/openapi-generator/src/main/resources/rust-server/Cargo.mustache

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ homepage = "{{.}}"
3232
{{/homePageUrl}}
3333

3434
[features]
35-
default = ["client", "server", "client-tls", "client-openssl"]
35+
default = ["client", "server"]
3636
client = [
3737
{{#apiUsesMultipartFormData}}
3838
"multipart", "multipart/client", "swagger/multipart_form",
@@ -49,16 +49,16 @@ client = [
4949
{{! Anything added to the list below, should probably be added to the callbacks list below }}
5050
"hyper", "percent-encoding", "hyper-util/http1", "hyper-util/http2", "url"
5151
]
52-
# TLS support using native-tls (macOS/Windows/iOS) or hyper-tls
52+
# TLS support - automatically selects backend based on target OS:
53+
# - macOS/Windows/iOS: native-tls via hyper-tls
54+
# - Other platforms: OpenSSL via hyper-openssl
55+
# Dependencies are in target-specific sections below
5356
client-tls = [
5457
"client",
55-
"hyper-tls", "native-tls",
56-
"swagger/tls"
57-
]
58-
# TLS support using OpenSSL (Linux and other platforms)
59-
client-openssl = [
60-
"client",
61-
"hyper-openssl", "openssl",
58+
"dep:native-tls",
59+
"dep:hyper-tls",
60+
"dep:openssl",
61+
"dep:hyper-openssl",
6262
"swagger/tls"
6363
]
6464
server = [
@@ -69,7 +69,6 @@ server = [
6969
"mime_multipart", "swagger/multipart_related",
7070
{{/apiUsesMultipartRelated}}
7171
{{#hasCallbacks}}
72-
"client-tls",
7372
{{/hasCallbacks}}
7473
{{! Anything added to the list below, should probably be added to the callbacks list above }}
7574
"serde_ignored", "hyper", "percent-encoding", "url",
@@ -86,14 +85,6 @@ conversion = ["frunk", "frunk_derives", "frunk_core", "frunk-enum-core", "frunk-
8685
mock = ["mockall"]
8786
validate = [{{^apiUsesByteArray}}"regex",{{/apiUsesByteArray}} "serde_valid", "swagger/serdevalid"]
8887

89-
[target.'cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))'.dependencies]
90-
native-tls = { version = "0.2", optional = true }
91-
hyper-tls = { version = "0.6", optional = true }
92-
93-
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dependencies]
94-
hyper-openssl = { version = "0.10", optional = true }
95-
openssl = { version = "0.10", optional = true }
96-
9788
[dependencies]
9889
# Common
9990
async-trait = "0.1.88"
@@ -145,6 +136,12 @@ serde_urlencoded = { version = "0.7.1", optional = true }
145136
{{/usesUrlEncodedForm}}
146137
tower-service = "0.3.3"
147138

139+
# TLS support - all listed here, actual usage determined by cfg attributes in code
140+
native-tls = { version = "0.2", optional = true }
141+
hyper-tls = { version = "0.6", optional = true }
142+
openssl = { version = "0.10", optional = true }
143+
hyper-openssl = { version = "0.10", optional = true }
144+
148145
# Server, and client callback-specific
149146
{{^apiUsesByteArray}}
150147
lazy_static = { version = "1.5", optional = true }
@@ -175,15 +172,13 @@ clap = "4.5"
175172
env_logger = "0.11"
176173
tokio = { version = "1.49", features = ["full"] }
177174
native-tls = "0.2"
175+
openssl = "0.10"
176+
tokio-openssl = "0.6"
178177
pin-project = "1.1.10"
179178

180179
# Bearer authentication, used in examples
181180
jsonwebtoken = {version = "10.0.0", features = ["rust_crypto"]}
182181

183-
[target.'cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))'.dev-dependencies]
184-
tokio-openssl = "0.6"
185-
openssl = "0.10"
186-
187182
[[example]]
188183
name = "client"
189184
required-features = ["client"]

modules/openapi-generator/src/main/resources/rust-server/README.mustache

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,10 @@ The generated library has a few optional features that can be activated through
127127
* This defaults to enabled and creates the basic skeleton of a client implementation based on hyper
128128
* The constructed client implements the API trait by making remote API call.
129129
* `client-tls`
130-
* This defaults to enabled and provides HTTPS support using native-tls (macOS/Windows/iOS).
131-
* Enable this feature for TLS support on Apple and Windows platforms.
132-
* `client-openssl`
133-
* This defaults to enabled and provides HTTPS support using OpenSSL (Linux and other platforms).
134-
* Enable this feature for TLS support on Linux and other Unix-like platforms.
130+
* Optional feature that provides HTTPS support with automatic TLS backend selection:
131+
- macOS/Windows/iOS: native-tls + hyper-tls
132+
- Linux/Unix/others: OpenSSL + hyper-openssl
133+
* Not enabled by default to minimize dependencies.
135134
* `conversions`
136135
* This defaults to disabled and creates extra derives on models to allow "transmogrification" between objects of structurally similar types.
137136
* `cli`
@@ -140,22 +139,27 @@ The generated library has a few optional features that can be activated through
140139
* This defaults to disabled and allows JSON Schema validation of received data using `MakeService::set_validation` or `Service::set_validation`.
141140
* Note, enabling validation will have a performance penalty, especially if the API heavily uses regex based checks.
142141

143-
### Minimal dependencies (no TLS)
142+
### Enabling HTTPS/TLS Support
144143

145-
If you only need HTTP support and want to minimize dependencies (e.g., to avoid OpenSSL build requirements), you can disable the default TLS features:
144+
By default, only HTTP support is included. To enable HTTPS, add the `client-tls` feature:
146145

147146
```toml
148147
[dependencies]
149-
{{{packageName}}} = { version = "{{{packageVersion}}}", default-features = false, features = ["server"] }
148+
{{{packageName}}} = { version = "{{{packageVersion}}}", features = ["client-tls"] }
150149
```
151150

152-
Or for client-only without TLS:
153-
151+
**For server with callbacks that need HTTPS:**
154152
```toml
155153
[dependencies]
156-
{{{packageName}}} = { version = "{{{packageVersion}}}", default-features = false, features = ["client"] }
154+
{{{packageName}}} = { version = "{{{packageVersion}}}", features = ["server", "client-tls"] }
157155
```
158156

157+
The TLS backend is automatically selected based on your target platform:
158+
- **macOS, Windows, iOS**: Uses `native-tls` (system TLS libraries)
159+
- **Linux, Unix, other platforms**: Uses `openssl`
160+
161+
This ensures the best compatibility and native integration on each platform.
162+
159163
See https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section for how to use features in your `Cargo.toml`.
160164

161165
## Documentation for API Endpoints

modules/openapi-generator/src/main/resources/rust-server/bin-cli.mustache

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,6 @@ struct Cli {
4444
#[clap(short = 'a', long, default_value = "http://localhost")]
4545
server_address: String,
4646
47-
/// Path to the client private key if using client-side TLS authentication
48-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
49-
#[clap(long, requires_all(&["client_certificate", "server_certificate"]))]
50-
client_key: Option<String>,
51-
52-
/// Path to the client's public certificate associated with the private key
53-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
54-
#[clap(long, requires_all(&["client_key", "server_certificate"]))]
55-
client_certificate: Option<String>,
56-
57-
/// Path to CA certificate used to authenticate the server
58-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
59-
#[clap(long)]
60-
server_certificate: Option<String>,
61-
6247
/// If set, write output to file instead of stdout
6348
#[clap(short, long)]
6449
output_file: Option<String>,
@@ -130,34 +115,11 @@ enum Operation {
130115
{{/apiInfo}}
131116
}
132117

133-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
134-
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
135-
if args.client_certificate.is_some() {
136-
debug!("Using mutual TLS");
137-
let client = Client::try_new_https_mutual(
138-
&args.server_address,
139-
args.server_certificate.clone().unwrap(),
140-
args.client_key.clone().unwrap(),
141-
args.client_certificate.clone().unwrap(),
142-
)
143-
.context("Failed to create HTTPS client")?;
144-
Ok(Box::new(client.with_context(context)))
145-
} else if args.server_certificate.is_some() {
146-
debug!("Using TLS with pinned server certificate");
147-
let client =
148-
Client::try_new_https_pinned(&args.server_address, args.server_certificate.clone().unwrap())
149-
.context("Failed to create HTTPS client")?;
150-
Ok(Box::new(client.with_context(context)))
151-
} else {
152-
debug!("Using client without certificates");
153-
let client =
154-
Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?;
155-
Ok(Box::new(client.with_context(context)))
156-
}
157-
}
158-
159-
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
160118
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
119+
// Client::try_new() automatically detects the URL scheme (http:// or https://)
120+
// and creates the appropriate client.
121+
// - With client-tls feature: Supports both http:// and https:// URLs
122+
// - Without TLS features: Only supports http://, returns error for https://
161123
let client =
162124
Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?;
163125
Ok(Box::new(client.with_context(context)))

modules/openapi-generator/src/main/resources/rust-server/client-mod.mustache

Lines changed: 18 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,8 @@ impl<Connector, C> Client<
121121
#[derive(Debug, Clone)]
122122
pub enum HyperClient {
123123
Http(hyper_util::client::legacy::Client<hyper_util::client::legacy::connect::HttpConnector, BoxBody<Bytes, Infallible>>),
124-
#[cfg(any(
125-
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")),
126-
all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios")))
127-
))]
128-
Https(hyper_util::client::legacy::Client<HttpsConnector, BoxBody<Bytes, Infallible>>),
124+
#[cfg(feature = "client-tls")]
125+
Https(hyper_util::client::legacy::Client<hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>, BoxBody<Bytes, Infallible>>),
129126
}
130127
131128
impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
@@ -136,10 +133,7 @@ impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
136133
fn call(&self, req: Request<BoxBody<Bytes, Infallible>>) -> Self::Future {
137134
match self {
138135
HyperClient::Http(client) => client.request(req),
139-
#[cfg(any(
140-
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")),
141-
all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios")))
142-
))]
136+
#[cfg(feature = "client-tls")]
143137
HyperClient::Https(client) => client.request(req),
144138
}
145139
}
@@ -166,20 +160,15 @@ impl<C> Client<DropContextService<HyperClient, C>, C> where
166160
"http" => {
167161
HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build()))
168162
},
169-
#[cfg(any(
170-
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")),
171-
all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios")))
172-
))]
163+
#[cfg(feature = "client-tls")]
173164
"https" => {
174-
let connector = connector.https()
175-
.build()
176-
.map_err(ClientInitError::SslError)?;
177-
HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector))
165+
// Build native-tls connector directly to avoid ambiguity with OpenSSL
166+
let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new();
167+
http_connector.enforce_http(false);
168+
let https_connector = hyper_tls::HttpsConnector::new_with_connector(http_connector);
169+
HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector))
178170
},
179-
#[cfg(not(any(
180-
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")),
181-
all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios")))
182-
)))]
171+
#[cfg(not(feature = "client-tls"))]
183172
"https" => {
184173
return Err(ClientInitError::TlsNotEnabled);
185174
},
@@ -225,21 +214,12 @@ impl<C> Client<
225214
}
226215
}
227216

228-
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
229-
type HttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
230-
231-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
232-
type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
233-
234-
#[cfg(any(
235-
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")),
236-
all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios")))
237-
))]
217+
#[cfg(feature = "client-tls")]
238218
impl<C> Client<
239219
DropContextService<
240220
hyper_util::service::TowerToHyperService<
241221
hyper_util::client::legacy::Client<
242-
HttpsConnector,
222+
hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>,
243223
BoxBody<Bytes, Infallible>
244224
>
245225
>,
@@ -252,62 +232,13 @@ impl<C> Client<
252232
/// Create a client with a TLS connection to the server
253233
///
254234
/// # Arguments
255-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
235+
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
256236
pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
257237
{
258-
let https_connector = Connector::builder()
259-
.https()
260-
.build()
261-
.map_err(ClientInitError::SslError)?;
262-
Self::try_new_with_connector(base_path, Some("https"), https_connector)
263-
}
264-
265-
/// Create a client with a TLS connection to the server using a pinned certificate
266-
///
267-
/// # Arguments
268-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
269-
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
270-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
271-
pub fn try_new_https_pinned<CA>(
272-
base_path: &str,
273-
ca_certificate: CA,
274-
) -> Result<Self, ClientInitError>
275-
where
276-
CA: AsRef<Path>,
277-
{
278-
let https_connector = Connector::builder()
279-
.https()
280-
.pin_server_certificate(ca_certificate)
281-
.build()
282-
.map_err(ClientInitError::SslError)?;
283-
Self::try_new_with_connector(base_path, Some("https"), https_connector)
284-
}
285-
286-
/// Create a client with a mutually authenticated TLS connection to the server.
287-
///
288-
/// # Arguments
289-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
290-
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
291-
/// * `client_key` - Path to the client private key
292-
/// * `client_certificate` - Path to the client's public certificate associated with the private key
293-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
294-
pub fn try_new_https_mutual<CA, K, D>(
295-
base_path: &str,
296-
ca_certificate: CA,
297-
client_key: K,
298-
client_certificate: D,
299-
) -> Result<Self, ClientInitError>
300-
where
301-
CA: AsRef<Path>,
302-
K: AsRef<Path>,
303-
D: AsRef<Path>,
304-
{
305-
let https_connector = Connector::builder()
306-
.https()
307-
.pin_server_certificate(ca_certificate)
308-
.client_authentication(client_key, client_certificate)
309-
.build()
310-
.map_err(ClientInitError::SslError)?;
238+
// Build native-tls connector directly
239+
let mut http_connector = hyper_util::client::legacy::connect::HttpConnector::new();
240+
http_connector.enforce_http(false);
241+
let https_connector = hyper_tls::HttpsConnector::new_with_connector(http_connector);
311242
Self::try_new_with_connector(base_path, Some("https"), https_connector)
312243
}
313244
}
@@ -352,12 +283,8 @@ pub enum ClientInitError {
352283
TlsNotEnabled,
353284
354285
/// SSL Connection Error
355-
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
286+
#[cfg(feature = "client-tls")]
356287
SslError(native_tls::Error),
357-
358-
/// SSL Connection Error
359-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
360-
SslError(openssl::error::ErrorStack),
361288
}
362289

363290
impl From<hyper::http::uri::InvalidUri> for ClientInitError {

0 commit comments

Comments
 (0)