From 604ca46ba6c1de7d33d75f09cfe611a90815cb21 Mon Sep 17 00:00:00 2001 From: Mathias Date: Thu, 18 Jul 2024 10:15:26 +0200 Subject: [PATCH] Bump embedded-mqtt --- Cargo.toml | 4 +- rust-toolchain.toml | 6 +- src/fmt.rs | 85 ++++++++++----- src/jobs/mod.rs | 4 +- src/jobs/update.rs | 36 ++---- src/ota/control_interface/mqtt.rs | 22 ++-- src/ota/data_interface/mod.rs | 2 +- src/ota/data_interface/mqtt.rs | 6 +- src/ota/error.rs | 2 +- src/ota/mod.rs | 176 +++++++++++++++--------------- src/provisioning/mod.rs | 4 +- src/shadows/error.rs | 122 ++++++++++----------- 12 files changed, 243 insertions(+), 226 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7619b16..c27aacc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,7 @@ serde_cbor = { version = "0.11", default-features = false, optional = true } serde-json-core = { version = "0.5" } shadow-derive = { path = "shadow_derive", version = "0.2.1" } embedded-storage-async = "0.4" -embedded-mqtt = { git = "ssh://git@github.com/BlackbirdHQ/embedded-mqtt/", rev = "dbf8af0", features = [ - "defmt", -] } +embedded-mqtt = { git = "ssh://git@github.com/BlackbirdHQ/embedded-mqtt/", rev = "d766137" } futures = { version = "0.3.28", default-features = false } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 17dc494..1368141 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,8 +1,8 @@ [toolchain] -channel = "1.78" -components = [ "rust-src", "rustfmt", "llvm-tools", "clippy" ] +channel = "1.79" +components = ["rust-src", "rustfmt", "llvm-tools"] targets = [ "x86_64-unknown-linux-gnu", + "thumbv6m-none-eabi", "thumbv7em-none-eabihf", - "thumbv6m-none-eabi" ] diff --git a/src/fmt.rs b/src/fmt.rs index c06793e..35b929f 100644 --- a/src/fmt.rs +++ b/src/fmt.rs @@ -1,31 +1,12 @@ -// MIT License - -// Copyright (c) 2020 Dario Nieuwenhuis - -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in all -// copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - #![macro_use] -#![allow(unused_macros)] +#![allow(unused)] + +use core::fmt::{Debug, Display, LowerHex}; #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); +#[collapse_debuginfo(yes)] macro_rules! assert { ($($x:tt)*) => { { @@ -37,6 +18,7 @@ macro_rules! assert { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_eq { ($($x:tt)*) => { { @@ -48,6 +30,7 @@ macro_rules! assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! assert_ne { ($($x:tt)*) => { { @@ -59,6 +42,7 @@ macro_rules! assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert { ($($x:tt)*) => { { @@ -70,6 +54,7 @@ macro_rules! debug_assert { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_eq { ($($x:tt)*) => { { @@ -81,6 +66,7 @@ macro_rules! debug_assert_eq { }; } +#[collapse_debuginfo(yes)] macro_rules! debug_assert_ne { ($($x:tt)*) => { { @@ -92,6 +78,7 @@ macro_rules! debug_assert_ne { }; } +#[collapse_debuginfo(yes)] macro_rules! todo { ($($x:tt)*) => { { @@ -103,17 +90,23 @@ macro_rules! todo { }; } +#[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unreachable { ($($x:tt)*) => { - { - #[cfg(not(feature = "defmt"))] - ::core::unreachable!($($x)*); - #[cfg(feature = "defmt")] - ::defmt::unreachable!($($x)*); - } + ::core::unreachable!($($x)*) }; } +#[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +#[collapse_debuginfo(yes)] macro_rules! panic { ($($x:tt)*) => { { @@ -125,6 +118,7 @@ macro_rules! panic { }; } +#[collapse_debuginfo(yes)] macro_rules! trace { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -138,6 +132,7 @@ macro_rules! trace { }; } +#[collapse_debuginfo(yes)] macro_rules! debug { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -151,6 +146,7 @@ macro_rules! debug { }; } +#[collapse_debuginfo(yes)] macro_rules! info { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -164,6 +160,7 @@ macro_rules! info { }; } +#[collapse_debuginfo(yes)] macro_rules! warn { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -177,6 +174,7 @@ macro_rules! warn { }; } +#[collapse_debuginfo(yes)] macro_rules! error { ($s:literal $(, $x:expr)* $(,)?) => { { @@ -191,6 +189,7 @@ macro_rules! error { } #[cfg(feature = "defmt")] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($($x:tt)*) => { ::defmt::unwrap!($($x)*) @@ -198,6 +197,7 @@ macro_rules! unwrap { } #[cfg(not(feature = "defmt"))] +#[collapse_debuginfo(yes)] macro_rules! unwrap { ($arg:expr) => { match $crate::fmt::Try::into_result($arg) { @@ -245,3 +245,30 @@ impl Try for Result { self } } + +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index 246d94b..8f35c71 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -262,7 +262,7 @@ impl Jobs { Describe::new() } - pub fn update(job_id: &str, status: JobStatus) -> Update { - Update::new(job_id, status) + pub fn update<'a>(status: JobStatus) -> Update<'a> { + Update::new(status) } } diff --git a/src/jobs/update.rs b/src/jobs/update.rs index db34d90..3875bd0 100644 --- a/src/jobs/update.rs +++ b/src/jobs/update.rs @@ -1,8 +1,6 @@ use serde::Serialize; -use crate::jobs::{ - data_types::JobStatus, JobTopic, MAX_CLIENT_TOKEN_LEN, MAX_JOB_ID_LEN, MAX_THING_NAME_LEN, -}; +use crate::jobs::{data_types::JobStatus, MAX_CLIENT_TOKEN_LEN}; use super::{JobError, StatusDetailsOwned}; @@ -69,7 +67,6 @@ pub struct UpdateJobExecutionRequest<'a> { } pub struct Update<'a> { - job_id: &'a str, status: JobStatus, client_token: Option<&'a str>, status_details: Option<&'a StatusDetailsOwned>, @@ -81,11 +78,8 @@ pub struct Update<'a> { } impl<'a> Update<'a> { - pub fn new(job_id: &'a str, status: JobStatus) -> Self { - assert!(job_id.len() < MAX_JOB_ID_LEN); - + pub fn new(status: JobStatus) -> Self { Self { - job_id, status, status_details: None, include_job_document: false, @@ -148,17 +142,7 @@ impl<'a> Update<'a> { } } - pub fn topic_payload( - self, - client_id: &str, - buf: &mut [u8], - ) -> Result< - ( - heapless::String<{ MAX_THING_NAME_LEN + MAX_JOB_ID_LEN + 25 }>, - usize, - ), - JobError, - > { + pub fn payload(self, buf: &mut [u8]) -> Result { let payload_len = serde_json_core::to_slice( &UpdateJobExecutionRequest { execution_number: self.execution_number, @@ -174,15 +158,14 @@ impl<'a> Update<'a> { ) .map_err(|_| JobError::Encoding)?; - Ok(( - JobTopic::Update(self.job_id).format(client_id)?, - payload_len, - )) + Ok(payload_len) } } #[cfg(test)] mod test { + use crate::jobs::JobTopic; + use super::*; use serde_json_core::to_string; @@ -207,14 +190,17 @@ mod test { #[test] fn topic_payload() { let mut buf = [0u8; 512]; - let (topic, payload_len) = Update::new("test_job_id", JobStatus::Failed) + let topic = JobTopic::Update("test_job_id") + .format::<64>("test_client") + .unwrap(); + let payload_len = Update::new(JobStatus::Failed) .client_token("test_client:token_update") .step_timeout_in_minutes(50) .execution_number(5) .expected_version(2) .include_job_document() .include_job_execution_state() - .topic_payload("test_client", &mut buf) + .payload(&mut buf) .unwrap(); assert_eq!(&buf[..payload_len], br#"{"executionNumber":5,"expectedVersion":2,"includeJobDocument":true,"includeJobExecutionState":true,"status":"FAILED","stepTimeoutInMinutes":50,"clientToken":"test_client:token_update"}"#); diff --git a/src/ota/control_interface/mqtt.rs b/src/ota/control_interface/mqtt.rs index 4259db2..094ce16 100644 --- a/src/ota/control_interface/mqtt.rs +++ b/src/ota/control_interface/mqtt.rs @@ -1,11 +1,11 @@ use core::fmt::Write; use embassy_sync::blocking_mutex::raw::RawMutex; -use embedded_mqtt::{Publish, QoS}; +use embedded_mqtt::{DeferredPayload, EncodingError, Publish, QoS}; use super::ControlInterface; use crate::jobs::data_types::JobStatus; -use crate::jobs::Jobs; +use crate::jobs::{JobTopic, Jobs, MAX_JOB_ID_LEN, MAX_THING_NAME_LEN}; use crate::ota::config::Config; use crate::ota::encoding::json::JobStatusReason; use crate::ota::encoding::FileContext; @@ -93,11 +93,17 @@ impl<'a, M: RawMutex, const SUBS: usize> ControlInterface } } - // FIXME: Serialize directly into the publish payload through `DeferredPublish` API - let mut buf = [0u8; 512]; - let (topic, payload_len) = Jobs::update(file_ctx.job_name.as_str(), status) - .status_details(&file_ctx.status_details) - .topic_payload(self.client_id(), &mut buf)?; + let topic = JobTopic::Update(file_ctx.job_name.as_str()) + .format::<{ MAX_THING_NAME_LEN + MAX_JOB_ID_LEN + 25 }>(self.client_id())?; + let payload = DeferredPayload::new( + |buf| { + Jobs::update(status) + .status_details(&file_ctx.status_details) + .payload(buf) + .map_err(|_| EncodingError::BufferSize) + }, + 512, + ); self.publish(Publish { dup: false, @@ -105,7 +111,7 @@ impl<'a, M: RawMutex, const SUBS: usize> ControlInterface retain: false, pid: None, topic_name: &topic, - payload: &buf[..payload_len], + payload, properties: embedded_mqtt::Properties::Slice(&[]), }) .await?; diff --git a/src/ota/data_interface/mod.rs b/src/ota/data_interface/mod.rs index 3723c69..cfb9cee 100644 --- a/src/ota/data_interface/mod.rs +++ b/src/ota/data_interface/mod.rs @@ -45,7 +45,7 @@ impl<'a> FileBlock<'a> { } pub trait BlockTransfer { - async fn next_block(&mut self) -> Result, OtaError>; + async fn next_block(&mut self) -> Result>, OtaError>; } pub trait DataInterface { diff --git a/src/ota/data_interface/mqtt.rs b/src/ota/data_interface/mqtt.rs index 1f32995..f40f31a 100644 --- a/src/ota/data_interface/mqtt.rs +++ b/src/ota/data_interface/mqtt.rs @@ -125,8 +125,8 @@ impl<'a> OtaTopic<'a> { } impl<'a, 'b, M: RawMutex, const SUBS: usize> BlockTransfer for Subscription<'a, 'b, M, SUBS, 1> { - async fn next_block(&mut self) -> Result, OtaError> { - Ok(self.next().await.ok_or(OtaError::Encoding)?) + async fn next_block(&mut self) -> Result>, OtaError> { + Ok(self.next().await) } } @@ -179,7 +179,7 @@ impl<'a, M: RawMutex, const SUBS: usize> DataInterface for MqttClient<'a, M, SUB }, buf, ) - .map_err(|e| EncodingError::BufferSize) + .map_err(|_| EncodingError::BufferSize) }, 32, ); diff --git a/src/ota/error.rs b/src/ota/error.rs index 8fdf6e6..8c5744b 100644 --- a/src/ota/error.rs +++ b/src/ota/error.rs @@ -27,7 +27,7 @@ pub enum OtaError { impl OtaError { pub fn is_retryable(&self) -> bool { - matches!(self, Self::Encoding) + matches!(self, Self::Encoding | Self::Timeout) } } diff --git a/src/ota/mod.rs b/src/ota/mod.rs index c962b54..338320c 100644 --- a/src/ota/mod.rs +++ b/src/ota/mod.rs @@ -227,113 +227,113 @@ impl Updater { info!("Initialized file handler! Requesting file blocks"); - // Prepare the storage layer on receiving a new file - let mut subscription = data.init_file_transfer(&mut file_ctx).await?; + loop { + // Prepare the storage layer on receiving a new file + let mut subscription = data.init_file_transfer(&mut file_ctx).await?; - data.request_file_block(&mut file_ctx, &config).await?; + data.request_file_block(&mut file_ctx, &config).await?; - info!("Awaiting file blocks!"); + info!("Awaiting file blocks!"); - while let Ok(mut payload) = subscription.next_block().await { - debug!("process_data_handler"); - // Decode the file block received - match Self::ingest_data_block( - data, - block_writer, - &config, - &mut file_ctx, - payload.deref_mut(), - ) - .await - { - Ok(true) => match pal.close_file(&file_ctx).await { - Err(e) => { + while let Some(mut payload) = subscription.next_block().await? { + debug!("process_data_handler"); + // Decode the file block received + match Self::ingest_data_block( + data, + block_writer, + &config, + &mut file_ctx, + payload.deref_mut(), + ) + .await + { + Ok(true) => match pal.close_file(&file_ctx).await { + Err(e) => { + control + .update_job_status( + &mut file_ctx, + &config, + JobStatus::Failed, + JobStatusReason::Pal(0), + ) + .await?; + + return Err(e.into()); + } + Ok(_) => { + let (status, reason, event) = if let Some(0) = file_ctx.file_type { + ( + JobStatus::InProgress, + JobStatusReason::SigCheckPassed, + pal::OtaEvent::Activate, + ) + } else { + ( + JobStatus::Succeeded, + JobStatusReason::Accepted, + pal::OtaEvent::UpdateComplete, + ) + }; + + control + .update_job_status(&mut file_ctx, &config, status, reason) + .await?; + + return Ok(event); + } + }, + Ok(false) => { + debug!("Ingested one block!"); + // Reset the momentum counter since we received a good block + request_momentum.store(0, Ordering::Relaxed); + + // We're actively receiving a file so update the job status as + // needed control .update_job_status( &mut file_ctx, &config, - JobStatus::Failed, - JobStatusReason::Pal(0), + JobStatus::InProgress, + JobStatusReason::Receiving, ) .await?; - return Err(e.into()); - } - Ok(_) => { - let (status, reason, event) = if let Some(0) = file_ctx.file_type { - ( - JobStatus::InProgress, - JobStatusReason::SigCheckPassed, - pal::OtaEvent::Activate, - ) + if file_ctx.request_block_remaining > 1 { + file_ctx.request_block_remaining -= 1; } else { - ( - JobStatus::Succeeded, - JobStatusReason::Accepted, - pal::OtaEvent::UpdateComplete, - ) - }; + data.request_file_block(&mut file_ctx, &config).await?; + } + } + Err(e) if e.is_retryable() => { + warn!("Failed to ingest data block, Error is retryable! ingest_data_block returned error {:?}", e); + } + Err(e) => { + error!("Failed to ingest data block, rejecting image: ingest_data_block returned error {:?}", e); + + // Call the platform specific code to reject the image + // TODO: This should never write to current image flags?! + // pal.set_platform_image_state(ImageState::Rejected( + // ImageStateReason::FailedIngest, + // )) + // .await?; + // TODO: Pal reason control - .update_job_status(&mut file_ctx, &config, status, reason) + .update_job_status( + &mut file_ctx, + &config, + JobStatus::Failed, + JobStatusReason::Pal(0), + ) .await?; - return Ok(event); - } - }, - Ok(false) => { - debug!("Ingested one block!"); - // Reset the momentum counter since we received a good block - request_momentum.store(0, Ordering::Relaxed); - - // We're actively receiving a file so update the job status as - // needed - control - .update_job_status( - &mut file_ctx, - &config, - JobStatus::InProgress, - JobStatusReason::Receiving, - ) - .await?; - - if file_ctx.request_block_remaining > 1 { - file_ctx.request_block_remaining -= 1; - } else { - data.request_file_block(&mut file_ctx, &config).await?; + pal.complete_callback(pal::OtaEvent::Fail).await?; + info!("Application callback! OtaEvent::Fail"); + return Err(e); } } - Err(e) if e.is_retryable() => { - warn!("Failed to ingest data block, Error is retryable! ingest_data_block returned error {:?}", e); - } - Err(e) => { - error!("Failed to ingest data block, rejecting image: ingest_data_block returned error {:?}", e); - - // Call the platform specific code to reject the image - // TODO: This should never write to current image flags?! - // pal.set_platform_image_state(ImageState::Rejected( - // ImageStateReason::FailedIngest, - // )) - // .await?; - - // TODO: Pal reason - control - .update_job_status( - &mut file_ctx, - &config, - JobStatus::Failed, - JobStatusReason::Pal(0), - ) - .await?; - - pal.complete_callback(pal::OtaEvent::Fail).await?; - info!("Application callback! OtaEvent::Fail"); - return Err(e); - } } } - - Err(error::OtaError::Mqtt(embedded_mqtt::Error::EOF)) }; // let (momentum_res, data_res) = embassy_futures::join::join(momentum_fut, data_fut).await; diff --git a/src/provisioning/mod.rs b/src/provisioning/mod.rs index b650a1c..57e1ef5 100644 --- a/src/provisioning/mod.rs +++ b/src/provisioning/mod.rs @@ -234,7 +234,7 @@ impl FleetProvisioner { }])) .await .map_err(|e| { - error!("Failed subscription to RegisterThingAny! {}", e); + error!("Failed subscription to RegisterThingAny! {:?}", e); Error::Mqtt })?; @@ -251,7 +251,7 @@ impl FleetProvisioner { }) .await .map_err(|e| { - error!("Failed publish to RegisterThing! {}", e); + error!("Failed publish to RegisterThing! {:?}", e); Error::Mqtt })?; diff --git a/src/shadows/error.rs b/src/shadows/error.rs index f7cd84b..f08ff4d 100644 --- a/src/shadows/error.rs +++ b/src/shadows/error.rs @@ -98,66 +98,66 @@ impl<'a> TryFrom> for ShadowError { } } -impl Display for ShadowError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::InvalidJson => write!(f, "Invalid JSON"), - Self::MissingState => write!(f, "Missing required node: state"), - Self::MalformedState => write!(f, "State node must be an object"), - Self::MalformedDesired => write!(f, "Desired node must be an object"), - Self::MalformedReported => write!(f, "Reported node must be an object"), - Self::InvalidVersion => write!(f, "Invalid version"), - Self::InvalidClientToken => write!(f, "Invalid clientToken"), - Self::JsonTooDeep => { - write!(f, "JSON contains too many levels of nesting; maximum is 6") - } - Self::InvalidStateNode => write!(f, "State contains an invalid node"), - Self::Unauthorized => write!(f, "Unauthorized"), - Self::Forbidden => write!(f, "Forbidden"), - Self::NotFound => write!(f, "Thing not found"), - Self::NoNamedShadow(shadow_name) => { - write!(f, "No shadow exists with name: {}", shadow_name) - } - Self::VersionConflict => write!(f, "Version conflict"), - Self::PayloadTooLarge => write!(f, "The payload exceeds the maximum size allowed"), - Self::UnsupportedEncoding => write!( - f, - "Unsupported documented encoding; supported encoding is UTF-8" - ), - Self::TooManyRequests => write!(f, "The Device Shadow service will generate this error message when there are more than 10 in-flight requests on a single connection"), - Self::InternalServerError => write!(f, "Internal service failure"), - } - } -} +// impl Display for ShadowError { +// fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { +// match self { +// Self::InvalidJson => write!(f, "Invalid JSON"), +// Self::MissingState => write!(f, "Missing required node: state"), +// Self::MalformedState => write!(f, "State node must be an object"), +// Self::MalformedDesired => write!(f, "Desired node must be an object"), +// Self::MalformedReported => write!(f, "Reported node must be an object"), +// Self::InvalidVersion => write!(f, "Invalid version"), +// Self::InvalidClientToken => write!(f, "Invalid clientToken"), +// Self::JsonTooDeep => { +// write!(f, "JSON contains too many levels of nesting; maximum is 6") +// } +// Self::InvalidStateNode => write!(f, "State contains an invalid node"), +// Self::Unauthorized => write!(f, "Unauthorized"), +// Self::Forbidden => write!(f, "Forbidden"), +// Self::NotFound => write!(f, "Thing not found"), +// Self::NoNamedShadow(shadow_name) => { +// write!(f, "No shadow exists with name: {}", shadow_name) +// } +// Self::VersionConflict => write!(f, "Version conflict"), +// Self::PayloadTooLarge => write!(f, "The payload exceeds the maximum size allowed"), +// Self::UnsupportedEncoding => write!( +// f, +// "Unsupported documented encoding; supported encoding is UTF-8" +// ), +// Self::TooManyRequests => write!(f, "The Device Shadow service will generate this error message when there are more than 10 in-flight requests on a single connection"), +// Self::InternalServerError => write!(f, "Internal service failure"), +// } +// } +// } -// TODO: This seems like an extremely brittle way of doing this??! -impl FromStr for ShadowError { - type Err = (); +// // TODO: This seems like an extremely brittle way of doing this??! +// impl FromStr for ShadowError { +// type Err = (); - fn from_str(s: &str) -> Result { - Ok(match s.trim() { - "Invalid JSON" => Self::InvalidJson, - "Missing required node: state" => Self::MissingState, - "State node must be an object" => Self::MalformedState, - "Desired node must be an object" => Self::MalformedDesired, - "Reported node must be an object" => Self::MalformedReported, - "Invalid version" => Self::InvalidVersion, - "Invalid clientToken" => Self::InvalidClientToken, - "JSON contains too many levels of nesting; maximum is 6" => Self::JsonTooDeep, - "State contains an invalid node" => Self::InvalidStateNode, - "Unauthorized" => Self::Unauthorized, - "Forbidden" => Self::Forbidden, - "Thing not found" => Self::NotFound, - // TODO: - "No shadow exists with name: " => Self::NoNamedShadow(String::new()), - "Version conflict" => Self::VersionConflict, - "The payload exceeds the maximum size allowed" => Self::PayloadTooLarge, - "Unsupported documented encoding; supported encoding is UTF-8" => { - Self::UnsupportedEncoding - } - "The Device Shadow service will generate this error message when there are more than 10 in-flight requests on a single connection" => Self::TooManyRequests, - "Internal service failure" => Self::InternalServerError, - _ => return Err(()), - }) - } -} +// fn from_str(s: &str) -> Result { +// Ok(match s.trim() { +// "Invalid JSON" => Self::InvalidJson, +// "Missing required node: state" => Self::MissingState, +// "State node must be an object" => Self::MalformedState, +// "Desired node must be an object" => Self::MalformedDesired, +// "Reported node must be an object" => Self::MalformedReported, +// "Invalid version" => Self::InvalidVersion, +// "Invalid clientToken" => Self::InvalidClientToken, +// "JSON contains too many levels of nesting; maximum is 6" => Self::JsonTooDeep, +// "State contains an invalid node" => Self::InvalidStateNode, +// "Unauthorized" => Self::Unauthorized, +// "Forbidden" => Self::Forbidden, +// "Thing not found" => Self::NotFound, +// // TODO: +// "No shadow exists with name: " => Self::NoNamedShadow(String::new()), +// "Version conflict" => Self::VersionConflict, +// "The payload exceeds the maximum size allowed" => Self::PayloadTooLarge, +// "Unsupported documented encoding; supported encoding is UTF-8" => { +// Self::UnsupportedEncoding +// } +// "The Device Shadow service will generate this error message when there are more than 10 in-flight requests on a single connection" => Self::TooManyRequests, +// "Internal service failure" => Self::InternalServerError, +// _ => return Err(()), +// }) +// } +// }