coap-numbers 0.2.9

Constants for the CoAP protocol
Documentation
//! Constants and functions for CoAP codes
//!
//! This contains codes for all ranges -- request, response and signalling.
//!
//! Codes are expressed as u8 values in accordance with their serialized form.

/// Format a CoAP code in dotted notation
///
/// This prints the class number of a code, followed by a dot and the two-digit detail, into a
/// formatter. That format is the common human-readable expression of CoAP codes, as it eases the
/// comparison to the (dot-less) codes of HTTP.
///
/// It is typically used to easily implement the Display trait on types that contain a code:
///
/// ```
/// # use coap_numbers::code::*;
/// struct Code(u8);
///
/// impl core::fmt::Display for Code {
///     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
///         format_dotted(self.0, f)?;
///         if let Some(name) = to_name(self.0) {
///             write!(f, " {}", name)?;
///         }
///         Ok(())
///     }
/// }
///
/// let g = format!("{}", Code(GET));
/// assert_eq!(g, "0.01 GET");
/// ```
///
/// # More formatting
///
/// This crate just provides very minimal formatting helpers in this function and in
/// [`to_dotted()`]. More elaborate and ergonomic tools are available through the
/// [`coap_message_utils::ShowCodeExt`](https://docs.rs/coap-message-utils/latest/coap_message_utils/trait.ShowCodeExt.html)
/// trait.
///
/// # Errors
///
/// Like the [formatting traits](https://doc.rust-lang.org/std/fmt/index.html#formatting-traits),
/// this only errs if the unerlying formatter errs.
pub fn format_dotted(code: u8, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    write!(f, "{}.{:02}", code >> 5, code & 0x1f)
}

/// Convert a CoAP code to dotted notation
///
/// This expresses the class number of a code, followed by a dot and the two-digit detail, into a
/// String. That format is the common human-readable expression of CoAP codes, as it eases the
/// comparison to the (dot-less) codes of HTTP.
///
/// Example:
///
/// ```
/// # use coap_numbers::code::*;
/// let g: String = to_dotted(GET);
/// assert_eq!(g, "0.01");
/// let d = to_dotted(PRECONDITION_FAILED);
/// assert_eq!(d, "4.12");
/// ```
///
/// This is equivalent in functionality to [`format_dotted`](fn.format_dotted.html), which may be
/// used as a replacement in ``no_std`` settings.
#[cfg(feature = "alloc")]
pub fn to_dotted(code: u8) -> alloc::string::String {
    // It's easier to copy this down from format_dotted than to fill a String
    alloc::format!("{}.{:02}", code >> 5, code & 0x1f)
}

/// Classification of CoAP codes used in responses
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Class {
    /// 2.xx Success codes
    Success,
    /// 4.xx Client Error codes
    ClientError,
    /// 5.xx Server Error codes
    ServerError,
}

/// Classification of CoAP codes in any message
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Range {
    /// The 0.00 Empty code
    Empty,
    /// A request code (0.01 to 0.31)
    Request,
    /// A response code (2.00 to 5.31 excluding 3.xx)
    ///
    /// 3.xx codes will be classified in here when they are assigned and get a name
    Response(Class),
    /// A signalling code (7.xx)
    Signaling,
    /// Any other range
    ///
    /// Try not to match for this classification, as codes that are recognized as Reserved in one
    /// version of coap-numbers may move into a newly created class later on.
    Reserved,
}

/// Find which range a code is in
///
/// ```
/// # use coap_numbers::code::*;
/// assert_eq!(classify(GET), Range::Request);
/// assert_eq!(classify(INTERNAL_SERVER_ERROR), Range::Response(Class::ServerError));
///
/// # let code = CONTENT;
/// match classify(code) {
///     Range::Response(Class::Success) => println!("Processing response"),
///     Range::Response(_) => println!("Some error, probably"),
///     _ => println!("Protocol violation"),
/// }
/// ```
#[inline]
#[must_use]
pub fn classify(code: u8) -> Range {
    match code {
        0 => Range::Empty,
        0x01..=0x1f => Range::Request,
        0x40..=0x5f => Range::Response(Class::Success),
        0x80..=0x9f => Range::Response(Class::ClientError),
        0xa0..=0xbf => Range::Response(Class::ServerError),
        0xe0..=0xff => Range::Signaling,
        _ => Range::Reserved,
    }
}

macro_rules! code {
    ( $class:tt . $detail:tt ) => {
        ($class << 5) + $detail
    };
}

// FIXME: Generate documentaton at least saying which class the identifier is, and group them
// visually (or at least keep them sorted)
macro_rules! codes {
    ( $( $name:tt $constname:ident  $class:tt $detail:expr ) , * ) => { $(
            #[doc=$name]
            pub const $constname: u8 = code!($class.$detail);
        )*

        /// Find the name for a CoAP code, if any is known
        ///
        /// Returns the registered name for a code, or None if the code is not known.
        ///
        /// ```
        /// # use coap_numbers::code::*;
        /// assert_eq!(to_name(HOP_LIMIT_REACHED), Some("Hop Limit Reached"));
        /// assert_eq!(to_name(0x31), None);
        /// ```
        #[must_use]
        pub fn to_name(code: u8) -> Option<&'static str> {
            match code {
                $(
                $constname => Some($name),
                )*
                _ => None
            }
        }
    }
}

codes!(
    // Not formally registered, but special by having its own single-item code range
    "Empty" EMPTY 0 0,

    "GET" GET 0 1,
    "POST" POST 0 2,
    "PUT" PUT 0 3,
    "DELETE" DELETE 0 4,
    "FETCH" FETCH 0 5,
    "PATCH" PATCH 0 6,
    "iPATCH" IPATCH 0 7,

    "Created" CREATED 2 1,
    "Deleted" DELETED 2 2,
    "Valid" VALID 2 3,
    "Changed" CHANGED 2 4,
    "Content" CONTENT 2 5,
    "Continue" CONTINUE 2 31,
    "Bad Request" BAD_REQUEST 4 0,
    "Unauthorized" UNAUTHORIZED 4 1,
    "Bad Option" BAD_OPTION 4 2,
    "Forbidden" FORBIDDEN 4 3,
    "Not Found" NOT_FOUND 4 4,
    "Method Not Allowed" METHOD_NOT_ALLOWED 4 5,
    "Not Acceptable" NOT_ACCEPTABLE 4 6,
    "Request Entity Incomplete" REQUEST_ENTITY_INCOMPLETE 4 8,
    "Conflict" CONFLICT 4 9,
    "Precondition Failed" PRECONDITION_FAILED 4 12,
    "Request Entity Too Large" REQUEST_ENTITY_TOO_LARGE 4 13,
    "Unsupported Content-Format" UNSUPPORTED_CONTENT_FORMAT 4 15,
    "Unprocessable Entity" UNPROCESSABLE_ENTITY 4 22,
    "Too Many Requests" TOO_MANY_REQUESTS 4 29,
    "Internal Server Error" INTERNAL_SERVER_ERROR 5 0,
    "Not Implemented" NOT_IMPLEMENTED 5 1,
    "Bad Gateway" BAD_GATEWAY 5 2,
    "Service Unavailable" SERVICE_UNAVAILABLE 5 3,
    "Gateway Timeout" GATEWAY_TIMEOUT 5 4,
    "Proxying Not Supported" PROXYING_NOT_SUPPORTED 5 5,
    "Hop Limit Reached" HOP_LIMIT_REACHED 5 8,

    "CSM" CSM 7 1,
    "Ping" PING 7 2,
    "Pong" PONG 7 3,
    "Release" RELEASE 7 4,
    "Abort" ABORT 7 5
);