diff --git a/src/http/client.rs b/src/http/client.rs index a0a8c4e..bf06b8c 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -30,12 +30,11 @@ pub enum HttpClientAction { }, } -/// HTTP Request type that can be shared over Wasm boundary to apps. -/// This is the one you send to the `http-client:distro:sys` service. +/// HTTP Request type contained in [`HttpClientAction::Http`]. /// /// BODY is stored in the lazy_load_blob, as bytes /// -/// TIMEOUT is stored in the message expect_response value +/// TIMEOUT is stored in the message's `expects_response` value #[derive(Clone, Debug, Serialize, Deserialize)] pub struct OutgoingHttpRequest { /// must parse to [`http::Method`] @@ -63,32 +62,38 @@ pub enum HttpClientRequest { /// [`crate::Response`] type received from the `http-client:distro:sys` service after /// sending a successful [`HttpClientAction`] to it. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub enum HttpClientResponse { Http(HttpResponse), WebSocketAck, } -#[derive(Error, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, Serialize, Deserialize)] pub enum HttpClientError { // HTTP errors - #[error("http-client: request is not valid HttpClientRequest: {req}.")] - BadRequest { req: String }, - #[error("http-client: http method not supported: {method}.")] + #[error("request could not be deserialized to valid HttpClientRequest")] + MalformedRequest, + #[error("http method not supported: {method}")] BadMethod { method: String }, - #[error("http-client: url could not be parsed: {url}.")] + #[error("url could not be parsed: {url}")] BadUrl { url: String }, - #[error("http-client: http version not supported: {version}.")] + #[error("http version not supported: {version}")] BadVersion { version: String }, - #[error("http-client: failed to execute request {error}.")] - RequestFailed { error: String }, + #[error("client failed to build request: {0}")] + BuildRequestFailed(String), + #[error("client failed to execute request: {0}")] + ExecuteRequestFailed(String), // WebSocket errors - #[error("websocket_client: failed to open connection {url}.")] + #[error("could not open connection to {url}")] WsOpenFailed { url: String }, - #[error("websocket_client: failed to send message {req}.")] - WsPushFailed { req: String }, - #[error("websocket_client: failed to close connection {channel_id}.")] + #[error("sent WebSocket push to unknown channel {channel_id}")] + WsPushUnknownChannel { channel_id: u32 }, + #[error("WebSocket push failed because message had no blob attached")] + WsPushNoBlob, + #[error("WebSocket push failed because message type was Text, but blob was not a valid UTF-8 string")] + WsPushBadText, + #[error("failed to close connection {channel_id} because it was not open")] WsCloseFailed { channel_id: u32 }, } @@ -141,17 +146,15 @@ pub fn send_request_await_response( url: url.to_string(), headers: headers.unwrap_or_default(), })) - .map_err(|e| HttpClientError::BadRequest { - req: format!("{e:?}"), - })?, + .map_err(|_| HttpClientError::MalformedRequest)?, ) .blob_bytes(body) .send_and_await_response(timeout) .unwrap(); let Ok(Message::Response { body, .. }) = res else { - return Err(HttpClientError::RequestFailed { - error: "http-client timed out".to_string(), - }); + return Err(HttpClientError::ExecuteRequestFailed( + "http-client timed out".to_string(), + )); }; let resp = match serde_json::from_slice::< std::result::Result, @@ -159,15 +162,15 @@ pub fn send_request_await_response( { Ok(Ok(HttpClientResponse::Http(resp))) => resp, Ok(Ok(HttpClientResponse::WebSocketAck)) => { - return Err(HttpClientError::RequestFailed { - error: "http-client gave unexpected response".to_string(), - }) + return Err(HttpClientError::ExecuteRequestFailed( + "http-client gave unexpected response".to_string(), + )) } Ok(Err(e)) => return Err(e), Err(e) => { - return Err(HttpClientError::RequestFailed { - error: format!("http-client gave invalid response: {e:?}"), - }) + return Err(HttpClientError::ExecuteRequestFailed(format!( + "http-client gave invalid response: {e:?}" + ))) } }; let mut http_response = http::Response::builder() @@ -175,14 +178,10 @@ pub fn send_request_await_response( let headers = http_response.headers_mut().unwrap(); for (key, value) in &resp.headers { let Ok(key) = http::header::HeaderName::from_str(key) else { - return Err(HttpClientError::RequestFailed { - error: format!("http-client gave invalid header key: {key}"), - }); + continue; }; let Ok(value) = http::header::HeaderValue::from_str(value) else { - return Err(HttpClientError::RequestFailed { - error: format!("http-client gave invalid header value: {value}"), - }); + continue; }; headers.insert(key, value); } diff --git a/src/http/server.rs b/src/http/server.rs index 872675b..bc4346a 100644 --- a/src/http/server.rs +++ b/src/http/server.rs @@ -268,14 +268,16 @@ impl HttpResponse { /// Part of the [`crate::Response`] type issued by http-server #[derive(Clone, Debug, Error, Serialize, Deserialize)] pub enum HttpServerError { - #[error("request could not be parsed to HttpServerAction: {req}.")] - BadRequest { req: String }, - #[error("action expected lazy_load_blob")] + #[error("request could not be deserialized to valid HttpServerRequest")] + MalformedRequest, + #[error("action expected blob")] NoBlob, - #[error("path binding error: {error}")] - PathBindError { error: String }, - #[error("WebSocket error: {error}")] - WebSocketPushError { error: String }, + #[error("path binding error: invalid source process")] + InvalidSourceProcess, + #[error("WebSocket error: ping/pong message too long")] + WsPingPongTooLong, + #[error("WebSocket error: channel not found")] + WsChannelNotFound, /// Not actually issued by `http-server:distro:sys`, just this library #[error("timeout")] Timeout, @@ -683,9 +685,7 @@ impl HttpServer { let entry = self .http_paths .get_mut(path) - .ok_or(HttpServerError::PathBindError { - error: "path not found".to_string(), - })?; + .ok_or(HttpServerError::MalformedRequest)?; let res = KiRequest::to(("our", "http-server", "distro", "sys")) .body( serde_json::to_vec(&HttpServerAction::Bind { @@ -722,9 +722,7 @@ impl HttpServer { let entry = self .ws_paths .get_mut(path) - .ok_or(HttpServerError::PathBindError { - error: "path not found".to_string(), - })?; + .ok_or(HttpServerError::MalformedRequest)?; let res = KiRequest::to(("our", "http-server", "distro", "sys")) .body(if entry.secure_subdomain { serde_json::to_vec(&HttpServerAction::WebSocketSecureBind { @@ -826,7 +824,7 @@ impl HttpServer { ), action: VfsAction::Read, }) - .map_err(|e| HttpServerError::BadRequest { req: e.to_string() })?, + .map_err(|_| HttpServerError::MalformedRequest)?, ) .send_and_await_response(self.timeout) .unwrap(); @@ -861,7 +859,7 @@ impl HttpServer { path: file_path.to_string(), action: VfsAction::Read, }) - .map_err(|e| HttpServerError::BadRequest { req: e.to_string() })?, + .map_err(|_| HttpServerError::MalformedRequest)?, ) .send_and_await_response(self.timeout) .unwrap(); @@ -911,9 +909,7 @@ impl HttpServer { .send_and_await_response(self.timeout) .unwrap() else { - return Err(HttpServerError::PathBindError { - error: format!("no ui directory to serve: {initial_path}"), - }); + return Err(HttpServerError::MalformedRequest); }; let directory_body = serde_json::from_slice::(directory_response.body()) @@ -976,7 +972,7 @@ impl HttpServer { pub fn parse_request(&self, body: &[u8]) -> Result { let request = serde_json::from_slice::(body) - .map_err(|e| HttpServerError::BadRequest { req: e.to_string() })?; + .map_err(|_| HttpServerError::MalformedRequest)?; Ok(request) } diff --git a/src/vfs/directory.rs b/src/vfs/directory.rs index 2069876..ceebbf9 100644 --- a/src/vfs/directory.rs +++ b/src/vfs/directory.rs @@ -15,10 +15,7 @@ impl Directory { let message = vfs_request(&self.path, VfsAction::ReadDir) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::ReadDir(entries) => Ok(entries), @@ -39,17 +36,13 @@ pub fn open_dir(path: &str, create: bool, timeout: Option) -> Result { if m.file_type != FileType::Directory { - return Err(VfsError::IOError { - error: "Entry at path not a directory".to_string(), - path: path.to_string(), - }); + return Err(VfsError::IOError( + "entry at path is not a directory".to_string(), + )); } } VfsResponse::Err(e) => return Err(e), @@ -70,10 +63,7 @@ pub fn open_dir(path: &str, create: bool, timeout: Option) -> Result Ok(Directory { @@ -95,10 +85,7 @@ pub fn remove_dir(path: &str, timeout: Option) -> Result<(), VfsError> { let message = vfs_request(path, VfsAction::RemoveDir) .send_and_await_response(timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: path.to_string(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), diff --git a/src/vfs/file.rs b/src/vfs/file.rs index 8a6ad07..a0b25ed 100644 --- a/src/vfs/file.rs +++ b/src/vfs/file.rs @@ -26,10 +26,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::Read) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Read => { @@ -58,10 +55,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::Read) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Read => { @@ -86,10 +80,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::ReadExact { length }) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Read => { @@ -112,10 +103,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::ReadToEnd) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Read => Ok(get_blob().unwrap_or_default().bytes), @@ -134,10 +122,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::ReadToString) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::ReadToString(s) => Ok(s), @@ -156,10 +141,7 @@ impl File { .blob_bytes(buffer) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), @@ -177,10 +159,7 @@ impl File { .blob_bytes(buffer) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), @@ -198,10 +177,7 @@ impl File { .blob_bytes(buffer) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), @@ -219,10 +195,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::Seek(pos)) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::SeekFrom { @@ -246,10 +219,7 @@ impl File { ) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(File { @@ -269,10 +239,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::SetLen(size)) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), @@ -289,10 +256,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::Metadata) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Metadata(metadata) => Ok(metadata), @@ -309,10 +273,7 @@ impl File { let message = vfs_request(&self.path, VfsAction::SyncAll) .send_and_await_response(self.timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: self.path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), @@ -347,10 +308,7 @@ pub fn create_drive( let message = vfs_request(&path, VfsAction::CreateDrive) .send_and_await_response(timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: path.clone(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(path), @@ -369,10 +327,7 @@ pub fn open_file(path: &str, create: bool, timeout: Option) -> Result Ok(File { @@ -394,10 +349,7 @@ pub fn create_file(path: &str, timeout: Option) -> Result { let message = vfs_request(path, VfsAction::CreateFile) .send_and_await_response(timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: path.to_string(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(File { @@ -419,10 +371,7 @@ pub fn remove_file(path: &str, timeout: Option) -> Result<(), VfsError> { let message = vfs_request(path, VfsAction::RemoveFile) .send_and_await_response(timeout) .unwrap() - .map_err(|e| VfsError::IOError { - error: e.to_string(), - path: path.to_string(), - })?; + .map_err(|e| VfsError::SendError(e.kind))?; match parse_response(message.body())? { VfsResponse::Ok => Ok(()), diff --git a/src/vfs/mod.rs b/src/vfs/mod.rs index f6b6319..0022731 100644 --- a/src/vfs/mod.rs +++ b/src/vfs/mod.rs @@ -12,7 +12,7 @@ pub use file::*; #[derive(Debug, Serialize, Deserialize)] pub struct VfsRequest { /// path is always prepended by [`crate::PackageId`], the capabilities of the topmost folder are checked - /// "/your_package:publisher.os/drive_folder/another_folder_or_file" + /// "/your-package:publisher.os/drive_folder/another_folder_or_file" pub path: String, pub action: VfsAction, } @@ -87,42 +87,27 @@ pub enum VfsResponse { Hash([u8; 32]), } -#[derive(Error, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Error, Serialize, Deserialize)] pub enum VfsError { - #[error("vfs: No capability for action {action} at path {path}")] - NoCap { action: String, path: String }, - #[error("vfs: Bytes blob required for {action} at path {path}")] - BadBytes { action: String, path: String }, - #[error("vfs: bad request error: {error}")] - BadRequest { error: String }, - #[error("vfs: error parsing path: {path}, error: {error}")] + #[error("no write capability for requested drive")] + NoWriteCap, + #[error("no read capability for requested drive")] + NoReadCap, + #[error("failed to generate capability for new drive")] + AddCapFailed, + #[error("request could not be deserialized to valid VfsRequest")] + MalformedRequest, + #[error("request type used requires a blob")] + NoBlob, + #[error("error parsing path: {path}: {error}")] ParseError { error: String, path: String }, - #[error("vfs: IO error: {error}, at path {path}")] - IOError { error: String, path: String }, - #[error("vfs: kernel capability channel error: {error}")] - CapChannelFail { error: String }, - #[error("vfs: Bad JSON blob: {error}")] - BadJson { error: String }, - #[error("vfs: File not found at path {path}")] - NotFound { path: String }, - #[error("vfs: Creating directory failed at path: {path}: {error}")] - CreateDirError { path: String, error: String }, -} - -impl VfsError { - pub fn kind(&self) -> &str { - match *self { - VfsError::NoCap { .. } => "NoCap", - VfsError::BadBytes { .. } => "BadBytes", - VfsError::BadRequest { .. } => "BadRequest", - VfsError::ParseError { .. } => "ParseError", - VfsError::IOError { .. } => "IOError", - VfsError::CapChannelFail { .. } => "CapChannelFail", - VfsError::BadJson { .. } => "NoJson", - VfsError::NotFound { .. } => "NotFound", - VfsError::CreateDirError { .. } => "CreateDirError", - } - } + #[error("IO error: {0}")] + IOError(String), + #[error("non-file non-dir in zip")] + UnzipError, + /// Not actually issued by `vfs:distro:sys`, just this library + #[error("SendError")] + SendError(crate::SendErrorKind), } pub fn vfs_request(path: T, action: VfsAction) -> Request @@ -145,10 +130,7 @@ pub fn metadata(path: &str, timeout: Option) -> Result Ok(metadata), @@ -175,7 +157,5 @@ pub fn remove_path(path: &str, timeout: Option) -> Result<(), VfsError> { } pub fn parse_response(body: &[u8]) -> Result { - serde_json::from_slice::(body).map_err(|e| VfsError::BadJson { - error: e.to_string(), - }) + serde_json::from_slice::(body).map_err(|_| VfsError::MalformedRequest) }