Skip to content

Commit a8e6ee2

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

43 files changed

Lines changed: 916 additions & 674 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: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ struct Cli {
4545
server_address: String,
4646
4747
/// 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")))]
48+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
4949
#[clap(long, requires_all(&["client_certificate", "server_certificate"]))]
5050
client_key: Option<String>,
5151
5252
/// 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")))]
53+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
5454
#[clap(long, requires_all(&["client_key", "server_certificate"]))]
5555
client_certificate: Option<String>,
5656
5757
/// Path to CA certificate used to authenticate the server
58-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
58+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
5959
#[clap(long)]
6060
server_certificate: Option<String>,
6161
@@ -130,7 +130,8 @@ enum Operation {
130130
{{/apiInfo}}
131131
}
132132

133-
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
133+
// On Linux/Unix with OpenSSL (client-tls feature), support certificate pinning and mutual TLS
134+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
134135
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
135136
if args.client_certificate.is_some() {
136137
debug!("Using mutual TLS");
@@ -156,8 +157,15 @@ fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoCont
156157
}
157158
}
158159

159-
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
160+
// On macOS/Windows/iOS or without client-tls feature, use simple client (no cert pinning/mutual TLS)
161+
#[cfg(any(
162+
not(feature = "client-tls"),
163+
all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios"))
164+
))]
160165
fn create_client(args: &Cli, context: ClientContext) -> Result<Box<dyn ApiNoContext<ClientContext>>> {
166+
// Client::try_new() automatically detects the URL scheme (http:// or https://)
167+
// and creates the appropriate client.
168+
// Note: Certificate pinning and mutual TLS are only available on Linux/Unix with OpenSSL
161169
let client =
162170
Client::try_new(&args.server_address).context("Failed to create HTTP(S) client")?;
163171
Ok(Box::new(client.with_context(context)))

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

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -118,14 +118,17 @@ impl<Connector, C> Client<
118118
}
119119
}
120120
121+
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
122+
type HyperHttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
123+
124+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
125+
type HyperHttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
126+
121127
#[derive(Debug, Clone)]
122128
pub enum HyperClient {
123129
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>>),
130+
#[cfg(feature = "client-tls")]
131+
Https(hyper_util::client::legacy::Client<HyperHttpsConnector, BoxBody<Bytes, Infallible>>),
129132
}
130133
131134
impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
@@ -136,10 +139,7 @@ impl Service<Request<BoxBody<Bytes, Infallible>>> for HyperClient {
136139
fn call(&self, req: Request<BoxBody<Bytes, Infallible>>) -> Self::Future {
137140
match self {
138141
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-
))]
142+
#[cfg(feature = "client-tls")]
143143
HyperClient::Https(client) => client.request(req),
144144
}
145145
}
@@ -166,20 +166,15 @@ impl<C> Client<DropContextService<HyperClient, C>, C> where
166166
"http" => {
167167
HyperClient::Http(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(connector.build()))
168168
},
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-
))]
169+
#[cfg(feature = "client-tls")]
173170
"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))
171+
let https_connector = connector
172+
.https()
173+
.build()
174+
.map_err(ClientInitError::SslError)?;
175+
HyperClient::Https(hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()).build(https_connector))
178176
},
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-
)))]
177+
#[cfg(not(feature = "client-tls"))]
183178
"https" => {
184179
return Err(ClientInitError::TlsNotEnabled);
185180
},
@@ -228,13 +223,10 @@ impl<C> Client<
228223
#[cfg(all(feature = "client-tls", any(target_os = "macos", target_os = "windows", target_os = "ios")))]
229224
type HttpsConnector = hyper_tls::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
230225

231-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
226+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
232227
type HttpsConnector = hyper_openssl::client::legacy::HttpsConnector<hyper_util::client::legacy::connect::HttpConnector>;
233228

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-
))]
229+
#[cfg(feature = "client-tls")]
238230
impl<C> Client<
239231
DropContextService<
240232
hyper_util::service::TowerToHyperService<
@@ -249,10 +241,25 @@ impl<C> Client<
249241
> where
250242
C: Clone + Send + Sync + 'static
251243
{
252-
/// Create a client with a TLS connection to the server
244+
/// Create a client with a TLS connection to the server using native-tls.
253245
///
254246
/// # Arguments
255-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
247+
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
248+
#[cfg(any(target_os = "macos", target_os = "windows", target_os = "ios"))]
249+
pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
250+
{
251+
let https_connector = Connector::builder()
252+
.https()
253+
.build()
254+
.map_err(ClientInitError::SslError)?;
255+
Self::try_new_with_connector(base_path, Some("https"), https_connector)
256+
}
257+
258+
/// Create a client with a TLS connection to the server using OpenSSL.
259+
///
260+
/// # Arguments
261+
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
262+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
256263
pub fn try_new_https(base_path: &str) -> Result<Self, ClientInitError>
257264
{
258265
let https_connector = Connector::builder()
@@ -265,9 +272,9 @@ impl<C> Client<
265272
/// Create a client with a TLS connection to the server using a pinned certificate
266273
///
267274
/// # Arguments
268-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
275+
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
269276
/// * `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"))))]
277+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
271278
pub fn try_new_https_pinned<CA>(
272279
base_path: &str,
273280
ca_certificate: CA,
@@ -286,11 +293,11 @@ impl<C> Client<
286293
/// Create a client with a mutually authenticated TLS connection to the server.
287294
///
288295
/// # Arguments
289-
/// * `base_path` - base path of the client API, i.e. "<http://www.my-api-implementation.com>"
296+
/// * `base_path` - base path of the client API, i.e. "<https://www.my-api-implementation.com>"
290297
/// * `ca_certificate` - Path to CA certificate used to authenticate the server
291298
/// * `client_key` - Path to the client private key
292299
/// * `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"))))]
300+
#[cfg(not(any(target_os = "macos", target_os = "windows", target_os = "ios")))]
294301
pub fn try_new_https_mutual<CA, K, D>(
295302
base_path: &str,
296303
ca_certificate: CA,
@@ -356,7 +363,7 @@ pub enum ClientInitError {
356363
SslError(native_tls::Error),
357364
358365
/// SSL Connection Error
359-
#[cfg(all(feature = "client-openssl", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
366+
#[cfg(all(feature = "client-tls", not(any(target_os = "macos", target_os = "windows", target_os = "ios"))))]
360367
SslError(openssl::error::ErrorStack),
361368
}
362369

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,33 @@ fn main() {
115115
let context: ClientContext =
116116
swagger::make_context!(ContextBuilder, EmptyContext, auth_data, XSpanIdString::default());
117117

118-
let mut client : Box<dyn ApiNoContext<ClientContext>> = if is_https {
119-
// Using Simple HTTPS
120-
let client = Box::new(Client::try_new_https(&base_url)
121-
.expect("Failed to create HTTPS client"));
122-
Box::new(client.with_context(context))
123-
} else {
124-
// Using HTTP
125-
let client = Box::new(Client::try_new_http(
126-
&base_url)
127-
.expect("Failed to create HTTP client"));
128-
Box::new(client.with_context(context))
118+
let mut client : Box<dyn ApiNoContext<ClientContext>> = {
119+
#[cfg(feature = "client-tls")]
120+
{
121+
if is_https {
122+
// Using HTTPS with native-tls
123+
let client = Box::new(Client::try_new_https(&base_url)
124+
.expect("Failed to create HTTPS client"));
125+
Box::new(client.with_context(context))
126+
} else {
127+
// Using HTTP
128+
let client = Box::new(Client::try_new_http(&base_url)
129+
.expect("Failed to create HTTP client"));
130+
Box::new(client.with_context(context))
131+
}
132+
}
133+
134+
#[cfg(not(feature = "client-tls"))]
135+
{
136+
if is_https {
137+
panic!("HTTPS requested but TLS support not enabled. \
138+
Enable the 'client-tls' feature to use HTTPS.");
139+
}
140+
// Using HTTP only
141+
let client = Box::new(Client::try_new_http(&base_url)
142+
.expect("Failed to create HTTP client"));
143+
Box::new(client.with_context(context))
144+
}
129145
};
130146

131147
let mut rt = tokio::runtime::Runtime::new().unwrap();

0 commit comments

Comments
 (0)