blob: 63460a26a6e6f1a9cb3ecbf5e82154a7afbba9f9 [file] [log] [blame]
//! This module defines a helper for parsing fields in a CBOR map.
use coset::{cbor::value::Value, AsCborValue, CoseError, CoseSign1};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FieldValueError {
#[error("expected a value for field {0}, but none was found")]
Missing(&'static str),
#[error("expected bytes for field {0}, but found `{1:?}`")]
NotBytes(&'static str, Value),
#[error("expected string for field {0}, but found `{1:?}`")]
NotString(&'static str, Value),
#[error("expected null for field {0}, but found `{1:?}`")]
NotNull(&'static str, Value),
#[error("expected u32 for field {0}, but found `{1:?}`")]
NotU32(&'static str, Value),
#[error("expected i64 for field {0}, but found `{1:?}`")]
NotI64(&'static str, Value),
#[error("expected u64 for field {0}, but found `{1:?}`")]
NotU64(&'static str, Value),
#[error("expected boolean for field {0}, but found `{1:?}`")]
NotBool(&'static str, Value),
#[error("expected map for field {0}, but found `{1:?}`")]
NotMap(&'static str, Value),
#[error("expected array for field {0}, but found `{1:?}`")]
NotArray(&'static str, Value),
#[error("unable to parse field {0} as COSE_sign1: `{1:?}`")]
CoseSign1ParseError(&'static str, CoseError),
#[error("field {0} may be set only once; encountered multiple values: `{1:?}`, `{2:?}`")]
DuplicateField(&'static str, Value, Value),
}
pub(super) struct FieldValue {
name: &'static str,
value: Option<Value>,
}
impl FieldValue {
pub fn new(name: &'static str) -> Self {
Self { name, value: None }
}
pub fn from_optional_value(name: &'static str, value: Option<Value>) -> Self {
Self { name, value }
}
pub fn set_once(&mut self, value: Value) -> Result<(), FieldValueError> {
match &self.value {
None => {
self.value = Some(value);
Ok(())
}
Some(previous) => {
Err(FieldValueError::DuplicateField(self.name, previous.clone(), value))
}
}
}
pub fn is_bytes(&self) -> bool {
self.value.as_ref().map_or(false, |v| v.is_bytes())
}
pub fn into_optional_bytes(self) -> Result<Option<Vec<u8>>, FieldValueError> {
self.value
.map(|v| match v {
Value::Bytes(b) => Ok(b),
_ => Err(FieldValueError::NotBytes(self.name, v)),
})
.transpose()
}
pub fn into_bytes(self) -> Result<Vec<u8>, FieldValueError> {
require_present(self.name, self.into_optional_bytes())
}
pub fn into_optional_string(self) -> Result<Option<String>, FieldValueError> {
self.value
.map(|v| match v {
Value::Text(s) => Ok(s),
_ => Err(FieldValueError::NotString(self.name, v)),
})
.transpose()
}
pub fn into_string(self) -> Result<String, FieldValueError> {
require_present(self.name, self.into_optional_string())
}
pub fn into_cose_sign1(self) -> Result<CoseSign1, FieldValueError> {
require_present(self.name, self.into_optional_cose_sign1())
}
pub fn into_optional_cose_sign1(self) -> Result<Option<CoseSign1>, FieldValueError> {
self.value
.map(|v| match v {
Value::Array(_) => CoseSign1::from_cbor_value(v)
.map_err(|e| FieldValueError::CoseSign1ParseError(self.name, e)),
_ => Err(FieldValueError::NotArray(self.name, v)),
})
.transpose()
}
pub fn into_map(self) -> Result<Vec<(Value, Value)>, FieldValueError> {
require_present(self.name, self.into_optional_map())
}
pub fn into_optional_map(self) -> Result<Option<Vec<(Value, Value)>>, FieldValueError> {
self.value
.map(|v| match v {
Value::Map(v) => Ok(v),
_ => Err(FieldValueError::NotMap(self.name, v)),
})
.transpose()
}
pub fn into_bool(self) -> Result<bool, FieldValueError> {
require_present(self.name, self.into_optional_bool())
}
pub fn into_optional_bool(self) -> Result<Option<bool>, FieldValueError> {
self.value
.map(|v| match v {
Value::Bool(b) => Ok(b),
Value::Integer(i) if i == 0.into() => Ok(false),
Value::Integer(i) if i == 1.into() => Ok(true),
_ => Err(FieldValueError::NotBool(self.name, v)),
})
.transpose()
}
pub fn is_null(&self) -> Result<bool, FieldValueError> {
// If there's no value, return false; if there is a null value, return true; anything else
// is an error.
self.value
.as_ref()
.map(|v| match *v {
Value::Null => Ok(true),
_ => Err(FieldValueError::NotNull(self.name, v.clone())),
})
.unwrap_or(Ok(false))
}
pub fn is_integer(&self) -> bool {
self.value.as_ref().map_or(false, |v| v.is_integer())
}
pub fn into_u32(self) -> Result<u32, FieldValueError> {
require_present(self.name, self.into_optional_u32())
}
pub fn into_optional_u32(self) -> Result<Option<u32>, FieldValueError> {
self.value
.map(|v| {
let value =
if let Value::Integer(i) = v { i128::from(i).try_into().ok() } else { None };
value.ok_or_else(|| FieldValueError::NotU32(self.name, v))
})
.transpose()
}
pub fn into_optional_i64(self) -> Result<Option<i64>, FieldValueError> {
self.value
.map(|v| {
let value =
if let Value::Integer(i) = v { i128::from(i).try_into().ok() } else { None };
value.ok_or_else(|| FieldValueError::NotI64(self.name, v))
})
.transpose()
}
pub fn into_optional_u64(self) -> Result<Option<u64>, FieldValueError> {
self.value
.map(|v| {
let value =
if let Value::Integer(i) = v { i128::from(i).try_into().ok() } else { None };
value.ok_or_else(|| FieldValueError::NotU64(self.name, v))
})
.transpose()
}
}
fn require_present<T>(
name: &'static str,
value: Result<Option<T>, FieldValueError>,
) -> Result<T, FieldValueError> {
value.and_then(|opt| opt.ok_or(FieldValueError::Missing(name)))
}