#[cfg(feature = "defmt_0_3")]
use defmt_0_3::Format;
#[cfg(feature = "defmt_0_3")]
use defmt_0_3 as defmt;
#[derive(Debug)]
#[cfg_attr(feature = "defmt_0_3", derive(Format))]
pub struct Error {
code: u8,
#[cfg(feature = "error_unprocessed_coap_option")]
unprocessed_option: Option<core::num::NonZeroU16>,
#[cfg(feature = "error_request_body_error_position")]
request_body_error_position: Option<u32>,
#[cfg(feature = "error_max_age")]
max_age: Option<u32>,
#[allow(dead_code)]
#[cfg(any(feature = "error_title", debug_assertions))]
title: Option<&'static str>,
}
impl Error {
const MAX_ENCODED_LEN: usize = {
let mut count = 0;
count += 1; if cfg!(feature = "error_unprocessed_coap_option") {
count += 1 + 3;
}
if cfg!(feature = "error_request_body_error_position") {
count += 2 + 5;
}
count
};
pub fn bad_option(unprocessed_option: u16) -> Self {
let special_code = match unprocessed_option {
coap_numbers::option::ACCEPT => Some(coap_numbers::code::NOT_ACCEPTABLE),
coap_numbers::option::PROXY_URI | coap_numbers::option::PROXY_SCHEME => {
Some(coap_numbers::code::PROXYING_NOT_SUPPORTED)
}
coap_numbers::option::CONTENT_FORMAT => {
Some(coap_numbers::code::UNSUPPORTED_CONTENT_FORMAT)
}
coap_numbers::option::URI_PATH => Some(coap_numbers::code::NOT_FOUND),
_ => None,
};
#[allow(unused)]
let unprocessed_option = if special_code.is_some() {
None
} else {
core::num::NonZeroU16::try_from(unprocessed_option).ok()
};
let code = special_code.unwrap_or(coap_numbers::code::BAD_OPTION);
#[allow(clippy::needless_update)]
Self {
code,
#[cfg(feature = "error_unprocessed_coap_option")]
unprocessed_option,
..Self::otherwise_empty()
}
}
pub fn bad_request_with_rbep(#[allow(unused)] byte: usize) -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::BAD_REQUEST,
#[cfg(feature = "error_request_body_error_position")]
request_body_error_position: byte.try_into().ok(),
..Self::otherwise_empty()
}
}
#[inline(always)]
fn problem_details_count(&self) -> u8 {
#[allow(unused_mut)]
let mut count = 0;
#[cfg(feature = "error_unprocessed_coap_option")]
if self.unprocessed_option.is_some() {
count += 1;
}
#[cfg(feature = "error_request_body_error_position")]
if self.request_body_error_position.is_some() {
count += 1;
}
count
}
pub fn from_unionerror(
_err: impl core::fmt::Debug + coap_message::error::RenderableOnMinimal,
) -> Self {
Self::internal_server_error()
}
pub fn bad_request() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::BAD_REQUEST,
..Self::otherwise_empty()
}
}
pub fn unauthorized() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::UNAUTHORIZED,
..Self::otherwise_empty()
}
}
pub fn forbidden() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::FORBIDDEN,
..Self::otherwise_empty()
}
}
pub fn not_found() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::NOT_FOUND,
..Self::otherwise_empty()
}
}
pub fn method_not_allowed() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::METHOD_NOT_ALLOWED,
..Self::otherwise_empty()
}
}
pub fn not_acceptable() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::NOT_ACCEPTABLE,
..Self::otherwise_empty()
}
}
pub fn unsupported_content_format() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::UNSUPPORTED_CONTENT_FORMAT,
..Self::otherwise_empty()
}
}
pub fn internal_server_error() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::INTERNAL_SERVER_ERROR,
..Self::otherwise_empty()
}
}
pub fn service_unavailable() -> Self {
#[allow(clippy::needless_update)]
Self {
code: coap_numbers::code::SERVICE_UNAVAILABLE,
..Self::otherwise_empty()
}
}
#[cfg(feature = "error_max_age")]
pub fn with_max_age(self, max_age: u32) -> Self {
Self {
max_age: Some(max_age),
..self
}
}
#[allow(unused_variables)]
pub fn with_title(self, title: &'static str) -> Self {
Self {
#[cfg(any(feature = "error_title", debug_assertions))]
title: Some(title),
..self
}
}
#[inline]
fn otherwise_empty() -> Self {
Self {
code: 0,
#[cfg(feature = "error_unprocessed_coap_option")]
unprocessed_option: None,
#[cfg(feature = "error_request_body_error_position")]
request_body_error_position: None,
#[cfg(feature = "error_max_age")]
max_age: None,
#[cfg(any(feature = "error_title", debug_assertions))]
title: None,
}
}
}
impl coap_message::error::RenderableOnMinimal for Error {
type Error<IE: coap_message::error::RenderableOnMinimal + core::fmt::Debug> = IE;
fn render<M: coap_message::MinimalWritableMessage>(
self,
message: &mut M,
) -> Result<(), Self::Error<M::UnionError>> {
use coap_message::{Code, OptionNumber};
message.set_code(M::Code::new(self.code)?);
let mut pd_count = self.problem_details_count();
if pd_count > 0 {
const PROBLEM_DETAILS: Option<u16> =
coap_numbers::content_format::from_str("application/concise-problem-details+cbor");
let cfopt = M::OptionNumber::new(coap_numbers::option::CONTENT_FORMAT);
if let Some((pd, cfopt)) = PROBLEM_DETAILS.zip(cfopt.ok()) {
if message.add_option_uint(cfopt, pd).is_err() {
pd_count = 0;
}
}
};
#[cfg(feature = "error_max_age")]
if let Some(max_age) = self.max_age {
message.add_option_uint(
M::OptionNumber::new(coap_numbers::option::MAX_AGE)?,
max_age,
)?;
}
let encode = |mut cursor: minicbor::encode::write::Cursor<_>, try_include_title| {
let mut encoder = minicbor::Encoder::new(&mut cursor);
#[cfg(feature = "error_title")]
let extra_length_for_title = u64::from(try_include_title && self.title.is_some());
#[cfg(not(feature = "error_title"))]
let extra_length_for_title = 0;
#[cfg(not(feature = "error_title"))]
let _ = try_include_title;
#[allow(unused_mut)]
let mut encoder = encoder.map(u64::from(pd_count) + extra_length_for_title)?;
#[cfg(feature = "error_unprocessed_coap_option")]
if let Some(unprocessed_option) = self.unprocessed_option {
encoder = encoder.i8(-8)?;
encoder = encoder.u16(unprocessed_option.into())?;
}
#[cfg(feature = "error_request_body_error_position")]
if let Some(position) = self.request_body_error_position {
encoder = encoder.i8(-25)?;
encoder = encoder.u32(position)?;
}
#[cfg(feature = "error_title")]
if try_include_title {
if let Some(title) = self.title {
encoder = encoder.i8(-1)?;
encoder = encoder.str(title)?;
}
}
let _ = encoder;
let written = cursor.position();
Ok(written)
};
if pd_count > 0 {
if let Some(message) = message.promote_to_mutable_writable_message() {
use coap_message::MutableWritableMessage;
let max_len = Self::MAX_ENCODED_LEN;
#[cfg(feature = "error_title")]
let max_len = max_len
+ match self.title {
Some(t) => 1 + 5 + t.len(),
None => 0,
};
let payload = message.payload_mut_with_len(max_len)?;
let cursor = minicbor::encode::write::Cursor::new(payload);
if let Ok::<_, minicbor::encode::Error<_>>(written) = encode(cursor, true) {
message.truncate(written)?;
} else {
message.truncate(0)?;
}
} else {
let mut buf = [0u8; Self::MAX_ENCODED_LEN];
let cursor = minicbor::encode::write::Cursor::new(buf.as_mut());
if let Ok::<_, minicbor::encode::Error<_>>(written) = encode(cursor, false) {
message.set_payload(&buf[..written])?;
}
}
} else {
#[cfg(feature = "error_title")]
if let Some(title) = self.title {
message.set_payload(title.as_bytes())?;
}
}
Ok(())
}
}