use bitflags::bitflags;
use core::convert::Infallible;
use core::fmt;
use std::str::FromStr;
use std::{ffi::OsStr, path::PathBuf};
use typed_path::WindowsPathBuf;
use super::convert::{ArchError, DllOverrideError, LogError};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[allow(unused_macros)]
macro_rules! str_serde {
($ident:ident) => {
#[cfg(feature = "serde")]
impl Serialize for $ident {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for $ident {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
};
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Debug, Clone)]
pub struct Paths {
#[cfg_attr(feature = "serde", serde(with = "crate::serde::windows_pathbuf_vec"))]
inner: Vec<WindowsPathBuf>,
}
impl Paths {
const SEPARATOR: char = ';';
}
impl fmt::Display for Paths {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.iter().enumerate().try_for_each(|(i, path)| {
if i != self.inner.len() - 1 {
write!(f, "{}{}", path.display(), Self::SEPARATOR)
} else {
write!(f, "{}", path.display())
}
})
}
}
impl FromStr for Paths {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(s.split(Self::SEPARATOR)
.map(WindowsPathBuf::from)
.collect::<Vec<_>>()
.into())
}
}
impl From<Vec<WindowsPathBuf>> for Paths {
fn from(inner: Vec<WindowsPathBuf>) -> Self {
Self { inner }
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "lowercase"))]
#[derive(Debug, Clone, Copy)]
pub enum Arch {
Win32,
Win64,
Wow64,
}
impl fmt::Display for Arch {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Win32 => write!(f, "win32"),
Self::Win64 => write!(f, "win64"),
Self::Wow64 => write!(f, "wow64"),
}
}
}
impl FromStr for Arch {
type Err = ArchError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"win32" => Ok(Self::Win32),
"win64" => Ok(Self::Win64),
"wow64" => Ok(Self::Wow64),
_ => Err(ArchError),
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum DllLoadOrder {
Disabled,
BuiltinOnly,
NativeOnly,
#[default]
BuiltinNative,
NativeBuiltin,
}
impl DllLoadOrder {
#[inline]
pub fn is_disabled(&self) -> bool {
*self == Self::Disabled
}
#[inline]
pub fn has_native(&self) -> bool {
matches!(
self,
Self::NativeOnly | Self::BuiltinNative | Self::NativeBuiltin
)
}
#[inline]
pub fn prefers_native(&self) -> bool {
matches!(self, Self::NativeOnly | Self::NativeBuiltin)
}
#[inline]
pub fn is_native_only(&self) -> bool {
matches!(self, Self::NativeOnly)
}
#[inline]
pub fn has_builtin(&self) -> bool {
matches!(
self,
Self::BuiltinOnly | Self::BuiltinNative | Self::NativeBuiltin
)
}
#[inline]
pub fn prefers_builtin(&self) -> bool {
matches!(self, Self::BuiltinOnly | Self::BuiltinNative)
}
#[inline]
pub fn is_builtin_only(&self) -> bool {
matches!(self, Self::BuiltinOnly)
}
}
impl fmt::Display for DllLoadOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Disabled => write!(f, ""),
Self::BuiltinOnly => write!(f, "b"),
Self::NativeOnly => write!(f, "n"),
Self::BuiltinNative => write!(f, "b,n"),
Self::NativeBuiltin => write!(f, "n,b"),
}
}
}
impl<T> From<T> for DllLoadOrder
where
T: AsRef<str>,
{
fn from(value: T) -> Self {
let s = value.as_ref();
let mut state = Self::Disabled;
for order in s.split(DllOverride::ITEM_SEPARATORS) {
state = match order.chars().next() {
Some('N') | Some('n') => match state {
Self::Disabled => Self::NativeOnly,
Self::BuiltinOnly => Self::BuiltinNative,
_ => state,
},
Some('B') | Some('b') => match state {
Self::Disabled => Self::BuiltinOnly,
Self::NativeOnly => Self::NativeBuiltin,
_ => state,
},
_ => state,
};
if matches!(state, Self::BuiltinNative | Self::NativeBuiltin) {
return state;
}
}
state
}
}
impl FromStr for DllLoadOrder {
type Err = DllOverrideError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut state = Self::Disabled;
for order in s.split(DllOverride::ITEM_SEPARATORS) {
state = match order.chars().next() {
Some('N') | Some('n') => match state {
Self::Disabled => Self::NativeOnly,
Self::BuiltinOnly => Self::BuiltinNative,
_ => state,
},
Some('B') | Some('b') => match state {
Self::Disabled => Self::BuiltinOnly,
Self::NativeOnly => Self::NativeBuiltin,
_ => state,
},
Some(c) => return Err(DllOverrideError::InvalidLoadOrder { state, char: c }),
_ => state,
};
if matches!(state, Self::BuiltinNative | Self::NativeBuiltin) {
return Ok(state);
}
}
Ok(state)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone)]
pub struct DllOverride {
modules: Vec<PathBuf>,
load_order: DllLoadOrder,
}
impl DllOverride {
const ITEM_SEPARATORS: [char; 2] = [',', '\t'];
const KV_SEPARATOR: char = '=';
pub fn modules(&self) -> &[PathBuf] {
self.modules.as_slice()
}
pub fn load_order(&self) -> DllLoadOrder {
self.load_order
}
}
impl FromStr for DllOverride {
type Err = DllOverrideError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((modules, load_order)) = s.split_once(DllOverride::KV_SEPARATOR) {
let load_order: DllLoadOrder = load_order.parse()?;
let modules = modules
.split(DllOverride::ITEM_SEPARATORS)
.map(PathBuf::from)
.collect::<Vec<_>>();
Ok(Self {
modules,
load_order,
})
} else {
Err(DllOverrideError::InvalldOverride)
}
}
}
impl fmt::Display for DllOverride {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}={}",
self.modules
.iter()
.map(|m| m.display().to_string())
.collect::<Vec<_>>()
.join(","),
self.load_order
)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Debug, Clone)]
pub struct DllOverrides {
inner: Vec<DllOverride>,
}
impl DllOverrides {
const SEPARATOR: char = ';';
pub fn inner(&self) -> &[DllOverride] {
&self.inner
}
pub fn into_inner(self) -> Vec<DllOverride> {
self.inner
}
}
impl FromStr for DllOverrides {
type Err = DllOverrideError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let overrides = s
.split(Self::SEPARATOR)
.map(|o| o.parse::<DllOverride>())
.collect::<Result<Vec<_>, _>>()?;
Ok(Self { inner: overrides })
}
}
impl fmt::Display for DllOverrides {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner
.iter()
.enumerate()
.try_for_each(|(i, dll_override)| {
if i != self.inner.len() - 1 {
write!(f, "{}{}", dll_override, Self::SEPARATOR)
} else {
write!(f, "{}", dll_override)
}
})
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LogClass {
FixMe,
Error,
Warning,
Trace,
}
impl FromStr for LogClass {
type Err = LogError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"fixme" => Ok(Self::FixMe),
"err" => Ok(Self::Error),
"warn" => Ok(Self::Warning),
"trace" => Ok(Self::Trace),
_ => Err(LogError::InvalidClass),
}
}
}
impl fmt::Display for LogClass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::FixMe => write!(f, "fixme"),
Self::Error => write!(f, "err"),
Self::Warning => write!(f, "warn"),
Self::Trace => write!(f, "trace"),
}
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LogClassSet: u8 {
const FIXME = 0b00000001;
const ERROR = 0b00000010;
const WARNING = 0b00000100;
const TRACE = 0b00001000;
}
}
impl From<LogClass> for LogClassSet {
fn from(class: LogClass) -> Self {
match class {
LogClass::FixMe => Self::FIXME,
LogClass::Error => Self::ERROR,
LogClass::Warning => Self::WARNING,
LogClass::Trace => Self::TRACE,
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
#[derive(Debug, Clone, Eq)]
pub enum LogChannel {
All,
Specific(String),
}
impl LogChannel {
pub fn is_all(&self) -> bool {
match self {
Self::All => true,
Self::Specific(s) if s == "all" => true,
_ => false,
}
}
pub fn is_specific(&self) -> bool {
!self.is_all()
}
pub fn specific(&self) -> Option<&str> {
match self {
Self::All => None,
Self::Specific(s) => {
if s == "all" {
None
} else {
Some(s)
}
}
}
}
}
impl From<&str> for LogChannel {
fn from(value: &str) -> Self {
if value == "all" {
Self::All
} else {
Self::Specific(value.to_string())
}
}
}
impl FromStr for LogChannel {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self::from(s))
}
}
impl fmt::Display for LogChannel {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::All => write!(f, "all"),
Self::Specific(s) => write!(f, "{}", s),
}
}
}
impl PartialEq for LogChannel {
fn eq(&self, other: &Self) -> bool {
self.specific() == other.specific()
}
}
impl PartialEq<&LogChannel> for LogChannel {
fn eq(&self, other: &&LogChannel) -> bool {
self.eq(*other)
}
}
impl PartialEq<LogChannel> for &LogChannel {
fn eq(&self, other: &LogChannel) -> bool {
(*self).eq(other)
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LogOperation {
#[default]
Set,
Clear,
}
impl fmt::Display for LogOperation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Set => write!(f, "+"),
Self::Clear => write!(f, "-"),
}
}
}
impl TryFrom<char> for LogOperation {
type Error = LogError;
fn try_from(value: char) -> Result<Self, Self::Error> {
match value {
'+' => Ok(Self::Set),
'-' => Ok(Self::Clear),
_ => Err(LogError::InvalidOperation),
}
}
}
impl FromStr for LogOperation {
type Err = LogError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 1 {
Self::try_from(s.chars().next().ok_or(LogError::InvalidOperation)?)
} else {
Err(LogError::InvalidOperation)
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LogOption {
operation: Option<LogOperation>,
class: Option<LogClass>,
channel: LogChannel,
}
impl LogOption {
pub fn operation(&self) -> LogOperation {
self.operation.unwrap_or_default()
}
pub fn operation_real(&self) -> Option<LogOperation> {
self.operation
}
pub fn class(&self) -> Option<LogClass> {
self.class
}
pub fn classes(&self) -> LogClassSet {
if let Some(class) = self.class {
class.into()
} else {
LogClassSet::all()
}
}
pub fn channel(&self) -> &LogChannel {
&self.channel
}
}
impl fmt::Display for LogOption {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match (self.class, self.operation) {
(None, None) => {
write!(f, "{}", self.channel)
}
(None, Some(operation)) => {
write!(f, "{}{}", operation, self.channel)
}
(Some(class), _) => {
write!(f, "{}{}{}", class, self.operation(), self.channel)
}
}
}
}
impl FromStr for LogOption {
type Err = LogError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(op) = s.chars().find(|&c| c == '+' || c == '-') {
let operation = LogOperation::try_from(op)?;
let (class, channel) = s.split_once(['+', '-']).unwrap();
Ok(Self {
operation: Some(operation),
class: if class.is_empty() {
None
} else {
Some(class.parse()?)
},
channel: LogChannel::from(channel),
})
} else {
Ok(Self {
operation: None,
class: None,
channel: LogChannel::from(s),
})
}
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(transparent))]
#[derive(Debug, Clone)]
pub struct LogOptions {
inner: Vec<LogOption>,
}
impl LogOptions {
pub fn inner(&self) -> &[LogOption] {
&self.inner
}
}
impl fmt::Display for LogOptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.iter().enumerate().try_for_each(|(i, option)| {
if i != self.inner.len() - 1 {
write!(f, "{},", option)
} else {
write!(f, "{}", option)
}
})
}
}
impl FromStr for LogOptions {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let options = s
.split(',')
.filter_map(|option| option.parse::<LogOption>().ok())
.collect::<Vec<_>>();
Ok(Self::from(options))
}
}
impl FromIterator<LogOption> for LogOptions {
fn from_iter<T: IntoIterator<Item = LogOption>>(iter: T) -> Self {
Self::from(Vec::from_iter(iter))
}
}
impl From<Vec<LogOption>> for LogOptions {
fn from(options: Vec<LogOption>) -> Self {
Self { inner: options }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn log_options_simple() {
let fixme_clear_all = "fixme-all".parse::<LogOption>().unwrap();
assert_eq!(fixme_clear_all.channel(), LogChannel::All);
assert_eq!(fixme_clear_all.operation(), LogOperation::Clear);
assert_eq!(fixme_clear_all.classes(), LogClassSet::FIXME);
let warn_set_ntdll = "warn+ntdll".parse::<LogOption>().unwrap();
assert_eq!(
warn_set_ntdll.channel(),
LogChannel::Specific("ntdll".to_string())
);
assert_eq!(warn_set_ntdll.operation(), LogOperation::Set);
assert_eq!(warn_set_ntdll.classes(), LogClassSet::WARNING);
let no_class_set = "+ntdll".parse::<LogOption>().unwrap();
assert_eq!(
no_class_set.channel(),
LogChannel::Specific("ntdll".to_string())
);
assert_eq!(no_class_set.operation(), LogOperation::Set);
assert_eq!(no_class_set.classes(), LogClassSet::all());
let no_class_clear = "-ntdll".parse::<LogOption>().unwrap();
assert_eq!(
no_class_clear.channel(),
LogChannel::Specific("ntdll".to_string())
);
assert_eq!(no_class_clear.operation(), LogOperation::Clear);
assert_eq!(no_class_clear.classes(), LogClassSet::all());
let channel_only = "ntdll".parse::<LogOption>().unwrap();
assert_eq!(
channel_only.channel(),
LogChannel::Specific("ntdll".to_string())
);
assert_eq!(channel_only.operation(), LogOperation::Set);
assert_eq!(channel_only.classes(), LogClassSet::all());
}
#[test]
fn log_options_reflexive() {
let testcases = ["fixme-all", "warn+ntdll", "+ntdll", "-ntdll", "ntdll"];
for testcase in testcases {
let option = testcase.parse::<LogOption>().unwrap();
assert_eq!(format!("{}", option), testcase);
}
}
#[cfg(feature = "serde")]
#[test]
fn log_options_serde() {
use serde_test::{Token, assert_tokens};
let testcases = ["fixme-all", "warn+ntdll", "+ntdll", "-ntdll", "ntdll"];
for testcase in testcases {
let option = testcase.parse::<LogOption>().unwrap();
assert_tokens(&option, &[Token::String(testcase)]);
}
}
}