#![warn(missing_docs)]
#![allow(async_fn_in_trait)]
extern crate alloc;
use alloc::string::String;
use core::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ConfigError {
BufferTooSmall(usize),
Utf8,
InvalidData,
Backend,
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConfigError::BufferTooSmall(n) => write!(f, "buffer too small: need {} bytes", n),
ConfigError::Utf8 => write!(f, "invalid UTF-8"),
ConfigError::InvalidData => write!(f, "invalid data"),
ConfigError::Backend => write!(f, "backend storage error"),
}
}
}
pub const MAX_VALUE_SIZE: usize = 256;
#[allow(async_fn_in_trait)]
pub trait ConfigStorage {
async fn load_bytes(&mut self, key: u64, buf: &mut [u8]) -> Result<Option<usize>, ConfigError>;
async fn store_bytes(&mut self, key: u64, bytes: &[u8]) -> Result<(), ConfigError>;
async fn get_value<T: ConfigValue>(&mut self, key: u64) -> Result<Option<T>, ConfigError> {
let mut buf = [0u8; MAX_VALUE_SIZE];
match self.load_bytes(key, &mut buf).await? {
Some(len) => Ok(Some(T::from_bytes(&buf[..len])?)),
None => Ok(None),
}
}
async fn set_value<T: ConfigValue>(&mut self, key: u64, value: &T) -> Result<(), ConfigError> {
let mut buf = [0u8; MAX_VALUE_SIZE];
let len = value.to_bytes(&mut buf)?;
self.store_bytes(key, &buf[..len]).await
}
}
#[doc(hidden)]
#[allow(async_fn_in_trait)]
pub trait ConfigLoadStore {
async fn load_from<S: ConfigStorage>(storage: &mut S) -> Result<Self, ConfigError>
where
Self: Sized;
async fn store_to<S: ConfigStorage>(&self, storage: &mut S) -> Result<(), ConfigError>;
}
#[doc(hidden)]
pub trait ConfigChangedSet {
fn is_empty(&self) -> bool;
}
impl<T: enumset::EnumSetType> ConfigChangedSet for enumset::EnumSet<T> {
fn is_empty(&self) -> bool {
enumset::EnumSet::is_empty(self)
}
}
#[doc(hidden)]
pub trait ConfigApi {
type Error: core::fmt::Display;
type ChangedSet: ConfigChangedSet;
fn get_group_json(&self, group: &str, buf: &mut [u8]) -> Result<usize, Self::Error>;
fn set_group_json(&mut self, group: &str, json: &str) -> Result<Self::ChangedSet, Self::Error>;
fn set_field(
&mut self,
key: &str,
value: &str,
) -> Result<Option<Self::ChangedSet>, Self::Error>;
}
#[doc(hidden)]
pub trait ConfigGet {
fn get(&self, key: &str) -> Option<String>;
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum JsSaveKind {
String = 0,
Int = 1,
Float = 2,
}
#[doc(hidden)]
pub trait ConfigFormGen {
fn page_names() -> &'static [&'static str]
where
Self: Sized;
fn html_segments_for_group(group: &str) -> Option<&'static [&'static str]>
where
Self: Sized;
fn js_segments_for_group(group: &str) -> Option<&'static [&'static str]>
where
Self: Sized;
}
pub trait ConfigValue: core::str::FromStr + core::fmt::Display {
type Getter<'a>
where
Self: 'a;
fn to_getter<'a>(&'a self) -> Self::Getter<'a>;
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError>;
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError>
where
Self: Sized;
const DEFAULT_INPUT_TYPE: &'static str = "number";
const IS_FLOAT: bool = false;
const JS_SAVE_KIND: JsSaveKind = JsSaveKind::Int;
}
impl ConfigValue for u8 {
type Getter<'a> = u8;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.is_empty() {
return Err(ConfigError::BufferTooSmall(1));
}
buf[0] = *self;
Ok(1)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.is_empty() {
return Err(ConfigError::BufferTooSmall(1));
}
Ok(bytes[0])
}
}
impl ConfigValue for u16 {
type Getter<'a> = u16;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 2 {
return Err(ConfigError::BufferTooSmall(2));
}
buf[0..2].copy_from_slice(&self.to_le_bytes());
Ok(2)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 2 {
return Err(ConfigError::BufferTooSmall(2));
}
Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
}
}
impl ConfigValue for u32 {
type Getter<'a> = u32;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
buf[0..4].copy_from_slice(&self.to_le_bytes());
Ok(4)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
}
impl ConfigValue for u64 {
type Getter<'a> = u64;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
buf[0..8].copy_from_slice(&self.to_le_bytes());
Ok(8)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
Ok(u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
}
impl ConfigValue for i16 {
type Getter<'a> = i16;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 2 {
return Err(ConfigError::BufferTooSmall(2));
}
buf[0..2].copy_from_slice(&self.to_le_bytes());
Ok(2)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 2 {
return Err(ConfigError::BufferTooSmall(2));
}
Ok(i16::from_le_bytes([bytes[0], bytes[1]]))
}
}
impl ConfigValue for i32 {
type Getter<'a> = i32;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
buf[0..4].copy_from_slice(&self.to_le_bytes());
Ok(4)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
Ok(i32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
}
impl ConfigValue for i8 {
type Getter<'a> = i8;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.is_empty() {
return Err(ConfigError::BufferTooSmall(1));
}
buf[0] = *self as u8;
Ok(1)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.is_empty() {
return Err(ConfigError::BufferTooSmall(1));
}
Ok(bytes[0] as i8)
}
}
impl ConfigValue for i64 {
type Getter<'a> = i64;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
buf[0..8].copy_from_slice(&self.to_le_bytes());
Ok(8)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
Ok(i64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
}
impl ConfigValue for f32 {
type Getter<'a> = f32;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
buf[0..4].copy_from_slice(&self.to_le_bytes());
Ok(4)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 4 {
return Err(ConfigError::BufferTooSmall(4));
}
Ok(f32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
}
const IS_FLOAT: bool = true;
const JS_SAVE_KIND: JsSaveKind = JsSaveKind::Float;
}
impl ConfigValue for f64 {
type Getter<'a> = f64;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
*self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
if buf.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
buf[0..8].copy_from_slice(&self.to_le_bytes());
Ok(8)
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
if bytes.len() < 8 {
return Err(ConfigError::BufferTooSmall(8));
}
Ok(f64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
const IS_FLOAT: bool = true;
const JS_SAVE_KIND: JsSaveKind = JsSaveKind::Float;
}
impl ConfigValue for String {
type Getter<'a> = &'a String;
fn to_getter<'a>(&'a self) -> Self::Getter<'a> {
self
}
fn to_bytes(&self, buf: &mut [u8]) -> Result<usize, ConfigError> {
let bytes = self.as_bytes();
if buf.len() < bytes.len() {
return Err(ConfigError::BufferTooSmall(bytes.len()));
}
buf[..bytes.len()].copy_from_slice(bytes);
Ok(bytes.len())
}
fn from_bytes(bytes: &[u8]) -> Result<Self, ConfigError> {
core::str::from_utf8(bytes)
.map(String::from)
.map_err(|_| ConfigError::Utf8)
}
const DEFAULT_INPUT_TYPE: &'static str = "text";
const JS_SAVE_KIND: JsSaveKind = JsSaveKind::String;
}