openprovider 0.1.0

API client for OpenProvider.nl
Documentation

use serde_json::{Value, Map, Number};

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum ValueType {
    Null,
    Bool,
    Number,
    String,
    Array,
    Object,
}

impl ValueType {
    fn describe(&self) -> String {
        match self {
            ValueType::Null => "the null constant".to_string(),
            ValueType::Bool => "a boolean".to_string(),
            ValueType::Number => "a number".to_string(),
            ValueType::String => "a string".to_string(),
            ValueType::Array => "an array".to_string(),
            ValueType::Object => "an object".to_string(),
        }
    }
}

#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum NumberType {
    I64,
    U64,
    U32,
    Float,
}

#[derive(Debug)]
pub enum Error {
    IndexOutOfBounds(usize),
    KeyMissing(String),
    WrongType(Value, ValueType),
    WrongNumberType(Number, NumberType),
}

impl std::error::Error for Error {}

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

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::KeyMissing(key) => write!(f, "key '{}' is missing", key),
            Self::WrongType(_value, expected) => write!(f, "expected {}", expected.describe()),
            Self::WrongNumberType(_value, expected) => write!(f, "expected {}", match expected {
                NumberType::U64 => "an unsigned 64-bit integer",
                NumberType::I64 => "a signed 64-bit integer",
                NumberType::U32 => "an unsigned 32-bit integer",
                NumberType::Float => "a floating point number",
            }),
            Self::IndexOutOfBounds(index) => write!(f, "index {} is out of bounds", index),
        }
    }
}

pub trait IndexExt {
    fn index_into<'v>(&self, v: &'v Value) -> Result<&'v Value>;
}

impl IndexExt for usize {
    fn index_into<'v>(&self, v: &'v Value) -> Result<&'v Value> {
        match v {
            Value::Array(vec) => 
                match vec.get(*self) {
                    None => Err(Error::IndexOutOfBounds(*self)),
                    Some(value) => Ok(value),
                },
            _ => Err(Error::WrongType(v.clone(), ValueType::Array)),
        }
    }
}

impl IndexExt for str {

    fn index_into<'v>(&self, v: &'v Value) -> Result<&'v Value> {
        match v {
            Value::Object(map) =>
                match map.get(self) {
                    None => Err(Error::KeyMissing(self.to_string())),
                    Some(value) => Ok(value),
                },
            _ => Err(Error::WrongType(v.clone(), ValueType::Object)),
        }
    }

}

impl<'a, T> IndexExt for &'a T
where
    T: ?Sized + IndexExt,
{
    fn index_into<'v>(&self, v: &'v Value) -> Result<&'v Value> {
        (**self).index_into(v)
    }
}

pub trait ValueExt {
    fn get_ok<I: IndexExt>(&self, index: I) -> Result<&Value>;
    fn as_object_ok(&self) -> Result<&Map<String, Value>>;
    fn as_array_ok(&self) -> Result<&Vec<Value>>;
    fn as_str_ok(&self) -> Result<&str>;
    fn as_bool_ok(&self) -> Result<bool>;
    fn as_null_ok(&self) -> Result<()>;
    fn as_i64_ok(&self) -> Result<i64>;
    fn as_u64_ok(&self) -> Result<u64>;
    fn as_f64_ok(&self) -> Result<f64>;
    fn as_u32_ok(&self) -> Result<u32>;
}

impl ValueExt for serde_json::Value {

    fn get_ok<I: IndexExt>(&self, index: I) -> Result<&Value> {
        index.index_into(self)
    }

    fn as_object_ok(&self) -> Result<&Map<String, Value>> {
        match self {
            Value::Object(map) => Ok(map),
            _ => Err(Error::WrongType(self.clone(), ValueType::Object)),
        }
    }

    fn as_array_ok(&self) -> Result<&Vec<Value>> {
        match self {
            Value::Array(vec) => Ok(vec),
            _ => Err(Error::WrongType(self.clone(), ValueType::Array)),
        }
    }

    fn as_str_ok(&self) -> Result<&str> {
        match self {
            Value::String(s) => Ok(s),
            _ => Err(Error::WrongType(self.clone(), ValueType::String)),
        }
    }

    fn as_bool_ok(&self) -> Result<bool> {
        match self {
            Value::Bool(b) => Ok(*b),
            _ => Err(Error::WrongType(self.clone(), ValueType::Bool)),
        }
    }

    fn as_null_ok(&self) -> Result<()> {
        match self {
            Value::Null => Ok(()),
            _ => Err(Error::WrongType(self.clone(), ValueType::Null))
        }
    }

    fn as_u64_ok(&self) -> Result<u64> {
        match self {
            Value::Number(num) if num.is_u64() => Ok(num.as_u64().unwrap()),
            Value::Number(num) => Err(Error::WrongNumberType(num.clone(), NumberType::U64)),
            _ => Err(Error::WrongType(self.clone(), ValueType::Number))
        }
    }

    fn as_u32_ok(&self) -> Result<u32> {
        match self {
            Value::Number(num) if num.is_u64() && num.as_u64().unwrap() < (std::u32::MAX as u64) => Ok(num.as_u64().unwrap() as u32),
            Value::Number(num) if num.is_u64() => Err(Error::WrongNumberType(num.clone(), NumberType::U32)),
            Value::Number(num) => Err(Error::WrongNumberType(num.clone(), NumberType::U64)),
            _ => Err(Error::WrongType(self.clone(), ValueType::Number))
        }
    }

    fn as_i64_ok(&self) -> Result<i64> {
        match self {
            Value::Number(num) if num.is_i64() => Ok(num.as_i64().unwrap()),
            Value::Number(num) => Err(Error::WrongNumberType(num.clone(), NumberType::I64)),
            _ => Err(Error::WrongType(self.clone(), ValueType::Number))
        }
    }

    fn as_f64_ok(&self) -> Result<f64> {
        match self {
            Value::Number(num) if num.is_f64() => Ok(num.as_f64().unwrap()),
            Value::Number(num) => Err(Error::WrongNumberType(num.clone(), NumberType::Float)),
            _ => Err(Error::WrongType(self.clone(), ValueType::Number))
        }
    }

}