Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions docs/src/api/builders.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,11 @@ The `build()` method validates all fields and returns `Result<Settings, Connecti
Convenience functions that wrap the builders:

```rust
use nmrs::builders::{build_wifi_connection, build_ethernet_connection};
use nmrs::builders::{try_build_wifi_connection, build_ethernet_connection};
use nmrs::{WifiSecurity, ConnectionOptions};

// Wi-Fi
let wifi = build_wifi_connection(
let wifi = try_build_wifi_connection(
"MyNetwork",
&WifiSecurity::Open,
&ConnectionOptions::default(),
Expand Down Expand Up @@ -191,11 +191,11 @@ async fn start_hotspot(nm: &NetworkManager, interface: &str) -> nmrs::Result<()>
To persist a profile without bringing it up immediately — the workflow from [#463](https://github.com/freedesktop-rs/nmrs/issues/463):

```rust
use nmrs::builders::build_wifi_connection;
use nmrs::builders::try_build_wifi_connection;
use nmrs::{ConnectionOptions, NetworkManager, WifiSecurity};

let nm = NetworkManager::new().await?;
let settings = build_wifi_connection(
let settings = try_build_wifi_connection(
"GuestWiFi",
&WifiSecurity::WpaPsk { psk: "password".into() },
&ConnectionOptions::new(true),
Expand Down
4 changes: 2 additions & 2 deletions docs/src/api/network-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ let config = nm.timeout_config();
## Saving Profiles Without Activating

```rust
use nmrs::builders::build_wifi_connection;
use nmrs::builders::try_build_wifi_connection;
use nmrs::{ConnectionOptions, NetworkManager, WifiSecurity};

let nm = NetworkManager::new().await?;
let settings = build_wifi_connection(
let settings = try_build_wifi_connection(
"GuestWiFi",
&WifiSecurity::WpaPsk { psk: "password".into() },
&ConnectionOptions::new(true),
Expand Down
79 changes: 40 additions & 39 deletions nmrs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,27 @@ All notable changes to the `nmrs` crate will be documented in this file.

## [Unreleased]

### Changed
### Added

- **Breaking:** WPA-EAP connection builders and `build_wifi_connection()` now
return `Result`, reporting conflicting certificate path/blob inputs as
`ConnectionError::InvalidInput` instead of panicking.
- `try_build_wifi_connection()`, `WifiConnectionBuilder::try_wpa_eap()`, and
`WifiConnectionBuilder::try_wpa3_eap_192_bit()` report conflicting EAP
certificate path/blob inputs as `ConnectionError::InvalidInput`.
([#478](https://github.com/freedesktop-rs/nmrs/issues/478))

### Deprecated

- `build_wifi_connection()`, `WifiConnectionBuilder::wpa_eap()`, and
`WifiConnectionBuilder::wpa3_eap_192_bit()` in favour of their `try_`
counterparts. They keep their existing infallible signatures, so no
existing code breaks. ([#478](https://github.com/freedesktop-rs/nmrs/issues/478))

### Fixed

- Supplying an EAP certificate or key as both a path and a blob no longer
panics. The `try_` builders return `ConnectionError::InvalidInput`; the
deprecated infallible builders use the path and log a warning.
([#478](https://github.com/freedesktop-rs/nmrs/issues/478))

- `get_vpn_info()` no longer surfaces a raw D-Bus error (e.g. "a VPN interface
does not exist") when a VPN is disconnected externally while it is being read.
The active connection and its device can vanish mid-read; missing-object
Expand Down Expand Up @@ -178,12 +190,7 @@ All notable changes to the `nmrs` crate will be documented in this file.
### Fixed

- Add `process` feature to tokio to fix build error on some systems

(No changes documented)

## [3.1.2] - 2026-05-14

- `set_bluetooth_radio_enabled` now toggles kernel rfkill before BlueZ adapter `Powered`, fixing airplane-mode state desync with rfkill-based consumers ([#417](https://github.com/freedesktop-rs/nmrs/issues/418))
- `set_bluetooth_radio_enabled` now toggles kernel rfkill before BlueZ adapter `Powered`, fixing airplane-mode state desync with rfkill-based consumers ([#418](https://github.com/freedesktop-rs/nmrs/issues/418))

## [3.1.1] - 2026-05-13

Expand Down Expand Up @@ -297,6 +304,7 @@ present)` constructor; `RadioState::new` keeps existing behavior and defaults

- Concurrency protection ([#268](https://github.com/freedesktop-rs/nmrs/pull/268))
- Expose `WirelessHardwareEnabled` in API to reflect rkfill state ([#284](https://github.com/freedesktop-rs/nmrs/pull/284))
- `#[must_use]` attributes across public API: constructors, builder methods, and pure functions ([#220](https://github.com/freedesktop-rs/nmrs/issues/220))

### Changed

Expand All @@ -306,12 +314,6 @@ present)` constructor; `RadioState::new` keeps existing behavior and defaults

- Let NetworkManager negotiate mixed-mode (WPA1+WPA2) security ([#271](https://github.com/freedesktop-rs/nmrs/pull/271))

## [2.1.0] - 2026-02-28

### Added

- `#[must_use]` attributes across public API: constructors, builder methods, and pure functions ([#220](https://github.com/freedesktop-rs/nmrs/issues/220))

## [2.0.1] - 2026-02-25

### Changed
Expand Down Expand Up @@ -499,30 +501,29 @@ present)` constructor; `RadioState::new` keeps existing behavior and defaults

- EAP connections default to no certificates (advanced certificate management coming in future releases)

[1.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.1.0...nmrs-v1.2.0
[1.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v1.3.0
[1.3.5]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.3.0...nmrs-v1.3.5
[2.0.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.3.5...nmrs-v2.0.0
[2.0.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.0.0...nmrs-v2.0.1
[2.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.0.1...nmrs-v2.2.0
[2.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.2.0...nmrs-v2.3.0
[2.4.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.3.0...nmrs-v2.4.0
[3.0.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.4.0...nmrs-v3.0.0
[3.0.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.0.0...nmrs-v3.0.1
[3.1.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.0.1...nmrs-v3.1.0
[3.1.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.0...nmrs-v3.1.1
[3.1.2]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.1.2
[3.1.3]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.1.3
[3.1.4]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.1.4
[3.1.5]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.1.5
[3.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.2.0
[3.2.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.2.1
[3.2.2]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.2.2
[3.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.3.0
[3.4.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.4.0
[3.4.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.4.1
[3.4.2]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v3.4.2
[Unreleased]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.4.2...HEAD
[3.4.2]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.4.1...nmrs-v3.4.2
[3.4.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.4.0...nmrs-v3.4.1
[3.4.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.3.0...nmrs-v3.4.0
[3.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.2.2...nmrs-v3.3.0
[3.2.2]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.2.1...nmrs-v3.2.2
[3.2.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.2.0...nmrs-v3.2.1
[3.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.5...nmrs-v3.2.0
[3.1.5]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.4...nmrs-v3.1.5
[3.1.4]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.3...nmrs-v3.1.4
[3.1.3]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.1...nmrs-v3.1.3
[3.1.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.1.0...nmrs-v3.1.1
[3.1.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.0.1...nmrs-v3.1.0
[3.0.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v3.0.0...nmrs-v3.0.1
[3.0.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.4.0...nmrs-v3.0.0
[2.4.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.3.0...nmrs-v2.4.0
[2.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.2.0...nmrs-v2.3.0
[2.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.0.1...nmrs-v2.2.0
[2.0.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v2.0.0...nmrs-v2.0.1
[2.0.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.3.5...nmrs-v2.0.0
[1.3.5]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.3.0...nmrs-v1.3.5
[1.3.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.2.0...nmrs-v1.3.0
[1.2.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.1.0...nmrs-v1.2.0
[1.1.0]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.0.1...nmrs-v1.1.0
[1.0.1]: https://github.com/freedesktop-rs/nmrs/compare/nmrs-v1.0.0...nmrs-v1.0.1
[1.0.0]: https://github.com/freedesktop-rs/nmrs/compare/v0.5.0-beta...nmrs-v1.0.0
Expand Down
11 changes: 7 additions & 4 deletions nmrs/src/api/builders/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
//!
//! # Free functions
//!
//! - [`build_wifi_connection`] / [`build_ethernet_connection`] (in [`wifi`])
//! - [`try_build_wifi_connection`] / [`build_ethernet_connection`] (in [`wifi`])
//! - [`build_wireguard_connection`] / [`build_openvpn_connection`] (in [`vpn`])
//! - [`build_bluetooth_connection`] (in [`bluetooth`])
//! - [`build_vlan_connection`] (in [`vlan`])
Expand All @@ -45,12 +45,12 @@
//! ## Wi-Fi (free function)
//!
//! ```rust
//! use nmrs::builders::{build_ethernet_connection, build_wifi_connection};
//! use nmrs::builders::{build_ethernet_connection, try_build_wifi_connection};
//! use nmrs::{ConnectionOptions, WifiSecurity};
//!
//! let opts = ConnectionOptions::new(true).with_priority(10);
//!
//! let wifi = build_wifi_connection(
//! let wifi = try_build_wifi_connection(
//! "MyNetwork",
//! &WifiSecurity::WpaPsk { psk: "password".into() },
//! &opts,
Expand Down Expand Up @@ -123,4 +123,7 @@ pub use wireguard_builder::WireGuardBuilder;
pub use bluetooth::build_bluetooth_connection;
pub use vlan::build_vlan_connection;
pub use vpn::{build_openvpn_connection, build_wireguard_connection};
pub use wifi::{build_ethernet_connection, build_wifi_connection};
pub use wifi::{build_ethernet_connection, try_build_wifi_connection};
// Re-exported for backward compatibility until the deprecation is removed.
#[allow(deprecated)]
pub use wifi::build_wifi_connection;
81 changes: 67 additions & 14 deletions nmrs/src/api/builders/wifi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,84 @@ use crate::api::models::{self, ConnectionError, ConnectionOptions};
/// This function is maintained for backward compatibility. For new code,
/// consider using `WifiConnectionBuilder` for a more ergonomic API.
///
/// If an EAP certificate or key is supplied as both a path and a blob, the path
/// is used and a warning is logged. Prefer [`try_build_wifi_connection`], which
/// reports the conflict as an error instead.
#[must_use]
#[deprecated(
since = "3.5.0",
note = "use `try_build_wifi_connection`, which reports conflicting EAP certificate path/blob inputs as an error instead of silently preferring the path"
)]
pub fn build_wifi_connection(
ssid: &str,
security: &models::WifiSecurity,
opts: &ConnectionOptions,
) -> HashMap<&'static str, HashMap<&'static str, Value<'static>>> {
// The deprecated EAP builders warn about path/blob conflicts themselves.
#[allow(deprecated)]
let builder = match security {
models::WifiSecurity::Open => base_wifi_builder(ssid, opts).open(),
models::WifiSecurity::WpaPsk { psk } => base_wifi_builder(ssid, opts).wpa_psk(psk),
models::WifiSecurity::WpaEap { opts: eap } => {
base_wifi_builder(ssid, opts).wpa_eap(eap.clone())
}
models::WifiSecurity::Wpa3Eap192bit { opts: eap } => {
base_wifi_builder(ssid, opts).wpa3_eap_192_bit(eap.clone())
}
};

builder.build()
}

/// Builds a complete Wi-Fi connection settings dictionary, reporting invalid
/// EAP input instead of silently resolving it.
///
/// Behaves exactly like [`build_wifi_connection`] for every valid input.
///
/// # Errors
///
/// Returns [`ConnectionError::InvalidInput`] when an EAP certificate or key
/// is supplied as both a path and a blob.
#[must_use = "handle invalid Wi-Fi EAP inputs before using the settings"]
pub fn build_wifi_connection(
///
/// # Example
///
/// ```rust
/// use nmrs::builders::try_build_wifi_connection;
/// use nmrs::{ConnectionOptions, WifiSecurity};
///
/// let settings = try_build_wifi_connection(
/// "MyNetwork",
/// &WifiSecurity::WpaPsk { psk: "password".into() },
/// &ConnectionOptions::new(true),
/// )?;
/// # Ok::<(), nmrs::ConnectionError>(())
/// ```
pub fn try_build_wifi_connection(
ssid: &str,
security: &models::WifiSecurity,
opts: &ConnectionOptions,
) -> Result<HashMap<&'static str, HashMap<&'static str, Value<'static>>>, ConnectionError> {
let mut builder = WifiConnectionBuilder::new(ssid)
.options(opts)
.ipv4_auto()
.ipv6_auto();

builder = match security {
models::WifiSecurity::Open => builder.open(),
models::WifiSecurity::WpaPsk { psk } => builder.wpa_psk(psk),
models::WifiSecurity::WpaEap { opts } => builder.wpa_eap(opts.clone())?,
models::WifiSecurity::Wpa3Eap192bit { opts } => builder.wpa3_eap_192_bit(opts.clone())?,
let base = base_wifi_builder(ssid, opts);

let builder = match security {
models::WifiSecurity::Open => base.open(),
models::WifiSecurity::WpaPsk { psk } => base.wpa_psk(psk),
models::WifiSecurity::WpaEap { opts: eap } => base.try_wpa_eap(eap.clone())?,
models::WifiSecurity::Wpa3Eap192bit { opts: eap } => {
base.try_wpa3_eap_192_bit(eap.clone())?
}
};

Ok(builder.build())
}

fn base_wifi_builder(ssid: &str, opts: &ConnectionOptions) -> WifiConnectionBuilder {
WifiConnectionBuilder::new(ssid)
.options(opts)
.ipv4_auto()
.ipv6_auto()
}

/// Builds a complete Ethernet connection settings dictionary.
///
/// Constructs all required sections for NetworkManager. The returned dictionary
Expand Down Expand Up @@ -124,7 +177,7 @@ mod tests {
security: &WifiSecurity,
opts: &ConnectionOptions,
) -> HashMap<&'static str, HashMap<&'static str, Value<'static>>> {
super::build_wifi_connection(ssid, security, opts).expect("valid Wi-Fi settings")
super::try_build_wifi_connection(ssid, security, opts).expect("valid Wi-Fi settings")
}

fn default_opts() -> ConnectionOptions {
Expand Down Expand Up @@ -160,7 +213,7 @@ mod tests {
opts.ca_cert_path = Some("file:///etc/ssl/certs/ca.pem".into());
opts.ca_cert_blob = Some(vec![1, 2, 3]);

match super::build_wifi_connection(
match super::try_build_wifi_connection(
"enterprise",
&WifiSecurity::WpaEap { opts },
&default_opts(),
Expand Down
Loading
Loading