pub(crate) mod decode;
pub(crate) mod encode;
pub use decode::parse_clixml;
pub use encode::{RefIdAllocator, escape, ps_enum, ps_host_info_null, to_clixml};
use indexmap::IndexMap;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PsValue {
Null,
Bool(bool),
I8(i8),
U8(u8),
I16(i16),
U16(u16),
I32(i32),
U32(u32),
I64(i64),
U64(u64),
F32(f32),
Double(f64),
Decimal(String),
Char(char),
String(String),
Bytes(Vec<u8>),
DateTime(String),
Duration(String),
Guid(Uuid),
Version(String),
Uri(String),
Xml(String),
ScriptBlock(String),
SecureString(String),
List(Vec<PsValue>),
Dict(Vec<(PsValue, PsValue)>),
Object(PsObject),
}
impl PsValue {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
match self {
Self::String(s)
| Self::Version(s)
| Self::Uri(s)
| Self::Xml(s)
| Self::ScriptBlock(s)
| Self::Decimal(s)
| Self::DateTime(s)
| Self::Duration(s)
| Self::SecureString(s) => Some(s.as_str()),
_ => None,
}
}
#[must_use]
pub fn as_i32(&self) -> Option<i32> {
match self {
Self::I8(v) => Some(i32::from(*v)),
Self::I16(v) => Some(i32::from(*v)),
Self::I32(v) => Some(*v),
Self::I64(v) => i32::try_from(*v).ok(),
Self::U8(v) => Some(i32::from(*v)),
Self::U16(v) => Some(i32::from(*v)),
Self::U32(v) => i32::try_from(*v).ok(),
_ => None,
}
}
#[must_use]
pub fn as_i64(&self) -> Option<i64> {
match self {
Self::I8(v) => Some(i64::from(*v)),
Self::I16(v) => Some(i64::from(*v)),
Self::I32(v) => Some(i64::from(*v)),
Self::I64(v) => Some(*v),
Self::U8(v) => Some(i64::from(*v)),
Self::U16(v) => Some(i64::from(*v)),
Self::U32(v) => Some(i64::from(*v)),
Self::U64(v) => i64::try_from(*v).ok(),
_ => None,
}
}
#[must_use]
pub fn as_bool(&self) -> Option<bool> {
if let Self::Bool(v) = self {
Some(*v)
} else {
None
}
}
#[must_use]
pub fn properties(&self) -> Option<&IndexMap<String, PsValue>> {
if let Self::Object(o) = self {
Some(&o.properties)
} else {
None
}
}
#[must_use]
pub fn type_names(&self) -> Option<&[String]> {
if let Self::Object(o) = self {
Some(&o.type_names)
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PsObject {
pub properties: IndexMap<String, PsValue>,
pub type_names: Vec<String>,
pub to_string: Option<String>,
}
impl PsObject {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with(mut self, name: impl Into<String>, value: PsValue) -> Self {
self.properties.insert(name.into(), value);
self
}
#[must_use]
pub fn with_type_names<I, S>(mut self, names: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.type_names = names.into_iter().map(Into::into).collect();
self
}
#[must_use]
pub fn get(&self, name: &str) -> Option<&PsValue> {
self.properties.get(name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn string_accessors() {
assert_eq!(PsValue::String("hi".into()).as_str(), Some("hi"));
assert_eq!(PsValue::Version("5.1".into()).as_str(), Some("5.1"));
assert_eq!(PsValue::Uri("http://x".into()).as_str(), Some("http://x"));
assert_eq!(PsValue::Decimal("1.5".into()).as_str(), Some("1.5"));
assert_eq!(PsValue::I32(5).as_str(), None);
}
#[test]
fn integer_accessors() {
assert_eq!(PsValue::I8(-1).as_i32(), Some(-1));
assert_eq!(PsValue::U8(255).as_i32(), Some(255));
assert_eq!(PsValue::I16(-100).as_i32(), Some(-100));
assert_eq!(PsValue::U16(65_535).as_i32(), Some(65_535));
assert_eq!(PsValue::I32(42).as_i32(), Some(42));
assert_eq!(PsValue::I64(i64::MAX).as_i32(), None);
assert_eq!(PsValue::I64(42).as_i64(), Some(42));
assert_eq!(PsValue::U64(u64::MAX).as_i64(), None);
assert_eq!(PsValue::String("x".into()).as_i32(), None);
}
#[test]
fn bool_accessor() {
assert_eq!(PsValue::Bool(true).as_bool(), Some(true));
assert_eq!(PsValue::I32(1).as_bool(), None);
}
#[test]
fn object_accessors() {
let obj = PsObject::new()
.with("Name", PsValue::String("Alice".into()))
.with_type_names(["Foo", "Bar"]);
let v = PsValue::Object(obj);
assert!(v.properties().is_some());
assert_eq!(
v.type_names(),
Some(&["Foo".to_string(), "Bar".to_string()][..])
);
assert_eq!(
v.properties()
.unwrap()
.get("Name")
.and_then(PsValue::as_str),
Some("Alice")
);
assert!(PsValue::Null.properties().is_none());
assert!(PsValue::Null.type_names().is_none());
}
#[test]
fn as_str_returns_none_for_non_string_variants() {
assert_eq!(PsValue::I32(5).as_str(), None);
assert_eq!(PsValue::Bool(true).as_str(), None);
assert_eq!(PsValue::Null.as_str(), None);
assert_eq!(PsValue::List(vec![]).as_str(), None);
assert_eq!(PsValue::Double(1.0).as_str(), None);
}
#[test]
fn as_str_covers_all_string_like_variants() {
assert_eq!(PsValue::Xml("<r/>".into()).as_str(), Some("<r/>"));
assert_eq!(PsValue::ScriptBlock("Get-X".into()).as_str(), Some("Get-X"));
assert_eq!(
PsValue::DateTime("2024-01-01".into()).as_str(),
Some("2024-01-01")
);
assert_eq!(PsValue::Duration("P1D".into()).as_str(), Some("P1D"));
assert_eq!(PsValue::SecureString("ss".into()).as_str(), Some("ss"));
}
#[test]
fn as_i32_overflow_returns_none() {
assert_eq!(PsValue::U32(u32::MAX).as_i32(), None);
assert_eq!(PsValue::I64(i64::MAX).as_i32(), None);
assert_eq!(PsValue::U64(100).as_i32(), None); }
#[test]
fn as_i64_overflow_returns_none() {
assert_eq!(PsValue::U64(u64::MAX).as_i64(), None);
assert_eq!(PsValue::Bool(true).as_i64(), None);
assert_eq!(PsValue::Null.as_i64(), None);
}
#[test]
fn as_i64_covers_all_integer_variants() {
assert_eq!(PsValue::I8(-1).as_i64(), Some(-1));
assert_eq!(PsValue::U8(200).as_i64(), Some(200));
assert_eq!(PsValue::I16(-1000).as_i64(), Some(-1000));
assert_eq!(PsValue::U16(60000).as_i64(), Some(60000));
assert_eq!(PsValue::U32(4_000_000_000).as_i64(), Some(4_000_000_000));
}
#[test]
fn properties_and_type_names_on_non_objects() {
assert!(PsValue::I32(1).properties().is_none());
assert!(PsValue::String("x".into()).properties().is_none());
assert!(PsValue::List(vec![]).properties().is_none());
assert!(PsValue::I32(1).type_names().is_none());
assert!(PsValue::String("x".into()).type_names().is_none());
assert!(PsValue::Bool(false).type_names().is_none());
}
}