Added v0 parsing

This commit is contained in:
Emi Simpson 2021-10-21 18:33:38 -04:00
parent a97b97182e
commit 01ef419505
Signed by: Emi
GPG Key ID: A12F2C2FFDC3D847
4 changed files with 203 additions and 14 deletions

View File

@ -10,7 +10,7 @@
//! ```
//! use pronouns_today::InstanceSettings;
//!
//! let instance_settings = InstanceSettings::default() // Or load from a config file
//! let instance_settings = InstanceSettings::default(); // Or load from a config file
//!
//! // When you receive a request
//! let user_name = Some("Emi");
@ -70,9 +70,10 @@ impl InstanceSettings {
/// This is shorthand for
///
/// ```
/// # settings = InstanceSettings::default();
/// # name = String::from("Sashanora");
/// # prefstring = String::from("todo");
/// # use pronouns_today::InstanceSettings;
/// # let settings = InstanceSettings::default();
/// # let name = String::from("Sashanora");
/// # let prefstring = String::from("todo");
/// let pronouns = settings.parse_prefstring(Some(&prefstring)).select_pronouns(Some(&name));
/// # assert_eq!(pronouns, settings.select_pronouns(Some(&name), Some(&prefstring)));
/// ```

View File

@ -0,0 +1,105 @@
//! Errors that might occur when parsing or rendering prefstrings
//!
//! There are several possible points at which an error might occur when working with user
//! preference string. Each of these points are documented here, as well as
//! human-readable error messages and explainations of each.
use std::{
fmt,
ops::Range,
};
use data_encoding::DecodeError;
/// An error occured while trying to parse a user's prefstring
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ParseError {
/// The provided prefstring wasn't properly formatted base32 text
///
/// This could be because an implementation generated text with a different alphabet,
/// or the provided text isn't base32 at all. The [`DecodeError`] in the error is the
/// error that caused this exception.
Base32Error(DecodeError),
/// The version of the prefstring didn't match the version of the parser
///
/// This could occur when the prefstring is of a newer version than the parser, or the
/// parser is meant for a different version of the prefstring than expected.
///
/// Parsing typically works by having a master parser which supports all implemented
/// versions of prefstrings, and which delegates to several sub parsers. Each sub
/// parser handles one and only one version of the spec. This error could occur if,
/// for example, a v0 prefstring was fed to the v1 parser.
///
/// The `expected_version` and `expected_variant` properties denote the accepted
/// version or range of accepted versions that the parser supported. The `actual_`
/// properties denote what was received. These can be `None` if a version wasn't
/// specified.
///
/// If `actual_version` is [`Some`] then so must be `actual_variant`, and vice versa.
VersionMismatch {
expected_version: Range<u8>,
expected_variant: Range<u8>,
actual_version_byte: u8,
},
/// The `content` part of the prefstring was malformed
///
/// Whatever version of the specification the prefstring was being parsed with does
/// not allow whatever is going on in the prefstring. More details are provided in
/// human-readable form in the attached string.
MalformedContent(String),
/// The passed prefstring was zero-length
///
/// The minimum length for a prefstring is one byte, but the version that was passed
/// was zero bytes long. Often, this means that the developer should simply use the
/// instance default preferences.
ZeroLengthPrefstring,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ParseError::Base32Error(e) => {
write!(f,
"A problem occured while deserializing the preference string as base32: {}",
e
)
},
ParseError::VersionMismatch {
expected_version,
expected_variant,
actual_version_byte,
} => {
write!(f,
"A parser meant to handle prefstring version {} through {} and
variants {} throught {} was given a prefstring of version {} and
variant {}.",
expected_version.start,
expected_version.end - 1,
expected_variant.start,
expected_variant.end - 1,
actual_version_byte >> 3,
actual_version_byte & 0b00000111,
)
},
ParseError::MalformedContent(msg) => {
write!(f,"The prefstring's content was malformed: {}", msg)
},
ParseError::ZeroLengthPrefstring => {
write!(f, "Tried to parse a zero-length prefstring")
}
}
}
}
impl From<DecodeError> for ParseError {
fn from(e: DecodeError) -> Self {
ParseError::Base32Error(e)
}
}

View File

@ -5,10 +5,12 @@
//! module houses the implementations for each of these versions.
pub mod v0;
mod error;
pub use error::ParseError;
use crate::{InstanceSettings, WeightedTable};
use data_encoding::{BASE32_NOPAD, DecodeError};
use data_encoding::BASE32_NOPAD;
/// A user's preferences for the probabilities of certain pronouns
///
@ -45,7 +47,7 @@ pub trait Preference {
/// format. Prefstrings will already have been parsed from base64 before being passed to the
/// implementation. Users looking to turn a b64 prefstring into a `Preference` should use
/// [`Preference::from_prefstring()`]
fn from_prefstring_bytes(bytes: &[u8]) -> Self;
fn from_prefstring_bytes(bytes: &[u8]) -> Result<Self, ParseError> where Self: Sized;
/// Serialize these preferences into as few bytes as possible
///
@ -60,9 +62,10 @@ pub trait Preference {
///
/// This is the primary method of creating a `Preference` object from a prefstring. The
/// default implementation calls the underlying [`Preference::from_prefstring_bytes()`] method.
fn from_prefstring(prefstring: &str) -> Result<Self, DecodeError> where Self: Sized {
fn from_prefstring(prefstring: &str) -> Result<Self, ParseError> where Self: Sized {
BASE32_NOPAD.decode(prefstring.as_ref())
.map(|ps| Self::from_prefstring_bytes(&ps))
.map_err(ParseError::Base32Error)
.and_then(|ps| Self::from_prefstring_bytes(&ps))
}
/// Serialize into a base64 prefstring
@ -81,7 +84,7 @@ impl Preference for UserPreferences {
}.into_weighted_table(settings)
}
fn from_prefstring_bytes(bytes: &[u8]) -> Self {
fn from_prefstring_bytes(bytes: &[u8]) -> Result<Self, ParseError> where Self: Sized {
todo!()
}

View File

@ -2,7 +2,7 @@
use crate::{
InstanceSettings,
user_preferences::Preference,
user_preferences::{Preference, ParseError},
WeightedTable,
};
@ -12,8 +12,9 @@ use crate::{
///
/// [1]: https://fem.mint.lgbt/Emi/PronounsToday/raw/branch/main/doc/User-Preference-String-Spec.txt
pub struct UserPreferencesV0 {
default_weight: u8,
default_enabled: bool,
pub default_weight: u8,
pub default_enabled: bool,
pub commands: Vec<Command>,
}
impl Preference for UserPreferencesV0 {
@ -21,11 +22,90 @@ impl Preference for UserPreferencesV0 {
todo!()
}
fn from_prefstring_bytes(bytes: &[u8]) -> Self {
todo!()
/// ```
/// # use pronouns_today::user_preferences::{Preference, v0::{Command, UserPreferencesV0}};
/// let pbytes = &[ 0x00, 0x81, 0x08, 0xcf, 0x80 ][..];
/// let prefs = UserPreferencesV0::from_prefstring_bytes(&pbytes).unwrap();
/// assert_eq!(prefs.default_weight, 1);
/// assert!(prefs.default_enabled);
/// assert_eq!(prefs.commands, vec![
/// Command::SetWeight(8),
/// Command::Move { toggle_enabled: true, distance: 15 },
/// Command::Move { toggle_enabled: false, distance: 0 },
/// ]);
/// ```
fn from_prefstring_bytes(pbytes: &[u8]) -> Result<Self, ParseError> {
// Some simple error checks
if pbytes.len() == 0 {
return Err(ParseError::ZeroLengthPrefstring);
} else if pbytes[0] != 00 {
return Err(ParseError::VersionMismatch {
expected_version: 0..1,
expected_variant: 0..1,
actual_version_byte: pbytes[0],
})
} else if pbytes.len() == 1 {
return Err(
ParseError::MalformedContent(
"Version 0 prefstrings must be at least two bytes long, but this one
was just one byte long".into()
)
)
}
// Extract the defaults from byte 1
Ok(UserPreferencesV0 {
default_enabled: pbytes[1] & 0b10000000 > 0,
default_weight: pbytes[1] & 0b01111111,
commands: pbytes[2..].iter().map(|b| Command::from(*b)).collect(),
})
}
fn into_prefstring_bytes(&self) -> Vec<u8> {
todo!()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum Command {
SetWeight(u8),
Move {
toggle_enabled: bool,
distance: u8,
}
}
impl From<u8> for Command {
/// ```
/// use pronouns_today::user_preferences::v0::Command;
/// assert_eq!(
/// Command::from(0b01001000),
/// Command::SetWeight(0b01001000)
/// );
/// assert_eq!(
/// Command::from(0b11000000),
/// Command::Move {
/// toggle_enabled: true,
/// distance: 0b00000000
/// }
/// );
/// assert_eq!(
/// Command::from(0b10101000),
/// Command::Move {
/// toggle_enabled: false,
/// distance: 0b00101000
/// }
/// );
/// ```
fn from(b: u8) -> Self {
if b & 0b10000000 > 0 {
Command::Move {
toggle_enabled: b & 0b01000000 > 0,
distance: b & 0b00111111,
}
} else {
Command::SetWeight(b)
}
}
}