zenops_safe_relative_path/
single_path_component.rs1use std::fmt;
2
3use crate::{SafeRelativePath, error::Error};
4use serde::de;
5use smol_str::{SmolStr, ToSmolStr};
6
7#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
34pub struct SinglePathComponent(SmolStr);
35
36impl SinglePathComponent {
37 pub fn try_new(v: &str) -> Result<Self, Error> {
42 let path = SafeRelativePath::from_relative_path(v)?;
43 let first = path.0.components().map(|c| c.as_str()).next();
44 if first == Some(v) {
45 Ok(Self(v.to_smolstr()))
46 } else {
47 Err(Error::NotASinglePathComponent(v.to_string()))
48 }
49 }
50
51 pub fn as_safe_relative_path(&self) -> &SafeRelativePath {
59 unsafe { SafeRelativePath::new_unchecked_from_str(self.0.as_str()) }
60 }
61}
62
63impl AsRef<SafeRelativePath> for SinglePathComponent {
64 fn as_ref(&self) -> &SafeRelativePath {
65 self.as_safe_relative_path()
66 }
67}
68
69impl fmt::Display for SinglePathComponent {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 fmt::Display::fmt(&self.0, f)
72 }
73}
74
75impl std::ops::Deref for SinglePathComponent {
76 type Target = SafeRelativePath;
77
78 fn deref(&self) -> &Self::Target {
79 self.as_safe_relative_path()
80 }
81}
82
83#[cfg(feature = "schemars")]
84impl schemars::JsonSchema for SinglePathComponent {
85 fn schema_name() -> std::borrow::Cow<'static, str> {
86 "SinglePathComponent".into()
87 }
88
89 fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
90 schemars::json_schema!({
91 "type": "string",
92 "description": "A single path component — no separators, no `..` traversal.",
93 "pattern": "^[^/]+$",
94 })
95 }
96}
97
98impl<'de> de::Deserialize<'de> for SinglePathComponent {
99 fn deserialize<D: de::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
100 struct Visitor;
101
102 impl<'de> de::Visitor<'de> for Visitor {
103 type Value = SinglePathComponent;
104
105 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
106 write!(f, "single path component")
107 }
108
109 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
110 SinglePathComponent::try_new(v).map_err(de::Error::custom)
111 }
112 }
113
114 d.deserialize_any(Visitor)
115 }
116}