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
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 39 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,48 @@ codegen-units = 1 # Maximize optimization for production
strip = true # Strip debug symbols
panic = "abort" # Remove unwinding code to further reduce size

[workspace.lints.clippy]
# Enable lint groups
pedantic = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
# Suppressed pedantic/nursery lints
missing_errors_doc = "allow"
module_name_repetitions = "allow"
option_if_let_else = "allow"
redundant_pub_crate = "allow"
similar_names = "allow"
struct_excessive_bools = "allow"
struct_field_names = "allow"
too_many_lines = "allow"
# Enforced restriction lints (cherry-picked)
map_err_ignore = "warn"
# Suppressed restriction lints (off by default, listed to document decisions)
absolute_paths = "allow"
arbitrary_source_item_ordering = "allow"
doc_paragraphs_missing_punctuation = "allow"
exhaustive_enums = "allow"
exhaustive_structs = "allow"
implicit_return = "allow"
mem_forget = "allow"
min_ident_chars = "allow"
missing_docs_in_private_items = "allow"
missing_inline_in_public_items = "allow"
mod_module_files = "allow"
pattern_type_mismatch = "allow"
pub_use = "allow"
pub_with_shorthand = "allow"
single_call_fn = "allow"
std_instead_of_core = "allow"
str_to_string = "allow"

[workspace.lints.rust.unexpected_cfgs]
level = "warn"
check-cfg = ['cfg(feature, values("facet_typegen"))']

[workspace.package]
authors = ["omnect@conplement.de"]
edition = "2024"
homepage = "https://www.omnect.io/home"
license = "MIT OR Apache-2.0"
repository = "git@github.com:omnect/omnect-ui.git"
version = "1.2.0"
version = "1.2.1"
4 changes: 2 additions & 2 deletions src/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ name = "omnect_ui_core"
[features]
typegen = ["crux_core/typegen", "crux_http/typegen", "crux_time/typegen"]

[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("facet_typegen"))'] }
[lints]
workspace = true

[dependencies]
base64 = { version = "0.22", default-features = false, features = ["alloc"] }
Expand Down
3 changes: 2 additions & 1 deletion src/app/src/commands/websocket.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@ where
}
}

/// Build the request into a Command RequestBuilder
/// Build the request into a Command `RequestBuilder`
#[must_use]
pub fn build(
self,
) -> command::RequestBuilder<Effect, Event, impl Future<Output = WebSocketOutput>> {
Expand Down
87 changes: 43 additions & 44 deletions src/app/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use serde::{Deserialize, Serialize};
use std::fmt;

use crate::types::*;
use crate::types::{
AuthToken, HealthcheckInfo, UpdateManifest, WifiAvailability, WifiSavedNetworksResponse,
WifiScanResultsResponse, WifiStatusResponse,
};

/// Authentication events
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum AuthEvent {
Login {
password: String,
Expand Down Expand Up @@ -106,7 +109,7 @@ pub enum WebSocketEvent {
Disconnected,
}

/// WiFi management events
/// `WiFi` management events
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum WifiEvent {
// User actions
Expand Down Expand Up @@ -144,41 +147,37 @@ pub enum WifiEvent {
ForgetNetworkResponse(Result<(), String>),
}

/// Custom Debug for WifiEvent to redact password
/// Custom Debug for `WifiEvent` to redact password
impl fmt::Debug for WifiEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WifiEvent::Connect { ssid, .. } => f
Self::Connect { ssid, .. } => f
.debug_struct("Connect")
.field("ssid", ssid)
.field("password", &"<redacted>")
.finish(),
WifiEvent::CheckAvailability => write!(f, "CheckAvailability"),
WifiEvent::Scan => write!(f, "Scan"),
WifiEvent::Disconnect => write!(f, "Disconnect"),
WifiEvent::GetStatus => write!(f, "GetStatus"),
WifiEvent::GetSavedNetworks => write!(f, "GetSavedNetworks"),
WifiEvent::ForgetNetwork { ssid } => {
Self::CheckAvailability => write!(f, "CheckAvailability"),
Self::Scan => write!(f, "Scan"),
Self::Disconnect => write!(f, "Disconnect"),
Self::GetStatus => write!(f, "GetStatus"),
Self::GetSavedNetworks => write!(f, "GetSavedNetworks"),
Self::ForgetNetwork { ssid } => {
f.debug_struct("ForgetNetwork").field("ssid", ssid).finish()
}
WifiEvent::ScanPollTick => write!(f, "ScanPollTick"),
WifiEvent::ConnectPollTick => write!(f, "ConnectPollTick"),
WifiEvent::CheckAvailabilityResponse(r) => {
Self::ScanPollTick => write!(f, "ScanPollTick"),
Self::ConnectPollTick => write!(f, "ConnectPollTick"),
Self::CheckAvailabilityResponse(r) => {
f.debug_tuple("CheckAvailabilityResponse").field(r).finish()
}
WifiEvent::ScanResponse(r) => f.debug_tuple("ScanResponse").field(r).finish(),
WifiEvent::ScanResultsResponse(r) => {
f.debug_tuple("ScanResultsResponse").field(r).finish()
}
WifiEvent::ConnectResponse(r) => f.debug_tuple("ConnectResponse").field(r).finish(),
WifiEvent::DisconnectResponse(r) => {
f.debug_tuple("DisconnectResponse").field(r).finish()
}
WifiEvent::StatusResponse(r) => f.debug_tuple("StatusResponse").field(r).finish(),
WifiEvent::SavedNetworksResponse(r) => {
Self::ScanResponse(r) => f.debug_tuple("ScanResponse").field(r).finish(),
Self::ScanResultsResponse(r) => f.debug_tuple("ScanResultsResponse").field(r).finish(),
Self::ConnectResponse(r) => f.debug_tuple("ConnectResponse").field(r).finish(),
Self::DisconnectResponse(r) => f.debug_tuple("DisconnectResponse").field(r).finish(),
Self::StatusResponse(r) => f.debug_tuple("StatusResponse").field(r).finish(),
Self::SavedNetworksResponse(r) => {
f.debug_tuple("SavedNetworksResponse").field(r).finish()
}
WifiEvent::ForgetNetworkResponse(r) => {
Self::ForgetNetworkResponse(r) => {
f.debug_tuple("ForgetNetworkResponse").field(r).finish()
}
}
Expand All @@ -200,7 +199,7 @@ pub enum UiEvent {
}

/// Main event enum - wraps domain events
#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
pub enum Event {
Initialize,
Auth(AuthEvent),
Expand All @@ -210,24 +209,24 @@ pub enum Event {
Wifi(WifiEvent),
}

/// Custom Debug implementation for AuthEvent to redact sensitive data
/// Custom Debug implementation for `AuthEvent` to redact sensitive data
impl fmt::Debug for AuthEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthEvent::Login { .. } => f
Self::Login { .. } => f
.debug_struct("Login")
.field("password", &"<redacted>")
.finish(),
AuthEvent::SetPassword { .. } => f
Self::SetPassword { .. } => f
.debug_struct("SetPassword")
.field("password", &"<redacted>")
.finish(),
AuthEvent::UpdatePassword { .. } => f
Self::UpdatePassword { .. } => f
.debug_struct("UpdatePassword")
.field("current_password", &"<redacted>")
.field("password", &"<redacted>")
.finish(),
AuthEvent::LoginResponse(result) => match result {
Self::LoginResponse(result) => match result {
Ok(_) => f
.debug_tuple("LoginResponse")
.field(&"Ok(<redacted token>)")
Expand All @@ -237,11 +236,11 @@ impl fmt::Debug for AuthEvent {
.field(&format!("Err({e})"))
.finish(),
},
AuthEvent::Logout => write!(f, "Logout"),
AuthEvent::CheckRequiresPasswordSet => write!(f, "CheckRequiresPasswordSet"),
AuthEvent::RestoreSession(_) => write!(f, "RestoreSession(<redacted token>)"),
AuthEvent::LogoutResponse(r) => f.debug_tuple("LogoutResponse").field(r).finish(),
AuthEvent::SetPasswordResponse(result) => match result {
Self::Logout => write!(f, "Logout"),
Self::CheckRequiresPasswordSet => write!(f, "CheckRequiresPasswordSet"),
Self::RestoreSession(_) => write!(f, "RestoreSession(<redacted token>)"),
Self::LogoutResponse(r) => f.debug_tuple("LogoutResponse").field(r).finish(),
Self::SetPasswordResponse(result) => match result {
Ok(_) => f
.debug_tuple("SetPasswordResponse")
.field(&"Ok(<redacted token>)")
Expand All @@ -251,10 +250,10 @@ impl fmt::Debug for AuthEvent {
.field(&format!("Err({e})"))
.finish(),
},
AuthEvent::UpdatePasswordResponse(r) => {
Self::UpdatePasswordResponse(r) => {
f.debug_tuple("UpdatePasswordResponse").field(r).finish()
}
AuthEvent::CheckRequiresPasswordSetResponse(r) => f
Self::CheckRequiresPasswordSetResponse(r) => f
.debug_tuple("CheckRequiresPasswordSetResponse")
.field(r)
.finish(),
Expand All @@ -266,12 +265,12 @@ impl fmt::Debug for AuthEvent {
impl fmt::Debug for Event {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Event::Initialize => write!(f, "Initialize"),
Event::Auth(e) => write!(f, "Auth({e:?})"),
Event::Device(e) => write!(f, "Device({e:?})"),
Event::WebSocket(e) => write!(f, "WebSocket({e:?})"),
Event::Ui(e) => write!(f, "Ui({e:?})"),
Event::Wifi(e) => write!(f, "Wifi({e:?})"),
Self::Initialize => write!(f, "Initialize"),
Self::Auth(e) => write!(f, "Auth({e:?})"),
Self::Device(e) => write!(f, "Device({e:?})"),
Self::WebSocket(e) => write!(f, "WebSocket({e:?})"),
Self::Ui(e) => write!(f, "Ui({e:?})"),
Self::Wifi(e) => write!(f, "Wifi({e:?})"),
}
}
}
23 changes: 14 additions & 9 deletions src/app/src/http_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,23 @@ pub const BASE_URL: &str = "https://relative";
/// let url = build_url("/api/device/reboot");
/// assert_eq!(url, "https://relative/api/device/reboot");
/// ```
#[must_use]
pub fn build_url(endpoint: &str) -> String {
format!("{BASE_URL}{endpoint}")
}

/// Validates HTTP response.
///
/// Returns `true` if the response status is 2xx.
#[must_use]
pub fn is_response_success(response: &Response<Vec<u8>>) -> bool {
response.status().is_success()
}

/// Extracts error message from successful HTTP response.
///
/// This is used when an API returns a 2xx status but indicates failure in the body,
/// or when manually processing non-2xx responses that were not caught as Errors by crux_http.
/// or when manually processing non-2xx responses that were not caught as Errors by `crux_http`.
pub fn extract_error_message(action: &str, response: &mut Response<Vec<u8>>) -> String {
let status = response.status();
let status_str = status.to_string();
Expand Down Expand Up @@ -121,9 +123,12 @@ pub fn extract_string_response(
}

match response.take_body() {
Some(bytes) => {
String::from_utf8(bytes).map_err(|_| format!("{action}: Invalid UTF-8 in response"))
}
Some(bytes) => String::from_utf8(bytes).map_err(|e| {
format!(
"{action}: Invalid UTF-8 in response at byte {}",
e.utf8_error().valid_up_to()
)
}),
None => Err(format!("{action}: Empty response body")),
}
}
Expand All @@ -135,7 +140,7 @@ pub fn process_status_response(
) -> Result<(), String> {
match result {
Ok(mut response) => check_response_status(action, &mut response),
Err(e) => Err(map_http_error(action, e)),
Err(e) => Err(map_http_error(action, &e)),
}
}

Expand All @@ -146,15 +151,15 @@ pub fn process_json_response<T: serde::de::DeserializeOwned>(
) -> Result<T, String> {
match result {
Ok(mut response) => parse_json_response(action, &mut response),
Err(e) => Err(map_http_error(action, e)),
Err(e) => Err(map_http_error(action, &e)),
}
}

pub fn map_http_error(action: &str, e: HttpError) -> String {
#[must_use]
pub fn map_http_error(action: &str, e: &HttpError) -> String {
match e {
HttpError::Http {
body: Some(ref body),
..
body: Some(body), ..
} => match String::from_utf8(body.clone()) {
Ok(msg) => msg,
Err(_) => format!("{action} failed: {e}"),
Expand Down
10 changes: 5 additions & 5 deletions src/app/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ macro_rules! unauth_post {
builder.build().then_send(|result| {
let event_result: Result<$response_type, String> = match result {
Ok(mut response) => $crate::parse_json_response($action, &mut response),
Err(e) => Err($crate::map_http_error($action, e)),
Err(e) => Err($crate::map_http_error($action, &e)),
};
$crate::events::Event::$domain($crate::events::$domain_event::$response_event(
event_result,
Expand All @@ -143,7 +143,7 @@ macro_rules! unauth_post {
builder.build().then_send(|result| {
let event_result = match result {
Ok(mut response) => $crate::check_response_status($action, &mut response),
Err(e) => Err($crate::map_http_error($action, e)),
Err(e) => Err($crate::map_http_error($action, &e)),
};
$crate::events::Event::$domain($crate::events::$domain_event::$response_event(
event_result,
Expand All @@ -170,7 +170,7 @@ macro_rules! unauth_post {
Ok(mut response) => {
$crate::extract_string_response($action, &mut response).map($mapper)
}
Err(e) => Err($crate::map_http_error($action, e)),
Err(e) => Err($crate::map_http_error($action, &e)),
};
$crate::events::Event::$domain($crate::events::$domain_event::$response_event(
event_result,
Expand All @@ -193,7 +193,7 @@ macro_rules! unauth_post {
.then_send(|result| {
let event_result: Result<$response_type, String> = match result {
Ok(mut response) => $crate::parse_json_response($action, &mut response),
Err(e) => Err($crate::map_http_error($action, e)),
Err(e) => Err($crate::map_http_error($action, &e)),
};
$crate::events::Event::$domain($crate::events::$domain_event::$response_event(
event_result,
Expand Down Expand Up @@ -232,7 +232,7 @@ macro_rules! auth_post_basic {
Ok(mut response) => {
$crate::extract_string_response($action, &mut response).map($mapper)
}
Err(e) => Err($crate::map_http_error($action, e)),
Err(e) => Err($crate::map_http_error($action, &e)),
};
$crate::events::Event::$domain($crate::events::$domain_event::$response_event(
event_result,
Expand Down
Loading
Loading