add support for camino::Utf8PathBuf and camino::Utf8Path (#1103)

This commit is contained in:
0xb002f0
2025-11-08 04:15:29 -05:00
committed by GitHub
parent 2a28cb4aff
commit f361a7177f
5 changed files with 195 additions and 0 deletions

View File

@@ -27,6 +27,7 @@ websocket = ["poem/websocket"]
geo = ["dep:geo-types", "dep:geojson"]
sonic-rs = ["poem/sonic-rs"]
cookie = ["poem/cookie"]
camino = ["dep:camino"]
ulid = ["dep:ulid"]
[dependencies]
@@ -84,6 +85,7 @@ sqlx = { version = "0.8.3", features = [
"sqlite",
"mysql",
], optional = true }
camino = { version = "1.2.1", optional = true }
ulid = { version = "1.2.1", optional = true }
[dev-dependencies]

View File

@@ -52,6 +52,7 @@ To avoid compiling unused dependencies, Poem gates certain features, some of whi
| Feature | Description |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| camino | Integrate with the [`camino` crate](https://crates.io/crates/camino). |
| chrono | Integrate with the [`chrono` crate](https://crates.io/crates/chrono). |
| time | Integrate with the [`time` crate](https://crates.io/crates/time). |
| humantime | Integrate with the [`humantime` crate](https://crates.io/crates/humantime) |

View File

@@ -94,6 +94,7 @@
//!
//! | Feature | Description |
//! |--------------------|----------------------------------------------------------------------------------------|
//! | camino | Integrate with the [`camino` crate](https://crates.io/crates/camino). |
//! | chrono | Integrate with the [`chrono` crate](https://crates.io/crates/chrono). |
//! | time | Integrate with the [`time` crate](https://crates.io/crates/time). |
//! | humantime | Integrate with the [`humantime` crate](https://crates.io/crates/humantime) |

View File

@@ -0,0 +1,189 @@
use std::borrow::Cow;
use camino::{Utf8Path, Utf8PathBuf};
use poem::{http::HeaderValue, web::Field};
use serde_json::Value;
use crate::{
registry::{MetaSchema, MetaSchemaRef},
types::{
ParseError, ParseFromJSON, ParseFromMultipartField, ParseFromParameter, ParseResult,
ToHeader, ToJSON, Type,
},
};
impl Type for Utf8PathBuf {
const IS_REQUIRED: bool = true;
type RawValueType = Self;
type RawElementValueType = Self;
fn name() -> Cow<'static, str> {
"path".into()
}
fn schema_ref() -> MetaSchemaRef {
MetaSchemaRef::Inline(Box::new(MetaSchema::new_with_format("string", "path")))
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
fn raw_element_iter<'a>(
&'a self,
) -> Box<dyn Iterator<Item = &'a Self::RawElementValueType> + 'a> {
Box::new(self.as_raw_value().into_iter())
}
}
impl ParseFromJSON for Utf8PathBuf {
fn parse_from_json(value: Option<Value>) -> ParseResult<Self> {
let value = value.unwrap_or_default();
if let Value::String(value) = value {
Ok(value.into())
} else {
Err(ParseError::expected_type(value))
}
}
}
impl ParseFromParameter for Utf8PathBuf {
fn parse_from_parameter(value: &str) -> ParseResult<Self> {
Ok(Utf8Path::new(value).to_path_buf())
}
}
impl ParseFromMultipartField for Utf8PathBuf {
async fn parse_from_multipart(field: Option<Field>) -> ParseResult<Self> {
match field {
Some(field) => Ok(field.text().await?.into()),
None => Err(ParseError::expected_input()),
}
}
}
impl ToJSON for Utf8PathBuf {
fn to_json(&self) -> Option<Value> {
Some(Value::String(self.to_string()))
}
}
impl ToHeader for Utf8PathBuf {
fn to_header(&self) -> Option<HeaderValue> {
HeaderValue::from_str(self.as_str()).ok()
}
}
impl Type for &Utf8Path {
const IS_REQUIRED: bool = true;
type RawValueType = Self;
type RawElementValueType = Self;
fn name() -> Cow<'static, str> {
"path".into()
}
fn schema_ref() -> MetaSchemaRef {
MetaSchemaRef::Inline(Box::new(MetaSchema::new_with_format("string", "path")))
}
fn as_raw_value(&self) -> Option<&Self::RawValueType> {
Some(self)
}
fn raw_element_iter<'a>(
&'a self,
) -> Box<dyn Iterator<Item = &'a Self::RawElementValueType> + 'a> {
Box::new(self.as_raw_value().into_iter())
}
}
impl ToJSON for &Utf8Path {
fn to_json(&self) -> Option<Value> {
Some(Value::String(self.as_str().to_owned()))
}
}
impl ToHeader for &Utf8Path {
fn to_header(&self) -> Option<HeaderValue> {
HeaderValue::from_str(self.as_str()).ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn type_name() {
assert_eq!(Utf8PathBuf::name(), "path");
}
#[test]
fn parse_from_json_none() {
assert_eq!(
Utf8PathBuf::parse_from_json(None)
.expect_err("unexpectedly succeeded in parsing `None`")
.message(),
ParseError::<Utf8PathBuf>::expected_type(Value::Null).message()
);
}
#[test]
fn parse_from_json_value_null() {
assert_eq!(
Utf8PathBuf::parse_from_json(Some(Value::Null))
.expect_err("unexpectedly succeeded in parsing `Value::Null`")
.message(),
ParseError::<Utf8PathBuf>::expected_type(Value::Null).message()
);
}
#[test]
fn parse_from_json_value_string() {
assert_eq!(
Utf8PathBuf::parse_from_json(Some(Value::String("/a/b/c".to_owned())))
.expect(r#"failed to parse "/a/b/c""#),
Utf8Path::new("/a/b/c")
);
}
#[test]
fn parse_from_parameter() {
assert_eq!(
Utf8PathBuf::parse_from_parameter("/a/b/c").expect(r#"failed to parse "/a/b/c""#),
Utf8Path::new("/a/b/c")
);
}
#[tokio::test]
async fn parse_from_multipart_none() {
assert_eq!(
Utf8PathBuf::parse_from_multipart(None)
.await
.expect_err("unexpectedly succeeded in parsing `None`")
.message(),
ParseError::<Utf8PathBuf>::expected_input().message(),
);
}
#[test]
fn to_json() {
assert_eq!(
Utf8Path::new("/a/b/c").to_json(),
Some(Value::String("/a/b/c".to_owned()))
);
}
#[test]
fn to_header() {
assert_eq!(
Utf8Path::new("/a/b/c").to_header(),
HeaderValue::from_str("/a/b/c").ok()
);
}
}

View File

@@ -4,6 +4,8 @@ mod bool;
mod bson;
mod btreemap;
mod btreeset;
#[cfg(feature = "camino")]
mod camino;
mod char;
#[cfg(feature = "chrono")]
mod chrono;