e173 0.1.0-alpha.7

A library implementing various aspects of the ESTA BSR E1.73 standard
Documentation
use core::result;
#[cfg(target_arch = "wasm32")]
use serde::{ser::SerializeStruct, Serialize, Serializer};
use thiserror::Error;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsValue;

pub type Result<T> = result::Result<T, Error>;

#[derive(Error, Debug)]
/// Errors that can occur while validating and transforming E1.73 data
pub enum Error {
    #[error("Invalid argument provided to parsing function")]
    InvalidArgument,
    #[error("Error parsing UDR Document")]
    ParseError { description: String },
    #[error("Error parsing UDR Document from JSON")]
    JsonParseError { source: serde_json::Error },
    #[error("UDR Document was invalid")]
    InvalidDocument {
        description: String,
        json_path: String,
    },
    #[error("UDR Library was invalid")]
    InvalidLibrary {
        description: String,
        json_path: String,
    },
    #[error("UDR Device Class was invalid")]
    InvalidDeviceClass {
        description: String,
        json_path: String,
    },
    #[error("UDR System was invalid")]
    InvalidSystem {
        description: String,
        json_path: String,
    },
    #[error("Error parsing ESTA DMX serializer")]
    DmxParseError { source: serde_json::Error },
    #[error("ESTA DMX serializer was invalid")]
    InvalidDmx {
        description: String,
        json_path: String,
    },
}

impl Error {
    #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
    pub(crate) fn parse(description: impl Into<String>) -> Self {
        Self::ParseError {
            description: description.into(),
        }
    }

    #[cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
    pub(crate) fn json_parse(source: serde_json::Error) -> Self {
        Self::JsonParseError { source }
    }

    pub(crate) fn invalid_document(
        description: impl Into<String>,
        json_path: impl Into<String>,
    ) -> Self {
        Self::InvalidDocument {
            description: description.into(),
            json_path: json_path.into(),
        }
    }

    pub(crate) fn invalid_library(
        description: impl Into<String>,
        json_path: impl Into<String>,
    ) -> Self {
        Self::InvalidLibrary {
            description: description.into(),
            json_path: json_path.into(),
        }
    }

    pub(crate) fn invalid_device_class(
        description: impl Into<String>,
        json_path: impl Into<String>,
    ) -> Self {
        Self::InvalidDeviceClass {
            description: description.into(),
            json_path: json_path.into(),
        }
    }

    pub(crate) fn invalid_system(
        description: impl Into<String>,
        json_path: impl Into<String>,
    ) -> Self {
        Self::InvalidSystem {
            description: description.into(),
            json_path: json_path.into(),
        }
    }

    pub(crate) fn dmx_parse(source: serde_json::Error) -> Self {
        Self::DmxParseError { source }
    }

    pub(crate) fn invalid_dmx(
        description: impl Into<String>,
        json_path: impl Into<String>,
    ) -> Self {
        Self::InvalidDmx {
            description: description.into(),
            json_path: json_path.into(),
        }
    }
}

// Represents the JS-serialized value of ControllerError. Unused in Rust, but useful for JS.
#[cfg(target_arch = "wasm32")]
mod wasm {
    use crate::tsify::Tsify;
    use serde::Serialize;

    #[allow(dead_code)]
    #[derive(Tsify, Serialize)]
    pub enum ErrorType {
        InvalidArgument,
        ParseError,
        JsonParseError,
        InvalidDocument,
        InvalidDeviceClass,
        InvalidLibrary,
        InvalidDmx,
        DmxParseError,
    }

    #[allow(dead_code)]
    #[derive(Tsify, Serialize)]
    pub struct Error {
        r#type: ErrorType,
        description: String,
        #[serde(skip_serializing_if = "Option::is_none")]
        path: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        line: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        column: Option<String>,
    }
}

// Manual impl needed so that we can serialize serde_json::Error
// TODO: it may be possible to go back to serde derive and a tagged enum if we create a newtype
// for serde_json::Error and use #[serde(transparent)]?
#[cfg(target_arch = "wasm32")]
impl Serialize for Error {
    fn serialize<S>(&self, serializer: S) -> result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        match self {
            Self::InvalidArgument => {
                let mut state = serializer.serialize_struct("ControllerError", 2)?;
                state.serialize_field("type", "InvalidArgument")?;
                state.serialize_field("description", "Invalid argument provided to function")?;
                state.end()
            }
            Self::ParseError { description } => {
                let mut state = serializer.serialize_struct("ControllerError", 2)?;
                state.serialize_field("type", "ParseError")?;
                state.serialize_field("description", description)?;
                state.end()
            }
            Self::JsonParseError { source } => {
                serialize_serde_json_err(serializer, "ParseError", source)
            }
            Self::InvalidDocument {
                description,
                json_path,
            } => {
                let mut state = serializer.serialize_struct("ControllerError", 3)?;
                state.serialize_field("type", "InvalidDocument")?;
                state.serialize_field("description", description)?;
                state.serialize_field("path", json_path)?;
                state.end()
            }
            Self::InvalidLibrary {
                description,
                json_path,
            } => {
                let mut state = serializer.serialize_struct("ControllerError", 3)?;
                state.serialize_field("type", "InvalidLibrary")?;
                state.serialize_field("description", description)?;
                state.serialize_field("path", json_path)?;
                state.end()
            }
            Self::InvalidDeviceClass {
                description,
                json_path,
            } => {
                let mut state = serializer.serialize_struct("ControllerError", 3)?;
                state.serialize_field("type", "InvalidDeviceClass")?;
                state.serialize_field("description", description)?;
                state.serialize_field("path", json_path)?;
                state.end()
            }
            Self::InvalidSystem {
                description,
                json_path,
            } => {
                let mut state = serializer.serialize_struct("ControllerError", 3)?;
                state.serialize_field("type", "InvalidSystem")?;
                state.serialize_field("description", description)?;
                state.serialize_field("path", json_path)?;
                state.end()
            }
            Self::InvalidDmx {
                description,
                json_path,
            } => {
                let mut state = serializer.serialize_struct("ControllerError", 3)?;
                state.serialize_field("type", "InvalidDmx")?;
                state.serialize_field("description", description)?;
                state.serialize_field("path", json_path)?;
                state.end()
            }
            Self::DmxParseError { source } => {
                serialize_serde_json_err(serializer, "DmxParseError", source)
            }
        }
    }
}

#[cfg(target_arch = "wasm32")]
fn serialize_serde_json_err<S>(
    serializer: S,
    variant: &str,
    err: &serde_json::Error,
) -> result::Result<S::Ok, S::Error>
where
    S: Serializer,
{
    let mut state = serializer.serialize_struct("ControllerError", 4)?;
    state.serialize_field("type", variant)?;
    state.serialize_field("description", &format!("{}", err))?;
    state.serialize_field("line", &err.line())?;
    state.serialize_field("column", &err.column())?;
    state.end()
}

#[cfg(target_arch = "wasm32")]
impl From<Error> for JsValue {
    fn from(value: Error) -> Self {
        serde_wasm_bindgen::to_value(&value).unwrap()
    }
}