use crate::prelude::{
fmt::{Display, Error as FmtError, Formatter},
iter,
vec::Vec,
};
use crate::{
form::{Form, MetaForm, PortableForm},
utils::is_rust_identifier,
IntoPortable, Registry,
};
use scale::Encode;
#[cfg(feature = "serde")]
use serde::{de::DeserializeOwned, Deserialize, Serialize};
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(
feature = "serde",
serde(bound(
serialize = "T::Type: Serialize, T::String: Serialize",
deserialize = "T::Type: DeserializeOwned, T::String: DeserializeOwned",
))
)]
#[cfg_attr(feature = "serde", serde(transparent))]
#[cfg_attr(any(feature = "std", feature = "decode"), derive(scale::Decode))]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Encode)]
pub struct Path<T: Form = MetaForm> {
pub segments: Vec<T::String>,
}
impl<T> Default for Path<T>
where
T: Form,
{
fn default() -> Self {
Path {
segments: Vec::new(),
}
}
}
impl IntoPortable for Path {
type Output = Path<PortableForm>;
fn into_portable(self, _registry: &mut Registry) -> Self::Output {
Path {
segments: self.segments.into_iter().map(Into::into).collect(),
}
}
}
impl Display for Path<PortableForm> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
write!(f, "{}", self.segments.join("::"))
}
}
impl Path<MetaForm> {
pub fn new(ident: &'static str, module_path: &'static str) -> Path {
let segments = module_path.split("::");
Self::from_segments(segments.chain(iter::once(ident)))
.expect("All path segments should be valid Rust identifiers")
}
pub fn new_with_replace(
ident: &'static str,
module_path: &'static str,
segment_replace: &[(&'static str, &'static str)],
) -> Path {
let segments = module_path.split("::");
Self::from_segments(
segments
.chain(iter::once(ident))
.map(|s| segment_replace.iter().find(|r| s == r.0).map_or(s, |r| r.1)),
)
.expect("All path segments should be valid Rust identifiers")
}
pub fn from_segments<I>(segments: I) -> Result<Self, PathError>
where
I: IntoIterator<Item = <MetaForm as Form>::String>,
{
let segments = segments.into_iter().collect::<Vec<_>>();
if segments.is_empty() {
return Err(PathError::MissingSegments);
}
if let Some(err_at) = segments.iter().position(|seg| !is_rust_identifier(seg)) {
return Err(PathError::InvalidIdentifier { segment: err_at });
}
Ok(Path { segments })
}
pub(crate) fn prelude(ident: <MetaForm as Form>::String) -> Self {
Self::from_segments([ident])
.unwrap_or_else(|_| panic!("{ident:?} is not a valid Rust identifier"))
}
}
impl<T> Path<T>
where
T: Form,
{
#[allow(unused)]
pub(crate) fn voldemort() -> Self {
Self {
segments: Vec::new(),
}
}
pub fn from_segments_unchecked<I>(segments: I) -> Path<T>
where
I: IntoIterator<Item = T::String>,
{
Self {
segments: segments.into_iter().collect(),
}
}
#[deprecated(
since = "2.5.0",
note = "Prefer to access the fields directly; this getter will be removed in the next major version"
)]
pub fn segments(&self) -> &[T::String] {
&self.segments
}
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
pub fn ident(&self) -> Option<T::String> {
self.segments.iter().last().cloned()
}
pub fn namespace(&self) -> &[T::String] {
self.segments.split_last().map(|(_, ns)| ns).unwrap_or(&[])
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum PathError {
MissingSegments,
InvalidIdentifier {
segment: usize,
},
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn path_ok() {
assert_eq!(
Path::from_segments(vec!["hello"]),
Ok(Path {
segments: vec!["hello"]
})
);
assert_eq!(
Path::from_segments(vec!["Hello", "World"]),
Ok(Path {
segments: vec!["Hello", "World"]
})
);
assert_eq!(
Path::from_segments(vec!["_"]),
Ok(Path {
segments: vec!["_"]
})
);
}
#[test]
fn path_with_raw_identifers_ok() {
assert_eq!(
Path::from_segments(vec!["r#mod", "r#Struct"]),
Ok(Path {
segments: vec!["r#mod", "r#Struct"]
})
);
}
#[test]
fn path_err() {
assert_eq!(
Path::from_segments(Vec::new()),
Err(PathError::MissingSegments)
);
assert_eq!(
Path::from_segments(vec![""]),
Err(PathError::InvalidIdentifier { segment: 0 })
);
assert_eq!(
Path::from_segments(vec!["1"]),
Err(PathError::InvalidIdentifier { segment: 0 })
);
assert_eq!(
Path::from_segments(vec!["Hello", ", World!"]),
Err(PathError::InvalidIdentifier { segment: 1 })
);
}
#[test]
fn path_from_module_path_and_ident() {
assert_eq!(
Path::new("Planet", "hello::world"),
Path {
segments: vec!["hello", "world", "Planet"]
}
);
assert_eq!(
Path::from_segments(vec!["Earth", "::world"]),
Err(PathError::InvalidIdentifier { segment: 1 })
);
}
#[test]
fn path_get_namespace_and_ident() {
let path = Path::new("Planet", "hello::world");
assert_eq!(path.namespace(), &["hello", "world"]);
assert_eq!(path.ident(), Some("Planet"));
}
#[test]
#[should_panic]
fn path_new_panics_with_invalid_identifiers() {
Path::new("Planet", "hello$!@$::world");
}
#[test]
fn path_display() {
let path = Path::new("Planet", "hello::world").into_portable(&mut Default::default());
assert_eq!("hello::world::Planet", format!("{}", path))
}
}