mirror of
https://github.com/poem-web/poem.git
synced 2026-01-25 04:18:25 +00:00
Merge branch 'master' into release
Some checks failed
Release / publish (map[name:poem path:poem registryName:poem]) (push) Has been cancelled
Release / publish (map[name:poem-derive path:poem-derive registryName:poem-derive]) (push) Has been cancelled
Release / publish (map[name:poem-grpc path:poem-grpc registryName:poem-grpc]) (push) Has been cancelled
Release / publish (map[name:poem-grpc-build path:poem-grpc-build registryName:poem-grpc-build]) (push) Has been cancelled
Release / publish (map[name:poem-lambda path:poem-lambda registryName:poem-lambda]) (push) Has been cancelled
Release / publish (map[name:poem-mcpserver path:poem-mcpserver registryName:poem-mcpserver]) (push) Has been cancelled
Release / publish (map[name:poem-mcpserver-macros path:poem-mcpserver-macros registryName:poem-mcpserver-macros]) (push) Has been cancelled
Release / publish (map[name:poem-openapi path:poem-openapi registryName:poem-openapi]) (push) Has been cancelled
Release / publish (map[name:poem-openapi-derive path:poem-openapi-derive registryName:poem-openapi-derive]) (push) Has been cancelled
Some checks failed
Release / publish (map[name:poem path:poem registryName:poem]) (push) Has been cancelled
Release / publish (map[name:poem-derive path:poem-derive registryName:poem-derive]) (push) Has been cancelled
Release / publish (map[name:poem-grpc path:poem-grpc registryName:poem-grpc]) (push) Has been cancelled
Release / publish (map[name:poem-grpc-build path:poem-grpc-build registryName:poem-grpc-build]) (push) Has been cancelled
Release / publish (map[name:poem-lambda path:poem-lambda registryName:poem-lambda]) (push) Has been cancelled
Release / publish (map[name:poem-mcpserver path:poem-mcpserver registryName:poem-mcpserver]) (push) Has been cancelled
Release / publish (map[name:poem-mcpserver-macros path:poem-mcpserver-macros registryName:poem-mcpserver-macros]) (push) Has been cancelled
Release / publish (map[name:poem-openapi path:poem-openapi registryName:poem-openapi]) (push) Has been cancelled
Release / publish (map[name:poem-openapi-derive path:poem-openapi-derive registryName:poem-openapi-derive]) (push) Has been cancelled
This commit is contained in:
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -48,10 +48,10 @@ jobs:
|
||||
- 5432:5432
|
||||
options: -e POSTGRES_PASSWORD=123456 -e POSTGRES_DB=test_poem_sessions
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# Use nightly Rust to check the format
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt, clippy
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
- name: Check Format
|
||||
run: cargo fmt --all -- --check
|
||||
# Switch to stable Rust
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt, clippy
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
uses: arduino/setup-protoc@v1
|
||||
|
||||
# Use nightly Rust to check the format
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt, clippy
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
run: cargo fmt --all -- --check
|
||||
working-directory: examples
|
||||
# Switch to stable Rust
|
||||
- uses: actions-rs/toolchain@v1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -10,6 +10,7 @@ members = [
|
||||
"poem-grpc",
|
||||
"poem-mcpserver",
|
||||
"poem-mcpserver-macros",
|
||||
"poem-worker",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
@@ -22,8 +23,8 @@ repository = "https://github.com/poem-web/poem"
|
||||
rust-version = "1.85"
|
||||
|
||||
[workspace.dependencies]
|
||||
poem = { path = "poem", version = "3.1.11", default-features = false }
|
||||
poem-derive = { path = "poem-derive", version = "3.1.11" }
|
||||
poem = { path = "poem", version = "3.1.12", default-features = false }
|
||||
poem-derive = { path = "poem-derive", version = "3.1.12" }
|
||||
poem-openapi-derive = { path = "poem-openapi-derive", version = "5.1.15" }
|
||||
poem-grpc-build = { path = "poem-grpc-build", version = "0.5.6" }
|
||||
poem-mcpserver-macros = { path = "poem-mcpserver-macros", version = "0.2.4" }
|
||||
@@ -34,7 +35,7 @@ quote = "1.0.9"
|
||||
syn = { version = "2.0" }
|
||||
tokio = "1.39.1"
|
||||
serde_json = "1.0.68"
|
||||
sonic-rs = "0.3.5"
|
||||
sonic-rs = "0.5.1"
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
thiserror = "2.0"
|
||||
regex = "1.5.5"
|
||||
@@ -56,7 +57,7 @@ async-stream = "0.3.6"
|
||||
tokio-util = "0.7.14"
|
||||
rand = "0.9.0"
|
||||
time = "0.3.39"
|
||||
schemars = "0.9"
|
||||
schemars = "1.0"
|
||||
|
||||
# rustls, update together
|
||||
rustls = "0.23"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["poem/*", "openapi/*", "grpc/*", "mcpserver/*"]
|
||||
exclude = ["poem/worker-hello-world"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
@@ -14,6 +15,7 @@ poem-openapi = { path = "../poem-openapi", features = ["swagger-ui"] }
|
||||
poem-lambda = { path = "../poem-lambda" }
|
||||
poem-grpc-build = { path = "../poem-grpc-build" }
|
||||
poem-mcpserver = { path = "../poem-mcpserver" }
|
||||
poem-worker = { path = "../poem-worker" }
|
||||
|
||||
tokio = "1.17.0"
|
||||
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
poem-mcpserver = { workspace = true, features = ["streamable-http"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
schemars = "0.9"
|
||||
schemars = "1.0"
|
||||
poem = { workspace = true, features = ["sse"] }
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] }
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
@@ -6,5 +6,5 @@ edition = "2021"
|
||||
[dependencies]
|
||||
poem-mcpserver.workspace = true
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
schemars = "0.9"
|
||||
schemars = "1.0"
|
||||
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "sync"] }
|
||||
|
||||
@@ -21,7 +21,7 @@ use tokio::{spawn, time::sleep};
|
||||
|
||||
#[handler]
|
||||
fn hello(Path(name): Path<String>) -> String {
|
||||
format!("hello: {}", name)
|
||||
format!("hello: {name}")
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@@ -55,7 +55,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||
{
|
||||
Ok(result) => result.rustls_key,
|
||||
Err(err) => {
|
||||
eprintln!("failed to issue certificate: {}", err);
|
||||
eprintln!("failed to issue certificate: {err}");
|
||||
sleep(Duration::from_secs(60 * 5)).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -8,10 +8,13 @@ publish.workspace = true
|
||||
poem = { workspace = true, features = ["opentelemetry"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tracing-subscriber.workspace = true
|
||||
opentelemetry = { version = "0.29.0", features = ["metrics"] }
|
||||
opentelemetry_sdk = { version = "0.29.0", features = ["rt-tokio"] }
|
||||
opentelemetry-http = { version = "0.29.0" }
|
||||
opentelemetry-otlp = { version = "0.29.0", default-features = false, features = ["trace", "grpc-tonic"] }
|
||||
opentelemetry = { version = "0.30.0", features = ["metrics"] }
|
||||
opentelemetry_sdk = { version = "0.30.0", features = ["rt-tokio"] }
|
||||
opentelemetry-http = { version = "0.30.0" }
|
||||
opentelemetry-otlp = { version = "0.30.0", default-features = false, features = [
|
||||
"trace",
|
||||
"grpc-tonic",
|
||||
] }
|
||||
reqwest = "0.12"
|
||||
|
||||
[[bin]]
|
||||
|
||||
@@ -8,7 +8,7 @@ publish.workspace = true
|
||||
poem = { workspace = true, features = ["redis-session"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
tracing-subscriber.workspace = true
|
||||
redis = { version = "0.31", features = [
|
||||
redis = { version = "0.32", features = [
|
||||
"aio",
|
||||
"tokio-comp",
|
||||
"connection-manager",
|
||||
|
||||
4
examples/poem/worker-hello-world/.gitignore
vendored
Normal file
4
examples/poem/worker-hello-world/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
target
|
||||
node_modules
|
||||
.wrangler
|
||||
build
|
||||
17
examples/poem/worker-hello-world/Cargo.toml
Normal file
17
examples/poem/worker-hello-world/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "examples-worker-hello-world"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[workspace]
|
||||
|
||||
[package.metadata.release]
|
||||
release = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
worker = { version = "0.6.0" }
|
||||
poem = { path = "../../../poem", default-features = false }
|
||||
poem-worker = { path = "../../../poem-worker" }
|
||||
6
examples/poem/worker-hello-world/README.md
Normal file
6
examples/poem/worker-hello-world/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# Poem with Cloudflare worker
|
||||
|
||||
## Prequirement
|
||||
|
||||
- [worker-build](https://github.com/cloudflare/workers-rs/tree/main/worker-build)
|
||||
- [wranger](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
|
||||
15
examples/poem/worker-hello-world/src/lib.rs
Normal file
15
examples/poem/worker-hello-world/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
use poem::{get, handler, web::Path, Route};
|
||||
use poem_worker::{CloudflareProperties, Server};
|
||||
use worker::event;
|
||||
|
||||
#[handler]
|
||||
fn hello(Path(name): Path<String>, _cf: CloudflareProperties) -> String {
|
||||
format!("hello: {}", name)
|
||||
}
|
||||
|
||||
#[event(start)]
|
||||
fn start() {
|
||||
let app = Route::new().at("/hello/:name", get(hello));
|
||||
|
||||
Server::new().run(app);
|
||||
}
|
||||
6
examples/poem/worker-hello-world/wrangler.toml
Normal file
6
examples/poem/worker-hello-world/wrangler.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
name = "worker-hello-world"
|
||||
main = "build/worker/shim.mjs"
|
||||
compatibility_date = "2025-07-20"
|
||||
|
||||
[build]
|
||||
command = "worker-build --release"
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-derive"
|
||||
version = "3.1.11"
|
||||
version = "3.1.12"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-grpc-build"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-grpc"
|
||||
version = "0.5.6"
|
||||
version = "0.5.7"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Error, ErrorKind, Result},
|
||||
io::{Error, Result},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
@@ -54,8 +54,7 @@ where
|
||||
|
||||
fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<()> {
|
||||
let mut ser = serde_json::Serializer::new(buf.writer());
|
||||
item.serialize(&mut ser)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
item.serialize(&mut ser).map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +66,7 @@ impl<U: DeserializeOwned + Send + 'static> Decoder for JsonDecoder<U> {
|
||||
|
||||
fn decode(&mut self, buf: &[u8]) -> Result<Self::Item> {
|
||||
let mut de = serde_json::Deserializer::from_slice(buf);
|
||||
U::deserialize(&mut de).map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
U::deserialize(&mut de).map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +115,7 @@ where
|
||||
fn encode(&mut self, item: Self::Item, buf: &mut BytesMut) -> Result<()> {
|
||||
let mut ser = serde_json::Serializer::new(buf.writer());
|
||||
item.serialize(i64string_serializer::I64ToStringSerializer(&mut ser))
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
.map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +128,6 @@ impl<U: DeserializeOwned + Send + 'static> Decoder for JsonI64ToStringDecoder<U>
|
||||
fn decode(&mut self, buf: &[u8]) -> Result<Self::Item> {
|
||||
let mut de = serde_json::Deserializer::from_slice(buf);
|
||||
U::deserialize(i64string_deserializer::I64ToStringDeserializer(&mut de))
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
.map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ pub trait Codec: Default {
|
||||
/// Returns whether the specified content type is supported
|
||||
#[inline]
|
||||
fn check_content_type(&self, ct: &str) -> bool {
|
||||
Self::CONTENT_TYPES.iter().any(|value| *value == ct)
|
||||
Self::CONTENT_TYPES.contains(&ct)
|
||||
}
|
||||
|
||||
/// Create the encoder
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Error, ErrorKind, Result},
|
||||
io::{Error, Result},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
@@ -49,9 +49,7 @@ where
|
||||
type Item = T;
|
||||
|
||||
fn encode(&mut self, message: Self::Item, buf: &mut BytesMut) -> Result<()> {
|
||||
message
|
||||
.encode(buf)
|
||||
.map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
message.encode(buf).map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +63,6 @@ where
|
||||
type Item = U;
|
||||
|
||||
fn decode(&mut self, buf: &[u8]) -> Result<Self::Item> {
|
||||
U::decode(buf).map_err(|err| Error::new(ErrorKind::Other, err))
|
||||
U::decode(buf).map_err(Error::other)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ use tower_service::Service;
|
||||
pub(crate) enum MaybeHttpsStream {
|
||||
TcpStream(TokioIo<TcpStream>),
|
||||
TlsStream {
|
||||
stream: TokioIo<TlsStream<TcpStream>>,
|
||||
stream: Box<TokioIo<TlsStream<TcpStream>>>,
|
||||
is_http2: bool,
|
||||
},
|
||||
}
|
||||
@@ -131,13 +131,13 @@ async fn do_connect(
|
||||
.unwrap_or_else(|| if scheme == Scheme::HTTPS { 443 } else { 80 });
|
||||
|
||||
if scheme == Scheme::HTTP {
|
||||
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||
let stream = TcpStream::connect(format!("{host}:{port}")).await?;
|
||||
Ok(MaybeHttpsStream::TcpStream(TokioIo::new(stream)))
|
||||
} else if scheme == Scheme::HTTPS {
|
||||
let mut tls_config = tls_config.unwrap_or_else(default_tls_config);
|
||||
tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
|
||||
let connector = TlsConnector::from(Arc::new(tls_config));
|
||||
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||
let stream = TcpStream::connect(format!("{host}:{port}")).await?;
|
||||
let domain = host.try_into().map_err(IoError::other)?;
|
||||
let mut is_http2 = false;
|
||||
let stream = connector
|
||||
@@ -146,7 +146,7 @@ async fn do_connect(
|
||||
})
|
||||
.await?;
|
||||
Ok(MaybeHttpsStream::TlsStream {
|
||||
stream: TokioIo::new(stream),
|
||||
stream: Box::new(TokioIo::new(stream)),
|
||||
is_http2,
|
||||
})
|
||||
} else {
|
||||
|
||||
@@ -95,7 +95,7 @@ impl proto::Health for HealthService {
|
||||
Ok(Response::new(Streaming::new(async_stream::try_stream! {
|
||||
while let Some(service_status) = stream.next().await {
|
||||
let res = service_status.get(&service_name);
|
||||
let status = res.ok_or_else(|| Status::new(Code::NotFound).with_message(format!("service `{}` not found", service_name)))?
|
||||
let status = res.ok_or_else(|| Status::new(Code::NotFound).with_message(format!("service `{service_name}` not found")))?
|
||||
.to_proto()
|
||||
.into();
|
||||
yield proto::HealthCheckResponse { status };
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-lambda"
|
||||
version = "5.1.3"
|
||||
version = "5.1.4"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{io::ErrorKind, ops::Deref, sync::Arc};
|
||||
use std::{ops::Deref, sync::Arc};
|
||||
|
||||
pub use lambda_http::lambda_runtime::Error;
|
||||
use lambda_http::{
|
||||
@@ -73,7 +73,7 @@ pub async fn run(ep: impl IntoEndpoint) -> Result<(), Error> {
|
||||
let data = body
|
||||
.into_vec()
|
||||
.await
|
||||
.map_err(|_| std::io::Error::new(ErrorKind::Other, "invalid request"))?;
|
||||
.map_err(|_| std::io::Error::other("invalid request"))?;
|
||||
let mut lambda_resp = poem::http::Response::new(if data.is_empty() {
|
||||
LambdaBody::Empty
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-mcpserver-macros"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# [Unreleased]
|
||||
|
||||
- bump `schemars` to 1.0
|
||||
|
||||
# [0.2.4] 20250-06-06
|
||||
|
||||
- bump `schemars` to 0.9
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-mcpserver"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -28,7 +28,7 @@ schemars.workspace = true
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
time = { workspace = true, features = ["macros", "formatting", "parsing"] }
|
||||
tokio = { workspace = true, features = ["io-std", "io-util", "rt"] }
|
||||
tokio = { workspace = true, features = ["io-std", "io-util", "rt", "net"] }
|
||||
poem = { workspace = true, features = ["sse"], optional = true }
|
||||
rand.workspace = true
|
||||
tokio-stream.workspace = true
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
[dependencies]
|
||||
poem-mcpserver.workspace = "*"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8.22"
|
||||
schemars = "1.0"
|
||||
```
|
||||
|
||||
```rust
|
||||
|
||||
@@ -86,8 +86,7 @@ impl Tools for NoTools {
|
||||
#[inline]
|
||||
async fn call(&mut self, name: &str, _arguments: Value) -> Result<ToolsCallResponse, RpcError> {
|
||||
Err(RpcError::method_not_found(format!(
|
||||
"tool '{}' not found",
|
||||
name
|
||||
"tool '{name}' not found"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-openapi-derive"
|
||||
version = "5.1.15"
|
||||
version = "5.1.16"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -302,13 +302,13 @@ fn generate_operation(
|
||||
.or(ignore_case)
|
||||
.or(api_args.ignore_case)
|
||||
.unwrap_or(false);
|
||||
let extract_param_name = is_path
|
||||
.then(|| {
|
||||
let n = format!("param{path_param_count}");
|
||||
path_param_count += 1;
|
||||
n
|
||||
})
|
||||
.unwrap_or_else(|| param_name.clone());
|
||||
let extract_param_name = if is_path {
|
||||
let n = format!("param{path_param_count}");
|
||||
path_param_count += 1;
|
||||
n
|
||||
} else {
|
||||
param_name.clone()
|
||||
};
|
||||
use_args.push(pname.clone());
|
||||
|
||||
if !hidden {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem-openapi"
|
||||
version = "5.1.15"
|
||||
version = "5.1.16"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
@@ -6,6 +6,7 @@ use poem::{
|
||||
use crate::{auth::BearerAuthorization, error::AuthorizationError};
|
||||
|
||||
/// Used to extract the token68 from the request.
|
||||
#[derive(Debug)]
|
||||
pub struct Bearer {
|
||||
/// token
|
||||
pub token: String,
|
||||
|
||||
@@ -2,7 +2,7 @@ fn normalize_path(path: &str) -> String {
|
||||
if path.is_empty() {
|
||||
"/".to_string()
|
||||
} else if !path.starts_with('/') {
|
||||
format!("/{}", path)
|
||||
format!("/{path}")
|
||||
} else {
|
||||
path.to_string()
|
||||
}
|
||||
|
||||
@@ -588,18 +588,18 @@ impl PartialEq for MetaTag {
|
||||
|
||||
impl Eq for MetaTag {}
|
||||
|
||||
impl PartialOrd for MetaTag {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.name.cmp(other.name))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for MetaTag {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.name.cmp(other.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for MetaTag {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for MetaTag {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
|
||||
@@ -104,7 +104,7 @@ mod tests {
|
||||
for case in invalid_cases {
|
||||
let result = Duration::parse_from_json(Some(Value::String(case.to_string())));
|
||||
dbg!(&result);
|
||||
assert!(result.is_err(), "Should have failed for: {}", case);
|
||||
assert!(result.is_err(), "Should have failed for: {case}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::{
|
||||
use poem::web::Field as PoemField;
|
||||
use tokio::{
|
||||
fs::File,
|
||||
io::{AsyncRead, AsyncReadExt, AsyncSeek, Error as IoError, ErrorKind},
|
||||
io::{AsyncRead, AsyncReadExt, AsyncSeek, Error as IoError},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -64,13 +64,8 @@ impl Upload {
|
||||
|
||||
/// Consumes this body object to return a [`String`] that contains all data.
|
||||
pub async fn into_string(self) -> Result<String, IoError> {
|
||||
String::from_utf8(
|
||||
self.into_vec()
|
||||
.await
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err))?
|
||||
.to_vec(),
|
||||
)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err))
|
||||
String::from_utf8(self.into_vec().await.map_err(IoError::other)?.to_vec())
|
||||
.map_err(IoError::other)
|
||||
}
|
||||
|
||||
/// Consumes this body object to return a reader.
|
||||
|
||||
@@ -26,9 +26,9 @@ pub use unique_items::UniqueItems;
|
||||
|
||||
use crate::registry::MetaSchema;
|
||||
|
||||
/// Represents a validator for validate the input value.
|
||||
/// Represents a validator to validate the input value.
|
||||
pub trait Validator<T>: Display {
|
||||
/// Check the value is valid.
|
||||
/// Checks if the value is valid.
|
||||
fn check(&self, value: &T) -> bool;
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ fn field_deprecated() {
|
||||
}
|
||||
|
||||
let meta = get_meta::<Obj>();
|
||||
assert_eq!(meta.properties[0].1.unwrap_inline().deprecated, true);
|
||||
assert!(meta.properties[0].1.unwrap_inline().deprecated);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
27
poem-worker/Cargo.toml
Normal file
27
poem-worker/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
||||
[package]
|
||||
name = "poem-worker"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
documentation.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde = { workspace = true }
|
||||
worker = { version = "0.6.0", features = ["http"] }
|
||||
bytes = { workspace = true }
|
||||
http = { workspace = true }
|
||||
http-body = "1.0.1"
|
||||
http-body-util = "0.1.0"
|
||||
async-trait = "0.1.88"
|
||||
|
||||
poem = { workspace = true, default-features = false }
|
||||
|
||||
tokio = { workspace = true }
|
||||
|
||||
[features]
|
||||
queue = ["worker/queue"]
|
||||
d1 = ["worker/d1"]
|
||||
50
poem-worker/src/body.rs
Normal file
50
poem-worker/src/body.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::{
|
||||
io,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use http_body::{Body, Frame, SizeHint};
|
||||
|
||||
pub struct WorkerBody(pub(crate) worker::Body);
|
||||
|
||||
impl Body for WorkerBody {
|
||||
type Data = bytes::Bytes;
|
||||
type Error = io::Error;
|
||||
|
||||
#[inline]
|
||||
fn poll_frame(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
||||
let body = self.get_mut();
|
||||
|
||||
let inner = Pin::new(&mut body.0);
|
||||
|
||||
let res = inner.poll_frame(cx);
|
||||
|
||||
match res {
|
||||
Poll::Pending => Poll::Pending,
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Ready(Some(Ok(r))) => Poll::Ready(Some(Ok(r))),
|
||||
Poll::Ready(Some(Err(e))) => match e {
|
||||
worker::Error::Io(e) => Poll::Ready(Some(Err(io::Error::other(e)))),
|
||||
_ => Poll::Ready(Some(Err(io::Error::other(e)))),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
self.0.size_hint()
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.0.is_end_stream()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_worker_body(body: poem::Body) -> Result<worker::Body, worker::Error> {
|
||||
let stream = body.into_bytes_stream();
|
||||
|
||||
worker::Body::from_stream(stream)
|
||||
}
|
||||
93
poem-worker/src/cloudflare.rs
Normal file
93
poem-worker/src/cloudflare.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use http::StatusCode;
|
||||
use poem::{FromRequest, Request, RequestBody};
|
||||
use serde::de::DeserializeOwned;
|
||||
use worker::{Cf, TlsClientAuth};
|
||||
|
||||
pub struct CloudflareProperties(Cf);
|
||||
|
||||
impl CloudflareProperties {
|
||||
pub fn colo(&self) -> String {
|
||||
self.0.colo()
|
||||
}
|
||||
|
||||
pub fn asn(&self) -> Option<u32> {
|
||||
self.0.asn()
|
||||
}
|
||||
|
||||
pub fn as_organization(&self) -> Option<String> {
|
||||
self.0.as_organization()
|
||||
}
|
||||
|
||||
pub fn country(&self) -> Option<String> {
|
||||
self.0.country()
|
||||
}
|
||||
|
||||
pub fn http_protocol(&self) -> String {
|
||||
self.0.http_protocol()
|
||||
}
|
||||
|
||||
pub fn tls_cipher(&self) -> String {
|
||||
self.0.tls_cipher()
|
||||
}
|
||||
|
||||
pub fn tls_client_auth(&self) -> Option<TlsClientAuth> {
|
||||
self.0.tls_client_auth()
|
||||
}
|
||||
|
||||
pub fn tls_version(&self) -> String {
|
||||
self.0.tls_version()
|
||||
}
|
||||
|
||||
pub fn city(&self) -> Option<String> {
|
||||
self.0.city()
|
||||
}
|
||||
|
||||
pub fn continent(&self) -> Option<String> {
|
||||
self.0.continent()
|
||||
}
|
||||
|
||||
pub fn coordinates(&self) -> Option<(f32, f32)> {
|
||||
self.0.coordinates()
|
||||
}
|
||||
|
||||
pub fn postal_code(&self) -> Option<String> {
|
||||
self.0.postal_code()
|
||||
}
|
||||
|
||||
pub fn metro_code(&self) -> Option<String> {
|
||||
self.0.metro_code()
|
||||
}
|
||||
|
||||
pub fn region(&self) -> Option<String> {
|
||||
self.0.region()
|
||||
}
|
||||
|
||||
pub fn region_code(&self) -> Option<String> {
|
||||
self.0.region_code()
|
||||
}
|
||||
|
||||
pub fn timezone_name(&self) -> String {
|
||||
self.0.timezone_name()
|
||||
}
|
||||
|
||||
pub fn is_eu_country(&self) -> bool {
|
||||
self.0.is_eu_country()
|
||||
}
|
||||
|
||||
pub fn host_metadata<T: DeserializeOwned>(&self) -> Result<Option<T>, worker::Error> {
|
||||
self.0.host_metadata::<T>()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromRequest<'a> for CloudflareProperties {
|
||||
async fn from_request(req: &'a Request, _body: &mut RequestBody) -> Result<Self, poem::Error> {
|
||||
let cf = req.data::<Cf>().ok_or_else(|| {
|
||||
poem::Error::from_string(
|
||||
"failed to get incoming cloudflare properties",
|
||||
StatusCode::BAD_REQUEST,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(CloudflareProperties(cf.clone()))
|
||||
}
|
||||
}
|
||||
34
poem-worker/src/context.rs
Normal file
34
poem-worker/src/context.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use http::StatusCode;
|
||||
use poem::{FromRequest, Request, RequestBody};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context(Arc<worker::Context>);
|
||||
|
||||
impl Context {
|
||||
pub fn new(ctx: worker::Context) -> Self {
|
||||
Self(Arc::new(ctx))
|
||||
}
|
||||
|
||||
pub fn wait_until<F>(&self, future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
{
|
||||
self.0.wait_until(future);
|
||||
}
|
||||
|
||||
pub fn pass_through_on_exception(&self) {
|
||||
self.0.pass_through_on_exception();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromRequest<'a> for Context {
|
||||
async fn from_request(req: &'a Request, _body: &mut RequestBody) -> poem::Result<Self> {
|
||||
let ctx = req.data::<Context>().ok_or_else(|| {
|
||||
poem::Error::from_string("failed to get incoming context", StatusCode::BAD_REQUEST)
|
||||
})?;
|
||||
|
||||
Ok(ctx.clone())
|
||||
}
|
||||
}
|
||||
90
poem-worker/src/env.rs
Normal file
90
poem-worker/src/env.rs
Normal file
@@ -0,0 +1,90 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use http::StatusCode;
|
||||
use poem::{FromRequest, Request, RequestBody};
|
||||
use serde::de::DeserializeOwned;
|
||||
use worker::{
|
||||
Ai, AnalyticsEngineDataset, Bucket, DynamicDispatcher, EnvBinding, Fetcher, Hyperdrive,
|
||||
ObjectNamespace, Secret, Var, kv::KvStore,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Env(pub(crate) Arc<worker::Env>);
|
||||
|
||||
impl Env {
|
||||
pub fn new(env: worker::Env) -> Self {
|
||||
Self(Arc::new(env))
|
||||
}
|
||||
|
||||
pub fn get_binding<T: EnvBinding>(&self, name: &str) -> worker::Result<T> {
|
||||
self.0.get_binding(name)
|
||||
}
|
||||
|
||||
pub fn ai(&self, binding: &str) -> worker::Result<Ai> {
|
||||
self.0.ai(binding)
|
||||
}
|
||||
|
||||
pub fn analytics_engine(&self, binding: &str) -> worker::Result<AnalyticsEngineDataset> {
|
||||
self.0.analytics_engine(binding)
|
||||
}
|
||||
|
||||
pub fn secret(&self, binding: &str) -> worker::Result<Secret> {
|
||||
self.0.secret(binding)
|
||||
}
|
||||
|
||||
pub fn var(&self, binding: &str) -> worker::Result<Var> {
|
||||
self.0.var(binding)
|
||||
}
|
||||
|
||||
pub fn object_var<T: DeserializeOwned>(&self, binding: &str) -> worker::Result<T> {
|
||||
self.0.object_var(binding)
|
||||
}
|
||||
|
||||
pub fn kv(&self, binding: &str) -> worker::Result<KvStore> {
|
||||
self.0.kv(binding)
|
||||
}
|
||||
|
||||
pub fn durable_object(&self, binding: &str) -> worker::Result<ObjectNamespace> {
|
||||
self.0.durable_object(binding)
|
||||
}
|
||||
|
||||
pub fn dynamic_dispatcher(&self, binding: &str) -> worker::Result<DynamicDispatcher> {
|
||||
self.0.dynamic_dispatcher(binding)
|
||||
}
|
||||
|
||||
pub fn service(&self, binding: &str) -> worker::Result<Fetcher> {
|
||||
self.0.service(binding)
|
||||
}
|
||||
|
||||
#[cfg(feature = "queue")]
|
||||
pub fn queue(&self, binding: &str) -> worker::Result<worker::Queue> {
|
||||
self.0.queue(binding)
|
||||
}
|
||||
|
||||
pub fn bucket(&self, binding: &str) -> worker::Result<Bucket> {
|
||||
self.0.bucket(binding)
|
||||
}
|
||||
|
||||
#[cfg(feature = "d1")]
|
||||
pub fn d1(&self, binding: &str) -> worker::Result<worker::D1Database> {
|
||||
self.0.d1(binding)
|
||||
}
|
||||
|
||||
pub fn assets(&self, binding: &str) -> worker::Result<Fetcher> {
|
||||
self.0.assets(binding)
|
||||
}
|
||||
|
||||
pub fn hyperdrive(&self, binding: &str) -> worker::Result<Hyperdrive> {
|
||||
self.0.hyperdrive(binding)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromRequest<'a> for Env {
|
||||
async fn from_request(req: &'a Request, _body: &mut RequestBody) -> poem::Result<Self> {
|
||||
let env = req.data::<Env>().ok_or_else(|| {
|
||||
poem::Error::from_string("failed to get incoming env", StatusCode::BAD_REQUEST)
|
||||
})?;
|
||||
|
||||
Ok(env.clone())
|
||||
}
|
||||
}
|
||||
14
poem-worker/src/lib.rs
Normal file
14
poem-worker/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
pub(crate) mod body;
|
||||
pub(crate) mod req;
|
||||
|
||||
mod cloudflare;
|
||||
pub use cloudflare::*;
|
||||
|
||||
mod env;
|
||||
pub use env::*;
|
||||
|
||||
mod context;
|
||||
pub use context::*;
|
||||
|
||||
mod server;
|
||||
pub use server::*;
|
||||
61
poem-worker/src/req.rs
Normal file
61
poem-worker/src/req.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use http::uri::Scheme;
|
||||
use http_body_util::combinators::BoxBody;
|
||||
use poem::{
|
||||
Request,
|
||||
web::{LocalAddr, RemoteAddr},
|
||||
};
|
||||
use worker::{HttpRequest, Result};
|
||||
|
||||
pub fn build_poem_req(req: HttpRequest) -> Result<poem::Request> {
|
||||
let headers = req.headers();
|
||||
|
||||
let local_addr = if let Some(client_ip) = headers.get("cf-connecting-ip") {
|
||||
let client_ip = client_ip
|
||||
.to_str()
|
||||
.map_err(|e| worker::Error::RustError(format!("{e}")))?;
|
||||
|
||||
let ip_addr = client_ip
|
||||
.parse::<IpAddr>()
|
||||
.map_err(|e| worker::Error::RustError(format!("{e}")))?;
|
||||
|
||||
let addr = SocketAddr::new(ip_addr, 0);
|
||||
|
||||
LocalAddr(poem::Addr::SocketAddr(addr))
|
||||
} else {
|
||||
LocalAddr::default()
|
||||
};
|
||||
|
||||
let remote_addr = RemoteAddr(poem::Addr::Custom("worker", "".into()));
|
||||
let scheme = Scheme::HTTPS;
|
||||
|
||||
let (parts, body) = req.into_parts();
|
||||
let body = crate::body::WorkerBody(body);
|
||||
let boxed_body = BoxBody::new(body);
|
||||
|
||||
let body = poem::Body::from(boxed_body);
|
||||
let request_parts = poem::RequestParts::from((parts, local_addr, remote_addr, scheme));
|
||||
|
||||
Ok(Request::from_parts(request_parts, body))
|
||||
}
|
||||
|
||||
pub fn build_worker_resp(resp: poem::Response) -> Result<worker::HttpResponse> {
|
||||
let (parts, body) = resp.into_parts();
|
||||
let body = crate::body::build_worker_body(body)?;
|
||||
|
||||
let mut builder = http::Response::builder()
|
||||
.status(parts.status)
|
||||
.version(parts.version)
|
||||
.extension(parts.extensions);
|
||||
|
||||
for (key, value) in parts.headers {
|
||||
if let Some(key) = key {
|
||||
builder = builder.header(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
let resp = builder.body(body)?;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
67
poem-worker/src/server.rs
Normal file
67
poem-worker/src/server.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use async_trait::async_trait;
|
||||
use poem::endpoint::Endpoint;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
#[worker::event(fetch)]
|
||||
pub async fn fetch(
|
||||
request: worker::Request,
|
||||
_env: worker::Env,
|
||||
_ctx: worker::Context,
|
||||
) -> Result<worker::Response, worker::Error> {
|
||||
let cf = request.cf().cloned();
|
||||
|
||||
let http_req = worker::HttpRequest::try_from(request)?;
|
||||
let mut poem_req = crate::req::build_poem_req(http_req)?;
|
||||
|
||||
if let Some(cf) = cf {
|
||||
poem_req.set_data(cf);
|
||||
}
|
||||
|
||||
poem_req.set_data(crate::Env::new(_env));
|
||||
|
||||
// TODO: handle error
|
||||
let app = SERVER_INSTANCE.get().unwrap();
|
||||
|
||||
let resp = app.get_poem_response(poem_req).await;
|
||||
let worker_resp = crate::req::build_worker_resp(resp)?;
|
||||
let resp = worker::Response::try_from(worker_resp)?;
|
||||
|
||||
Ok(resp)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
trait GetResponseInner: Send + Sync + 'static {
|
||||
async fn get_poem_response(&self, req: poem::Request) -> poem::Response;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<E: Endpoint + Send + Sync + 'static> GetResponseInner for E {
|
||||
async fn get_poem_response(&self, req: poem::Request) -> poem::Response {
|
||||
self.get_response(req).await
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Server {}
|
||||
|
||||
type BoxedGetResponseInner = Box<dyn GetResponseInner>;
|
||||
|
||||
static SERVER_INSTANCE: OnceCell<BoxedGetResponseInner> = OnceCell::const_new();
|
||||
|
||||
impl Default for Server {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn run(&self, app: impl Endpoint + 'static) {
|
||||
SERVER_INSTANCE
|
||||
.set(Box::new(app))
|
||||
.map_err(|_| "Server instance can only be set once".to_string())
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
# [3.1.12] 2025-07-28
|
||||
|
||||
- Bump `tokio-tungstenite` to `0.27`
|
||||
- Bump `opentelemetry`
|
||||
- Bump `x509-parser` to `0.17`
|
||||
|
||||
# [3.1.11] 2025-06-06
|
||||
|
||||
- bump `tokio-tungstenite` to `0.26`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "poem"
|
||||
version = "3.1.11"
|
||||
version = "3.1.12"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
@@ -21,7 +21,13 @@ categories = [
|
||||
[features]
|
||||
default = ["server"]
|
||||
|
||||
server = ["tokio/rt", "tokio/net", "hyper/server"]
|
||||
server = [
|
||||
"tokio/rt",
|
||||
"tokio/net",
|
||||
"hyper/server",
|
||||
"hyper-util/server-auto",
|
||||
"hyper-util/tokio",
|
||||
]
|
||||
websocket = ["tokio/rt", "tokio-tungstenite", "base64"]
|
||||
multipart = ["multer"]
|
||||
rustls = ["server", "tokio-rustls", "rustls-pemfile"]
|
||||
@@ -76,9 +82,9 @@ bytes.workspace = true
|
||||
futures-util = { workspace = true, features = ["sink"] }
|
||||
http.workspace = true
|
||||
hyper = { version = "1.0.0", features = ["http1", "http2"] }
|
||||
hyper-util = { version = "0.1.6", features = ["server-auto", "tokio"] }
|
||||
hyper-util = { version = "0.1.16", features = ["tokio"] }
|
||||
http-body-util = "0.1.0"
|
||||
tokio = { workspace = true, features = ["sync", "time", "macros", "net"] }
|
||||
tokio = { workspace = true, features = ["sync", "time", "macros"] }
|
||||
tokio-util = { workspace = true, features = ["io"] }
|
||||
serde.workspace = true
|
||||
sonic-rs = { workspace = true, optional = true }
|
||||
@@ -99,7 +105,7 @@ sync_wrapper = { version = "1.0.0", features = ["futures"] }
|
||||
|
||||
# Non-feature optional dependencies
|
||||
multer = { version = "3.0.0", features = ["tokio"], optional = true }
|
||||
tokio-tungstenite = { version = "0.26.2", optional = true }
|
||||
tokio-tungstenite = { version = "0.27", optional = true }
|
||||
tokio-rustls = { workspace = true, optional = true }
|
||||
rustls-pemfile = { version = "2.0.0", optional = true }
|
||||
async-compression = { version = "0.4.0", optional = true, features = [
|
||||
@@ -119,7 +125,7 @@ chrono = { workspace = true, optional = true, default-features = false, features
|
||||
time = { version = "0.3", optional = true }
|
||||
mime_guess = { version = "2.0.3", optional = true }
|
||||
rand = { version = "0.9.0", optional = true }
|
||||
redis = { version = "0.31", optional = true, features = [
|
||||
redis = { version = "0.32", optional = true, features = [
|
||||
"aio",
|
||||
"tokio-comp",
|
||||
"connection-manager",
|
||||
@@ -131,13 +137,13 @@ libcookie = { package = "cookie", version = "0.18", features = [
|
||||
"key-expansion",
|
||||
"secure",
|
||||
], optional = true }
|
||||
opentelemetry-http = { version = "0.29.0", optional = true }
|
||||
opentelemetry-http = { version = "0.30", optional = true }
|
||||
opentelemetry-semantic-conventions = { version = "0.30.0", optional = true, features = [
|
||||
"semconv_experimental",
|
||||
] }
|
||||
opentelemetry-prometheus = { version = "0.29.0", optional = true }
|
||||
opentelemetry-prometheus = { version = "0.29.1", optional = true }
|
||||
libprometheus = { package = "prometheus", version = "0.14.0", optional = true }
|
||||
libopentelemetry = { package = "opentelemetry", version = "0.29.0", features = [
|
||||
libopentelemetry = { package = "opentelemetry", version = "0.30", features = [
|
||||
"metrics",
|
||||
], optional = true }
|
||||
libtempfile = { package = "tempfile", version = "3.2.0", optional = true }
|
||||
@@ -157,7 +163,7 @@ intl-memoizer = { version = "0.5.1", optional = true }
|
||||
ring = { version = "0.17.14", optional = true }
|
||||
reqwest = { workspace = true, features = ["json"], optional = true }
|
||||
rcgen = { version = "0.12.0", optional = true }
|
||||
x509-parser = { version = "0.16.0", optional = true }
|
||||
x509-parser = { version = "0.17.0", optional = true }
|
||||
tokio-metrics = { version = "0.4", optional = true }
|
||||
rust-embed = { version = "8.0", optional = true }
|
||||
hex = { version = "0.4", optional = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
fmt::{Debug, Formatter},
|
||||
io::{Error as IoError, ErrorKind},
|
||||
io::Error as IoError,
|
||||
pin::Pin,
|
||||
task::Poll,
|
||||
};
|
||||
@@ -175,7 +175,7 @@ impl Body {
|
||||
.0
|
||||
.collect()
|
||||
.await
|
||||
.map_err(|err| ReadBodyError::Io(IoError::new(ErrorKind::Other, err)))?
|
||||
.map_err(|err| ReadBodyError::Io(IoError::other(err)))?
|
||||
.to_bytes())
|
||||
}
|
||||
|
||||
|
||||
@@ -92,17 +92,17 @@ impl<E: RustEmbed + Send + Sync> Endpoint for EmbeddedFilesEndpoint<E> {
|
||||
if path.is_empty() && !original_end_with_slash {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::FOUND)
|
||||
.header(LOCATION, format!("{}/", original_path))
|
||||
.header(LOCATION, format!("{original_path}/"))
|
||||
.finish())
|
||||
} else if original_end_with_slash {
|
||||
let path = format!("{}index.html", path);
|
||||
let path = format!("{path}index.html");
|
||||
EmbeddedFileEndpoint::<E>::new(&path).call(req).await
|
||||
} else if E::get(path).is_some() {
|
||||
EmbeddedFileEndpoint::<E>::new(path).call(req).await
|
||||
} else if E::get(&format!("{}/index.html", path)).is_some() {
|
||||
} else if E::get(&format!("{path}/index.html")).is_some() {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::FOUND)
|
||||
.header(LOCATION, format!("{}/", original_path))
|
||||
.header(LOCATION, format!("{original_path}/"))
|
||||
.finish())
|
||||
} else {
|
||||
EmbeddedFileEndpoint::<E>::new(path).call(req).await
|
||||
|
||||
@@ -208,7 +208,7 @@ pub trait DynEndpoint: Send + Sync {
|
||||
type Output: IntoResponse;
|
||||
|
||||
/// Get the response to the request.
|
||||
fn call(&self, req: Request) -> BoxFuture<Result<Self::Output>>;
|
||||
fn call(&self, req: Request) -> BoxFuture<'_, Result<Self::Output>>;
|
||||
}
|
||||
|
||||
/// A [`Endpoint`] wrapper used to implement [`DynEndpoint`].
|
||||
@@ -221,7 +221,7 @@ where
|
||||
type Output = E::Output;
|
||||
|
||||
#[inline]
|
||||
fn call(&self, req: Request) -> BoxFuture<Result<Self::Output>> {
|
||||
fn call(&self, req: Request) -> BoxFuture<'_, Result<Self::Output>> {
|
||||
self.0.call(req).boxed()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ pub trait ResponseError {
|
||||
/// The status code of this error.
|
||||
fn status(&self) -> StatusCode;
|
||||
|
||||
/// Convert this error to a HTTP response.
|
||||
/// Converts this error to an HTTP response.
|
||||
fn as_response(&self) -> Response
|
||||
where
|
||||
Self: StdError + Send + Sync + 'static,
|
||||
@@ -103,7 +103,7 @@ impl AsResponse {
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Create you own error type
|
||||
/// # Create your own error type
|
||||
///
|
||||
/// ```
|
||||
/// use poem::{Endpoint, Request, Result, error::ResponseError, handler, http::StatusCode};
|
||||
|
||||
@@ -222,7 +222,10 @@ impl I18NResources {
|
||||
pub struct I18NBundle(SmallVec<[Arc<FluentBundle>; 8]>);
|
||||
|
||||
impl I18NBundle {
|
||||
fn message(&self, id: impl AsRef<str>) -> Result<(&FluentBundle, FluentMessage), I18NError> {
|
||||
fn message(
|
||||
&self,
|
||||
id: impl AsRef<str>,
|
||||
) -> Result<(&'_ FluentBundle, FluentMessage<'_>), I18NError> {
|
||||
let id = id.as_ref();
|
||||
for bundle in &self.0 {
|
||||
if let Some(message) = bundle.get_message(id) {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
//! # Endpoint
|
||||
//!
|
||||
//! The [`Endpoint`] trait represents a type that can handle HTTP requests, and
|
||||
//! it returns the `Result<T: IntoResponse, Error>` type.
|
||||
//! it returns a `Result<T: IntoResponse, Error>` type.
|
||||
//!
|
||||
//! The [`handler`] macro is used to convert a function into an endpoint.
|
||||
//!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
io::{Error as IoError, ErrorKind, Result as IoResult},
|
||||
io::{Error as IoError, Result as IoResult},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
@@ -77,14 +77,12 @@ impl AutoCertBuilder {
|
||||
|
||||
/// Consumes this builder and returns a [`AutoCert`] object.
|
||||
pub fn build(self) -> IoResult<AutoCert> {
|
||||
let directory_url = self.directory_url.parse().map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("invalid directory url: {err}"))
|
||||
})?;
|
||||
let directory_url = self
|
||||
.directory_url
|
||||
.parse()
|
||||
.map_err(|err| IoError::other(format!("invalid directory url: {err}")))?;
|
||||
if self.domains.is_empty() {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
"at least one domain name is expected",
|
||||
));
|
||||
return Err(IoError::other("at least one domain name is expected"));
|
||||
}
|
||||
|
||||
let mut cache_key = None;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Error as IoError, ErrorKind, Result as IoResult},
|
||||
io::{Error as IoError, Result as IoResult},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
@@ -172,12 +172,7 @@ impl AcmeClient {
|
||||
Ok(resp
|
||||
.bytes()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed to download certificate: {err}"),
|
||||
)
|
||||
})?
|
||||
.map_err(|err| IoError::other(format!("failed to download certificate: {err}")))?
|
||||
.to_vec())
|
||||
}
|
||||
}
|
||||
@@ -185,20 +180,23 @@ impl AcmeClient {
|
||||
async fn get_directory(client: &Client, directory_url: &str) -> IoResult<Directory> {
|
||||
tracing::debug!("loading directory");
|
||||
|
||||
let resp = client.get(directory_url).send().await.map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to load directory: {err}"))
|
||||
})?;
|
||||
let resp = client
|
||||
.get(directory_url)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| IoError::other(format!("failed to load directory: {err}")))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed to load directory: status = {}", resp.status()),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"failed to load directory: status = {}",
|
||||
resp.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let directory = resp.json::<Directory>().await.map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to load directory: {err}"))
|
||||
})?;
|
||||
let directory = resp
|
||||
.json::<Directory>()
|
||||
.await
|
||||
.map_err(|err| IoError::other(format!("failed to load directory: {err}")))?;
|
||||
|
||||
tracing::debug!(
|
||||
new_nonce = ?directory.new_nonce,
|
||||
@@ -216,13 +214,13 @@ async fn get_nonce(client: &Client, directory: &Directory) -> IoResult<String> {
|
||||
.get(&directory.new_nonce)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, format!("failed to get nonce: {err}")))?;
|
||||
.map_err(|err| IoError::other(format!("failed to get nonce: {err}")))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed to load directory: status = {}", resp.status()),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"failed to load directory: status = {}",
|
||||
resp.status()
|
||||
)));
|
||||
}
|
||||
|
||||
let nonce = resp
|
||||
@@ -263,7 +261,7 @@ async fn create_acme_account(
|
||||
.get("location")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.map(ToString::to_string)
|
||||
.ok_or_else(|| IoError::new(ErrorKind::Other, "unable to get account id"))?;
|
||||
.ok_or_else(|| IoError::other("unable to get account id"))?;
|
||||
|
||||
tracing::debug!(kid = kid.as_str(), "account created");
|
||||
Ok(kid)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::io::{Error as IoError, ErrorKind, Result as IoResult};
|
||||
use std::io::{Error as IoError, Result as IoResult};
|
||||
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
||||
use reqwest::{Client, Response};
|
||||
@@ -33,13 +33,11 @@ impl<'a> Protected<'a> {
|
||||
url,
|
||||
};
|
||||
#[cfg(not(feature = "sonic-rs"))]
|
||||
let protected = serde_json::to_vec(&protected).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
|
||||
})?;
|
||||
let protected = serde_json::to_vec(&protected)
|
||||
.map_err(|err| IoError::other(format!("failed to encode jwt: {err}")))?;
|
||||
#[cfg(feature = "sonic-rs")]
|
||||
let protected = sonic_rs::to_vec(&protected).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
|
||||
})?;
|
||||
let protected = sonic_rs::to_vec(&protected)
|
||||
.map_err(|err| IoError::other(format!("failed to encode jwt: {err}")))?;
|
||||
Ok(URL_SAFE_NO_PAD.encode(protected))
|
||||
}
|
||||
}
|
||||
@@ -84,13 +82,11 @@ impl Jwk {
|
||||
y: &self.y,
|
||||
};
|
||||
#[cfg(not(feature = "sonic-rs"))]
|
||||
let json = serde_json::to_vec(&jwk_thumb).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
|
||||
})?;
|
||||
let json = serde_json::to_vec(&jwk_thumb)
|
||||
.map_err(|err| IoError::other(format!("failed to encode jwt: {err}")))?;
|
||||
#[cfg(feature = "sonic-rs")]
|
||||
let json = sonic_rs::to_vec(&jwk_thumb).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode jwt: {err}"))
|
||||
})?;
|
||||
let json = sonic_rs::to_vec(&jwk_thumb)
|
||||
.map_err(|err| IoError::other(format!("failed to encode jwt: {err}")))?;
|
||||
let hash = sha256(json);
|
||||
Ok(URL_SAFE_NO_PAD.encode(hash))
|
||||
}
|
||||
@@ -123,13 +119,11 @@ pub(crate) async fn request(
|
||||
let payload = match payload {
|
||||
Some(payload) => {
|
||||
#[cfg(not(feature = "sonic-rs"))]
|
||||
let res = serde_json::to_vec(&payload).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode payload: {err}"))
|
||||
})?;
|
||||
let res = serde_json::to_vec(&payload)
|
||||
.map_err(|err| IoError::other(format!("failed to encode payload: {err}")))?;
|
||||
#[cfg(feature = "sonic-rs")]
|
||||
let res = sonic_rs::to_vec(&payload).map_err(|err| {
|
||||
IoError::new(ErrorKind::Other, format!("failed to encode payload: {err}"))
|
||||
})?;
|
||||
let res = sonic_rs::to_vec(&payload)
|
||||
.map_err(|err| IoError::other(format!("failed to encode payload: {err}")))?;
|
||||
res
|
||||
}
|
||||
None => Vec::new(),
|
||||
@@ -150,18 +144,13 @@ pub(crate) async fn request(
|
||||
})
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed to send http request: {err}"),
|
||||
)
|
||||
})?;
|
||||
.map_err(|err| IoError::other(format!("failed to send http request: {err}")))?;
|
||||
|
||||
if !resp.status().is_success() {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("unexpected status code: status = {}", resp.status()),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"unexpected status code: status = {}",
|
||||
resp.status()
|
||||
)));
|
||||
}
|
||||
Ok(resp)
|
||||
}
|
||||
@@ -183,22 +172,20 @@ where
|
||||
let data = resp
|
||||
.text()
|
||||
.await
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to read response"))?;
|
||||
.map_err(|_| IoError::other("failed to read response"))?;
|
||||
#[cfg(not(feature = "sonic-rs"))]
|
||||
{
|
||||
serde_json::from_str(&data)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, format!("bad response: {err}")))
|
||||
serde_json::from_str(&data).map_err(|err| IoError::other(format!("bad response: {err}")))
|
||||
}
|
||||
#[cfg(feature = "sonic-rs")]
|
||||
{
|
||||
sonic_rs::from_str(&data)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, format!("bad response: {err}")))
|
||||
sonic_rs::from_str(&data).map_err(|err| IoError::other(format!("bad response: {err}")))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn key_authorization(key: &KeyPair, token: &str) -> IoResult<String> {
|
||||
let jwk = Jwk::new(key);
|
||||
let key_authorization = format!("{}.{}", token, jwk.thumb_sha256_base64()?);
|
||||
let key_authorization = format!("{token}.{}", jwk.thumb_sha256_base64()?);
|
||||
Ok(key_authorization)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::io::{Error as IoError, ErrorKind, Result as IoResult};
|
||||
use std::io::{Error as IoError, Result as IoResult};
|
||||
|
||||
use ring::{
|
||||
rand::SystemRandom,
|
||||
@@ -12,14 +12,14 @@ impl KeyPair {
|
||||
let rng = SystemRandom::new();
|
||||
EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, pkcs8.as_ref(), &rng)
|
||||
.map(KeyPair)
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to load key pair"))
|
||||
.map_err(|_| IoError::other("failed to load key pair"))
|
||||
}
|
||||
|
||||
fn generate_pkcs8() -> IoResult<impl AsRef<[u8]>> {
|
||||
let alg = &ECDSA_P256_SHA256_FIXED_SIGNING;
|
||||
let rng = SystemRandom::new();
|
||||
EcdsaKeyPair::generate_pkcs8(alg, &rng)
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to generate acme key pair"))
|
||||
.map_err(|_| IoError::other("failed to generate acme key pair"))
|
||||
}
|
||||
|
||||
pub(crate) fn generate() -> IoResult<Self> {
|
||||
@@ -29,7 +29,7 @@ impl KeyPair {
|
||||
pub(crate) fn sign(&self, message: impl AsRef<[u8]>) -> IoResult<Signature> {
|
||||
self.0
|
||||
.sign(&SystemRandom::new(), message.as_ref())
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to sign message"))
|
||||
.map_err(|_| IoError::other("failed to sign message"))
|
||||
}
|
||||
|
||||
pub(crate) fn public_key(&self) -> &[u8] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
io::{Error as IoError, ErrorKind, Result as IoResult},
|
||||
io::{Error as IoError, Result as IoResult},
|
||||
sync::{Arc, Weak},
|
||||
time::{Duration, UNIX_EPOCH},
|
||||
};
|
||||
@@ -113,11 +113,11 @@ impl<T: Listener> Listener for AutoCertListener<T> {
|
||||
if let Some(cache_cert) = &self.auto_cert.cache_cert {
|
||||
match rustls_pemfile::certs(&mut cache_cert.as_slice())
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, format!("invalid pem: {err}")))
|
||||
.map_err(|err| IoError::other(format!("invalid pem: {err}")))
|
||||
{
|
||||
Ok(c) => certs = Some(c),
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to parse cached tls certificates: {}", err)
|
||||
tracing::warn!("failed to parse cached tls certificates: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -128,7 +128,7 @@ impl<T: Listener> Listener for AutoCertListener<T> {
|
||||
{
|
||||
Ok(k) => key = k.into_iter().next(),
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to parse cached private key: {}", err)
|
||||
tracing::warn!("failed to parse cached private key: {err}")
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -230,14 +230,14 @@ fn gen_acme_cert(domain: &str, acme_hash: &[u8]) -> IoResult<CertifiedKey> {
|
||||
params.alg = &PKCS_ECDSA_P256_SHA256;
|
||||
params.custom_extensions = vec![CustomExtension::new_acme_identifier(acme_hash)];
|
||||
let cert = Certificate::from_params(params)
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to generate acme certificate"))?;
|
||||
.map_err(|_| IoError::other("failed to generate acme certificate"))?;
|
||||
let key = any_ecdsa_type(&PrivateKeyDer::Pkcs8(
|
||||
cert.serialize_private_key_der().into(),
|
||||
))
|
||||
.unwrap();
|
||||
Ok(CertifiedKey::new(
|
||||
vec![CertificateDer::from(cert.serialize_der().map_err(
|
||||
|_| IoError::new(ErrorKind::Other, "failed to serialize acme certificate"),
|
||||
|_| IoError::other("failed to serialize acme certificate"),
|
||||
)?)],
|
||||
key,
|
||||
))
|
||||
@@ -310,17 +310,14 @@ pub async fn issue_cert<T: AsRef<str>>(
|
||||
.trigger_challenge(&resp.identifier.value, challenge_type, &challenge.url)
|
||||
.await?;
|
||||
} else if resp.status == "invalid" {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"unable to authorize `{}`: {}",
|
||||
resp.identifier.value,
|
||||
resp.error
|
||||
.as_ref()
|
||||
.map(|problem| &*problem.detail)
|
||||
.unwrap_or("unknown")
|
||||
),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"unable to authorize `{}`: {}",
|
||||
resp.identifier.value,
|
||||
resp.error
|
||||
.as_ref()
|
||||
.map(|problem| &*problem.detail)
|
||||
.unwrap_or("unknown")
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,10 +330,7 @@ pub async fn issue_cert<T: AsRef<str>>(
|
||||
}
|
||||
|
||||
if !valid {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
"authorization failed too many times",
|
||||
));
|
||||
return Err(IoError::other("authorization failed too many times"));
|
||||
}
|
||||
|
||||
// send csr
|
||||
@@ -348,62 +342,49 @@ pub async fn issue_cert<T: AsRef<str>>(
|
||||
);
|
||||
params.distinguished_name = DistinguishedName::new();
|
||||
params.alg = &PKCS_ECDSA_P256_SHA256;
|
||||
let cert = Certificate::from_params(params).map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed create certificate request: {err}"),
|
||||
)
|
||||
})?;
|
||||
let cert = Certificate::from_params(params)
|
||||
.map_err(|err| IoError::other(format!("failed create certificate request: {err}")))?;
|
||||
let pk = any_ecdsa_type(&PrivateKeyDer::Pkcs8(
|
||||
cert.serialize_private_key_der().into(),
|
||||
))
|
||||
.unwrap();
|
||||
let csr = cert.serialize_request_der().map_err(|err| {
|
||||
IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!("failed to serialize request der {err}"),
|
||||
)
|
||||
})?;
|
||||
let csr = cert
|
||||
.serialize_request_der()
|
||||
.map_err(|err| IoError::other(format!("failed to serialize request der {err}")))?;
|
||||
|
||||
let order_resp = client.send_csr(&order_resp.finalize, &csr).await?;
|
||||
|
||||
if order_resp.status == "invalid" {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"failed to request certificate: {}",
|
||||
order_resp
|
||||
.error
|
||||
.as_ref()
|
||||
.map(|problem| &*problem.detail)
|
||||
.unwrap_or("unknown")
|
||||
),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"failed to request certificate: {}",
|
||||
order_resp
|
||||
.error
|
||||
.as_ref()
|
||||
.map(|problem| &*problem.detail)
|
||||
.unwrap_or("unknown")
|
||||
)));
|
||||
}
|
||||
|
||||
if order_resp.status != "valid" {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
format!(
|
||||
"failed to request certificate: unexpected status `{}`",
|
||||
order_resp.status
|
||||
),
|
||||
));
|
||||
return Err(IoError::other(format!(
|
||||
"failed to request certificate: unexpected status `{}`",
|
||||
order_resp.status
|
||||
)));
|
||||
}
|
||||
|
||||
// download certificate
|
||||
let acme_cert_pem = client
|
||||
.obtain_certificate(order_resp.certificate.as_ref().ok_or_else(|| {
|
||||
IoError::new(
|
||||
ErrorKind::Other,
|
||||
"invalid response: missing `certificate` url",
|
||||
)
|
||||
})?)
|
||||
.obtain_certificate(
|
||||
order_resp
|
||||
.certificate
|
||||
.as_ref()
|
||||
.ok_or_else(|| IoError::other("invalid response: missing `certificate` url"))?,
|
||||
)
|
||||
.await?;
|
||||
let pkey_pem = cert.serialize_private_key_pem();
|
||||
let cert_chain = rustls_pemfile::certs(&mut acme_cert_pem.as_slice())
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, format!("invalid pem: {err}")))?;
|
||||
.map_err(|err| IoError::other(format!("invalid pem: {err}")))?;
|
||||
let cert_key = CertifiedKey::new(cert_chain, pk);
|
||||
|
||||
tracing::debug!("certificate obtained");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{
|
||||
fmt::{self, Display, Formatter},
|
||||
io::{Error as IoError, ErrorKind, Result as IoResult},
|
||||
io::{Error as IoError, Result as IoResult},
|
||||
};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -101,9 +101,7 @@ impl FetchAuthorizationResponse {
|
||||
self.challenges
|
||||
.iter()
|
||||
.find(|c| c.ty == ty.to_string())
|
||||
.ok_or_else(|| {
|
||||
IoError::new(ErrorKind::Other, format!("unable to find `{ty}` challenge"))
|
||||
})
|
||||
.ok_or_else(|| IoError::other(format!("unable to find `{ty}` challenge")))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ pub trait DynAcceptor: Send {
|
||||
/// This function will yield once a new TCP connection is established. When
|
||||
/// established, the corresponding IO stream and the remote peer’s
|
||||
/// address will be returned.
|
||||
fn accept(&mut self) -> BoxFuture<IoResult<(BoxIo, LocalAddr, RemoteAddr, Scheme)>>;
|
||||
fn accept(&mut self) -> BoxFuture<'_, IoResult<(BoxIo, LocalAddr, RemoteAddr, Scheme)>>;
|
||||
}
|
||||
|
||||
/// A [`Acceptor`] wrapper used to implement [`DynAcceptor`].
|
||||
@@ -120,7 +120,7 @@ impl<A: Acceptor> DynAcceptor for ToDynAcceptor<A> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn accept(&mut self) -> BoxFuture<IoResult<(BoxIo, LocalAddr, RemoteAddr, Scheme)>> {
|
||||
fn accept(&mut self) -> BoxFuture<'_, IoResult<(BoxIo, LocalAddr, RemoteAddr, Scheme)>> {
|
||||
async move {
|
||||
let (io, local_addr, remote_addr, scheme) = self.0.accept().await?;
|
||||
let io = BoxIo::new(io);
|
||||
|
||||
@@ -3,7 +3,7 @@ use futures_util::{
|
||||
stream::{BoxStream, Chain, Pending},
|
||||
};
|
||||
use http::uri::Scheme;
|
||||
use tokio::io::{Error as IoError, ErrorKind, Result as IoResult};
|
||||
use tokio::io::{Error as IoError, Result as IoResult};
|
||||
use tokio_native_tls::{TlsStream, native_tls::Identity};
|
||||
|
||||
use crate::{
|
||||
@@ -49,9 +49,9 @@ impl NativeTlsConfig {
|
||||
|
||||
fn create_acceptor(&self) -> IoResult<tokio_native_tls::native_tls::TlsAcceptor> {
|
||||
let identity = Identity::from_pkcs12(&self.pkcs12, &self.password)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
.map_err(|err| IoError::other(err.to_string()))?;
|
||||
tokio_native_tls::native_tls::TlsAcceptor::new(identity)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err.to_string()))
|
||||
.map_err(|err| IoError::other(err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ impl IntoTlsConfigStream<NativeTlsConfig> for NativeTlsConfig {
|
||||
|
||||
fn into_stream(self) -> IoResult<Self::Stream> {
|
||||
let _ = Identity::from_pkcs12(&self.pkcs12, &self.password)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
.map_err(|err| IoError::other(err.to_string()))?;
|
||||
Ok(futures_util::stream::once(futures_util::future::ready(
|
||||
self,
|
||||
)))
|
||||
@@ -170,9 +170,9 @@ where
|
||||
let (stream, local_addr, remote_addr, _) = res?;
|
||||
let tls_acceptor = match &self.current_tls_acceptor {
|
||||
Some(tls_acceptor) => tls_acceptor.clone(),
|
||||
None => return Err(IoError::new(ErrorKind::Other, "no valid tls config.")),
|
||||
None => return Err(IoError::other("no valid tls config.")),
|
||||
};
|
||||
let fut = async move { tls_acceptor.accept(stream).map_err(|err| IoError::new(ErrorKind::Other, err.to_string())).await };
|
||||
let fut = async move { tls_acceptor.accept(stream).map_err(|err| IoError::other(err.to_string())).await };
|
||||
let stream = HandshakeStream::new(fut);
|
||||
return Ok((stream, local_addr, remote_addr, Scheme::HTTPS));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ use openssl::{
|
||||
ssl::{Ssl, SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod, SslRef},
|
||||
x509::X509,
|
||||
};
|
||||
use tokio::io::{Error as IoError, ErrorKind, Result as IoResult};
|
||||
use tokio::io::{Error as IoError, Result as IoResult};
|
||||
use tokio_openssl::SslStream;
|
||||
use tokio_util::either::Either;
|
||||
|
||||
@@ -76,7 +76,7 @@ impl OpensslTlsConfig {
|
||||
builder.set_certificate(
|
||||
certs
|
||||
.next()
|
||||
.ok_or_else(|| IoError::new(ErrorKind::Other, "no leaf certificate"))?
|
||||
.ok_or_else(|| IoError::other("no leaf certificate"))?
|
||||
.as_ref(),
|
||||
)?;
|
||||
certs.try_for_each(|cert| builder.add_extra_chain_cert(cert))?;
|
||||
@@ -215,16 +215,16 @@ where
|
||||
let (stream, local_addr, remote_addr, _) = res?;
|
||||
let tls_acceptor = match &self.current_tls_acceptor {
|
||||
Some(tls_acceptor) => tls_acceptor.clone(),
|
||||
None => return Err(IoError::new(ErrorKind::Other, "no valid tls config.")),
|
||||
None => return Err(IoError::other("no valid tls config.")),
|
||||
};
|
||||
let fut = async move {
|
||||
let ssl = Ssl::new(tls_acceptor.context()).map_err(|err|
|
||||
IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
IoError::other(err.to_string()))?;
|
||||
let mut tls_stream = SslStream::new(ssl, stream).map_err(|err|
|
||||
IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
IoError::other(err.to_string()))?;
|
||||
use std::pin::Pin;
|
||||
Pin::new(&mut tls_stream).accept().await.map_err(|err|
|
||||
IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
IoError::other(err.to_string()))?;
|
||||
Ok(tls_stream) };
|
||||
let stream = HandshakeStream::new(fut);
|
||||
return Ok((stream, local_addr, remote_addr, Scheme::HTTPS));
|
||||
|
||||
@@ -6,7 +6,7 @@ use futures_util::{
|
||||
};
|
||||
use http::uri::Scheme;
|
||||
use rustls_pemfile::Item;
|
||||
use tokio::io::{Error as IoError, ErrorKind, Result as IoResult};
|
||||
use tokio::io::{Error as IoError, Result as IoResult};
|
||||
use tokio_rustls::{
|
||||
rustls::{
|
||||
ConfigBuilder, DEFAULT_VERSIONS, RootCertStore, ServerConfig, WantsVerifier,
|
||||
@@ -71,7 +71,7 @@ impl RustlsCertificate {
|
||||
fn create_certificate_key(&self) -> IoResult<CertifiedKey> {
|
||||
let cert = rustls_pemfile::certs(&mut self.cert.as_slice())
|
||||
.collect::<Result<_, _>>()
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "failed to parse tls certificates"))?;
|
||||
.map_err(|_| IoError::other("failed to parse tls certificates"))?;
|
||||
let mut key_reader = self.key.as_slice();
|
||||
let priv_key = loop {
|
||||
match rustls_pemfile::read_one(&mut key_reader)? {
|
||||
@@ -79,17 +79,14 @@ impl RustlsCertificate {
|
||||
Some(Item::Pkcs8Key(key)) => break key.into(),
|
||||
Some(Item::Sec1Key(key)) => break key.into(),
|
||||
None => {
|
||||
return Err(IoError::new(
|
||||
ErrorKind::Other,
|
||||
"failed to parse tls private keys",
|
||||
));
|
||||
return Err(IoError::other("failed to parse tls private keys"));
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
};
|
||||
|
||||
let key = any_supported_type(&priv_key)
|
||||
.map_err(|_| IoError::new(ErrorKind::Other, "invalid private key"))?;
|
||||
let key =
|
||||
any_supported_type(&priv_key).map_err(|_| IoError::other("invalid private key"))?;
|
||||
|
||||
Ok(CertifiedKey {
|
||||
cert,
|
||||
@@ -282,10 +279,10 @@ fn read_trust_anchor(mut trust_anchor: &[u8]) -> IoResult<RootCertStore> {
|
||||
let mut store = RootCertStore::empty();
|
||||
let ders = rustls_pemfile::certs(&mut trust_anchor);
|
||||
for der in ders {
|
||||
let der = der.map_err(|err| IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
let der = der.map_err(|err| IoError::other(err.to_string()))?;
|
||||
store
|
||||
.add(der)
|
||||
.map_err(|err| IoError::new(ErrorKind::Other, err.to_string()))?;
|
||||
.map_err(|err| IoError::other(err.to_string()))?;
|
||||
}
|
||||
Ok(store)
|
||||
}
|
||||
@@ -405,7 +402,7 @@ where
|
||||
let (stream, local_addr, remote_addr, _) = res?;
|
||||
let tls_acceptor = match &self.current_tls_acceptor {
|
||||
Some(tls_acceptor) => tls_acceptor,
|
||||
None => return Err(IoError::new(ErrorKind::Other, "no valid tls config.")),
|
||||
None => return Err(IoError::other("no valid tls config.")),
|
||||
};
|
||||
|
||||
let stream = HandshakeStream::new(tls_acceptor.accept(stream));
|
||||
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
use http::uri::Scheme;
|
||||
use http_body_util::BodyExt;
|
||||
use hyper::{body::Incoming, rt::Write as _};
|
||||
use hyper_util::rt::TokioIo;
|
||||
use parking_lot::Mutex;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
@@ -75,6 +76,42 @@ pub struct RequestParts {
|
||||
pub(crate) state: RequestState,
|
||||
}
|
||||
|
||||
impl From<(http::request::Parts, LocalAddr, RemoteAddr, Scheme)> for RequestParts {
|
||||
fn from(
|
||||
(parts, local_addr, remote_addr, scheme): (
|
||||
http::request::Parts,
|
||||
LocalAddr,
|
||||
RemoteAddr,
|
||||
Scheme,
|
||||
),
|
||||
) -> Self {
|
||||
let mut parts = parts;
|
||||
let on_upgrade = Mutex::new(
|
||||
parts
|
||||
.extensions
|
||||
.remove::<hyper::upgrade::OnUpgrade>()
|
||||
.map(|fut| OnUpgrade { fut }),
|
||||
);
|
||||
|
||||
Self {
|
||||
method: parts.method,
|
||||
uri: parts.uri.clone(),
|
||||
version: parts.version,
|
||||
headers: parts.headers,
|
||||
extensions: parts.extensions,
|
||||
state: RequestState {
|
||||
local_addr,
|
||||
remote_addr,
|
||||
scheme,
|
||||
original_uri: parts.uri,
|
||||
match_params: Default::default(),
|
||||
#[cfg(feature = "cookie")]
|
||||
cookie_jar: None,
|
||||
on_upgrade,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Debug for RequestParts {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("RequestParts")
|
||||
@@ -489,7 +526,7 @@ impl AsyncRead for Upgraded {
|
||||
cx: &mut Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<std::io::Result<()>> {
|
||||
Pin::new(&mut hyper_util::rt::TokioIo::new(self.project().stream)).poll_read(cx, buf)
|
||||
Pin::new(&mut TokioIo::new(self.project().stream)).poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ impl<T> RadixTree<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn matches(&self, path: &str) -> Option<Matches<T>> {
|
||||
pub(crate) fn matches(&self, path: &str) -> Option<Matches<'_, T>> {
|
||||
if path.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use serde_json::{Map, Value};
|
||||
pub struct TestJson(Value);
|
||||
|
||||
impl TestJson {
|
||||
/// Returns a reference the value.
|
||||
/// Returns a reference to the value.
|
||||
#[inline]
|
||||
pub fn value(&self) -> TestJsonValue<'_> {
|
||||
TestJsonValue(&self.0)
|
||||
@@ -133,17 +133,17 @@ impl<'a> TestJsonValue<'a> {
|
||||
self.array().iter().map(|value| value.string()).collect()
|
||||
}
|
||||
|
||||
/// Asserts that the value is an array and return `TestJsonArray`.
|
||||
/// Asserts that the value is an array and returns `TestJsonArray`.
|
||||
pub fn array(&self) -> TestJsonArray<'a> {
|
||||
TestJsonArray(self.0.as_array().expect("array"))
|
||||
}
|
||||
|
||||
/// Asserts that the value is an object and return `TestJsonArray`.
|
||||
/// Asserts that the value is an object and returns `TestJsonArray`.
|
||||
pub fn object(&self) -> TestJsonObject<'a> {
|
||||
TestJsonObject(self.0.as_object().expect("object"))
|
||||
}
|
||||
|
||||
/// Asserts that the value is an object array and return
|
||||
/// Asserts that the value is an object array and returns
|
||||
/// `Vec<TestJsonObject>`.
|
||||
pub fn object_array(&self) -> Vec<TestJsonObject<'a>> {
|
||||
self.array().iter().map(|value| value.object()).collect()
|
||||
@@ -198,7 +198,7 @@ impl<'a> TestJsonArray<'a> {
|
||||
}
|
||||
|
||||
/// Returns the element at index `idx`, or `None` if the element does not
|
||||
/// exists exists.
|
||||
/// exist.
|
||||
pub fn get_opt(&self, idx: usize) -> Option<TestJsonValue<'a>> {
|
||||
self.0.get(idx).map(TestJsonValue)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ impl<'a> TestJsonArray<'a> {
|
||||
self.0.iter().map(TestJsonValue)
|
||||
}
|
||||
|
||||
/// Asserts the array length is equals to `len`.
|
||||
/// Asserts the array length is equal to `len`.
|
||||
#[track_caller]
|
||||
pub fn assert_len(&self, len: usize) {
|
||||
assert_eq!(self.len(), len);
|
||||
@@ -264,7 +264,7 @@ impl<'a> TestJsonObject<'a> {
|
||||
}
|
||||
|
||||
/// Returns the element corresponding to the `name`, or `None` if the
|
||||
/// element does not exists exists.
|
||||
/// element does not exist.
|
||||
pub fn get_opt(&self, name: impl AsRef<str>) -> Option<TestJsonValue<'a>> {
|
||||
self.0.get(name.as_ref()).map(TestJsonValue)
|
||||
}
|
||||
@@ -274,7 +274,7 @@ impl<'a> TestJsonObject<'a> {
|
||||
self.0.iter().map(|(k, v)| (k, TestJsonValue(v)))
|
||||
}
|
||||
|
||||
/// Asserts the object length is equals to `len`.
|
||||
/// Asserts the object length is equal to `len`.
|
||||
#[track_caller]
|
||||
pub fn assert_len(&self, len: usize) {
|
||||
assert_eq!(self.len(), len);
|
||||
|
||||
@@ -485,7 +485,7 @@ impl CookieJar {
|
||||
|
||||
/// Similar to the `private_with_key` function, but using the key specified
|
||||
/// by the `CookieJarManager::with_key`.
|
||||
pub fn private(&self) -> PrivateCookieJar {
|
||||
pub fn private(&self) -> PrivateCookieJar<'_> {
|
||||
self.private_with_key(
|
||||
self.key
|
||||
.as_ref()
|
||||
@@ -532,7 +532,7 @@ impl CookieJar {
|
||||
|
||||
/// Similar to the `signed_with_key` function, but using the key specified
|
||||
/// by the `CookieJarManager::with_key`.
|
||||
pub fn signed(&self) -> SignedCookieJar {
|
||||
pub fn signed(&self) -> SignedCookieJar<'_> {
|
||||
self.signed_with_key(
|
||||
self.key
|
||||
.as_ref()
|
||||
|
||||
@@ -93,8 +93,7 @@ impl Field {
|
||||
/// Consume this field to return a reader.
|
||||
pub fn into_async_read(self) -> impl AsyncRead + Send {
|
||||
tokio_util::io::StreamReader::new(
|
||||
self.0
|
||||
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string())),
|
||||
self.0.map_err(|err| std::io::Error::other(err.to_string())),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::io::{Error as IoError, ErrorKind};
|
||||
use std::io::Error as IoError;
|
||||
|
||||
use tokio_tungstenite::tungstenite::{handshake::derive_accept_key, protocol::CloseFrame};
|
||||
|
||||
@@ -15,7 +15,7 @@ pub(crate) fn tungstenite_error_to_io_error(
|
||||
use tokio_tungstenite::tungstenite::Error::*;
|
||||
match error {
|
||||
Io(err) => err,
|
||||
_ => IoError::new(ErrorKind::Other, error.to_string()),
|
||||
_ => IoError::other(error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::{
|
||||
web::RequestBody,
|
||||
};
|
||||
|
||||
/// JSON extractor and response.
|
||||
/// YAML extractor and response.
|
||||
///
|
||||
/// To extract the specified type of YAML from the body, `T` must implement
|
||||
/// [`serde::Deserialize`].
|
||||
|
||||
Reference in New Issue
Block a user