Files
simple-rust-tests/__ffi/abi_stable_crates/interface/src/which_plugin.rs

262 lines
7.0 KiB
Rust

use crate::PluginId;
use std::{fmt::{self,Display}, str::FromStr};
use arrayvec::ArrayVec;
use abi_stable::{StableAbi, std_types::{RCow,RVec,RString,cow::BorrowingRCowStr}};
use core_extensions::{StringExt,SelfOps};
use serde::{Serialize,Deserialize,Deserializer,Serializer};
/// A way to choose to which plugins one refers to when sending commands,and other operations.
#[repr(u8)]
#[derive(Debug,Clone,PartialEq,Eq,StableAbi)]
pub enum WhichPlugin{
Id(PluginId),
First{
named:RCow<'static,str>,
},
Last{
named:RCow<'static,str>,
},
Every{
named:RCow<'static,str>,
},
Many(RVec<WhichPlugin>),
}
impl WhichPlugin{
/// Converts this `WhichPlugin` to its json representation,
/// generally used as a key in a json object.
pub fn to_key(&self)->RString{
let mut buffer=RString::new();
self.write_key(&mut buffer);
buffer
}
/// Writes the value of this as a key usable in the application config.
pub fn write_key(&self,buf:&mut RString){
use std::fmt::Write;
match self {
WhichPlugin::Id(id)=>write!(buf,"{}:{}",id.named,id.instance).drop_(),
WhichPlugin::First{named}=>write!(buf,"{}:first",named).drop_(),
WhichPlugin::Last{named}=>write!(buf,"{}:last",named).drop_(),
WhichPlugin::Every{named}=>write!(buf,"{}:every",named).drop_(),
WhichPlugin::Many(list)=>{
for elem in list {
elem.write_key(buf);
buf.push(',');
}
},
}
}
}
impl FromStr for WhichPlugin{
type Err=WhichPluginError;
fn from_str(full_str:&str)->Result<Self,WhichPluginError>{
let mut comma_sep=full_str.split(',').peekable();
let first=comma_sep.next().unwrap_or("").piped(|s|Self::parse_single(s,full_str))?;
if comma_sep.peek().is_some() {
let mut list:RVec<WhichPlugin>=vec![first].into();
for s in comma_sep.filter(|s| !s.is_empty() ) {
list.push( Self::parse_single(s,full_str)? );
}
WhichPlugin::Many(list)
}else{
first
}.piped(Ok)
}
}
impl WhichPlugin{
fn parse_single(s:&str,full_str:&str)->Result<Self,WhichPluginError>{
let splitted=s.splitn(2,':').map(|s|s.trim()).collect::<ArrayVec<[&str;2]>>();
let named=splitted.get(0)
.filter(|s| !s.is_empty() )
.ok_or_else(|| WhichPluginError(full_str.into()) )?
.to_string()
.into_(RCow::<'static,str>::T);
let selector=splitted.get(1).map_or("",|x|*x);
match selector {
"first"=>return Ok(WhichPlugin::First{named}),
""|"last"=>return Ok(WhichPlugin::Last{named}),
"all"|"every"=>return Ok(WhichPlugin::Every{named}),
_=>(),
}
let instance=selector.parse::<u64>().map_err(|_| WhichPluginError(full_str.into()) )?;
Ok(WhichPlugin::Id(PluginId{named,instance}))
}
pub const FMT_MSG:&'static str=r##"
"plugin name":
refers to the last plugin named "plugin name".
"plugin name:10":
refers to the 10th instance of the plugin named "plugin name".
"plugin name:first":
refers to the first instance of the plugin named "plugin name".
"plugin name:last":
refers to the last instance of the plugin named "plugin name".
"plugin name:every":
refers to all the instances of the plugin named "plugin name".
"plugin name 1,plugin name 2:first,plugin name 3:every":
refers to the last instance of the plugin named "plugin name 1".
refers to the first instance of the plugin named "plugin name 2".
refers to the all the instances of the plugin named "plugin name 3".
Plugin names:
- Are trimmed,so you can add spaces at the start and the end.
- Cannot contain commas,since they will be interpreted as a list of plugins.
"##;
}
impl<'de> Deserialize<'de> for WhichPlugin{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;
BorrowingRCowStr::deserialize(deserializer)?
.cow
.parse::<Self>()
.map_err(de::Error::custom)
}
}
impl Serialize for WhichPlugin{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_key().serialize(serializer)
}
}
///////////////////////////////////////
#[repr(transparent)]
#[derive(Debug,Clone,StableAbi)]
pub struct WhichPluginError(RString);
impl Display for WhichPluginError{
fn fmt(&self,f:&mut fmt::Formatter)->fmt::Result{
writeln!(
f,
"Could not parse this as a `WhichPlugin`:\n\t'{}'\nExpected format:\n{}\n",
self.0,
WhichPlugin::FMT_MSG.left_padder(4),
)
}
}
#[cfg(test)]
mod tests{
use super::*;
fn new_str_expected()->Vec<(&'static str,WhichPlugin)>{
vec![
(
"plugin name",
WhichPlugin::Last{named:"plugin name".into()}
),
(
"plugin name:10",
WhichPlugin::Id(PluginId{
named:"plugin name".into(),
instance:10,
})
),
(
"plugin name:first",
WhichPlugin::First{named:"plugin name".into()}
),
(
"plugin name:last",
WhichPlugin::Last{named:"plugin name".into()}
),
(
"plugin name:every",
WhichPlugin::Every{named:"plugin name".into()}
),
(
"plugin name 1,plugin name 2:first,plugin name 3:every",
WhichPlugin::Many(vec![
WhichPlugin::Last{named:"plugin name 1".into()},
WhichPlugin::First{named:"plugin name 2".into()},
WhichPlugin::Every{named:"plugin name 3".into()},
].into())
),
]
}
#[test]
fn parses_correctly(){
let str_expected=new_str_expected();
for (str_,expected) in str_expected {
let parsed=str_.parse::<WhichPlugin>().unwrap();
assert_eq!(parsed,expected);
assert_eq!(
parsed.to_key().parse::<WhichPlugin>().unwrap(),
expected,
);
}
}
#[test]
fn serde_(){
let str_expected=new_str_expected();
for (_,elem) in str_expected {
let str_=serde_json::to_string(&elem).unwrap();
let other:WhichPlugin=serde_json::from_str(&str_)
.unwrap_or_else(|e| panic!("{}",e) );
assert_eq!(other,elem);
}
}
#[test]
fn parses_incorrectly(){
let list=vec![
// An empty plugin name is invalid
"",
":",
":first",
":last",
",",
",,,:first,:last",
];
for str_ in list {
str_.parse::<WhichPlugin>().unwrap_err();
}
}
}