Skip to main content

reqsign_core/
lib.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18//! Core components for signing API requests.
19//!
20//! This crate provides the foundational types and traits for the reqsign ecosystem.
21//! It defines the core abstractions that enable flexible and extensible request signing.
22//!
23//! ## Overview
24//!
25//! The crate is built around several key concepts:
26//!
27//! - **Context**: A container that holds implementations for file reading, HTTP sending, and environment access
28//! - **Traits**: Abstract interfaces for credential loading (`ProvideCredential`) and request signing (`SignRequest`)
29//! - **Signer**: The main orchestrator that coordinates credential loading and request signing
30//!
31//! ## Request URI contract
32//!
33//! Built-in request signers expect the request URI to be a valid, wire-ready URI
34//! with an authority. Callers must construct the intended path and query structure
35//! and percent-encode data components exactly once before signing. Structural URI
36//! delimiters remain literal, while delimiter bytes that belong to data must already
37//! be encoded, such as `%2F` for a slash inside one path segment.
38//!
39//! Existing path and query representations are authoritative. Canonicalization is a
40//! service-specific, read-only view: header authentication preserves the URI, while
41//! query authentication appends protocol-encoded authentication fields without
42//! decoding, sorting, or rebuilding the existing URI.
43//!
44//! [`Signer::sign`] runs the service signer against a private candidate request head.
45//! On error, the caller's method, URI, version, headers, and extensions remain
46//! unchanged. On success, only the URI and headers are committed; the caller retains
47//! ownership of the method, version, and extensions.
48//!
49//! `expires_in` is a service-specific validity input, not a universal selector between
50//! header and query authentication. The service and credential type determine the
51//! authentication mode.
52//!
53//! [`SigningCredential::is_valid`] controls whether a cached credential can be reused
54//! without refresh. [`SigningCredential::is_valid_at`] checks exact usability at the
55//! timestamp returned by [`SignRequest::required_valid_until`]. A refreshed credential
56//! only needs to satisfy the exact operation requirement. Credential refresh is
57//! serialized per shared cache, so concurrent cold or stale callers reuse a successful
58//! refresh. Refresh failures are not cached, so the next waiting or later caller can
59//! retry. Provider errors are returned without retrying internally or falling back to
60//! the old cached credential.
61//!
62//! ## Example
63//!
64//! ```no_run
65//! use reqsign_core::{Context, OsEnv, ProvideCredential, Result, SignRequest, Signer, SigningCredential};
66//! use http::request::Parts;
67//! use std::time::Duration;
68//!
69//! // Define your credential type
70//! #[derive(Clone, Debug)]
71//! struct MyCredential {
72//!     key: String,
73//!     secret: String,
74//! }
75//!
76//! impl SigningCredential for MyCredential {
77//!     fn is_valid(&self) -> bool {
78//!         !self.key.is_empty() && !self.secret.is_empty()
79//!     }
80//! }
81//!
82//! // Implement credential loader
83//! #[derive(Debug)]
84//! struct MyLoader;
85//!
86//! impl ProvideCredential for MyLoader {
87//!     type Credential = MyCredential;
88//!
89//!     async fn provide_credential(&self, _: &Context) -> Result<Option<Self::Credential>> {
90//!         Ok(Some(MyCredential {
91//!             key: "my-access-key".to_string(),
92//!             secret: "my-secret-key".to_string(),
93//!         }))
94//!     }
95//! }
96//!
97//! // Implement request builder
98//! #[derive(Debug)]
99//! struct MyBuilder;
100//!
101//! impl SignRequest for MyBuilder {
102//!     type Credential = MyCredential;
103//!
104//!     async fn sign_request(
105//!         &self,
106//!         _ctx: &Context,
107//!         req: &mut Parts,
108//!         _cred: Option<&Self::Credential>,
109//!         _expires_in: Option<Duration>,
110//!     ) -> Result<()> {
111//!         // Add example header
112//!         req.headers.insert("x-custom-auth", "signed".parse()?);
113//!         Ok(())
114//!     }
115//! }
116//!
117//! # async fn example() -> Result<()> {
118//! # use reqsign_core::{FileRead, HttpSend};
119//! # use bytes::Bytes;
120//! #
121//! # // Mock implementations for the example
122//! # #[derive(Debug, Clone)]
123//! # struct MockFileRead;
124//! # impl FileRead for MockFileRead {
125//! #     async fn file_read(&self, _path: &str) -> Result<Vec<u8>> {
126//! #         Ok(vec![])
127//! #     }
128//! # }
129//! #
130//! # #[derive(Debug, Clone)]
131//! # struct MockHttpSend;
132//! # impl HttpSend for MockHttpSend {
133//! #     async fn http_send(&self, _req: http::Request<Bytes>) -> Result<http::Response<Bytes>> {
134//! #         Ok(http::Response::builder().status(200).body(Bytes::new())?)
135//! #     }
136//! # }
137//! #
138//! // Create a context with your implementations
139//! let ctx = Context::new()
140//!     .with_file_read(MockFileRead)
141//!     .with_http_send(MockHttpSend)
142//!     .with_env(OsEnv);
143//!
144//! // Create a signer
145//! let signer = Signer::new(ctx, MyLoader, MyBuilder);
146//!
147//! // Sign your requests
148//! let mut parts = http::Request::builder()
149//!     .method("GET")
150//!     .uri("https://example.com")
151//!     .body(())
152//!     .unwrap()
153//!     .into_parts()
154//!     .0;
155//!
156//! signer.sign(&mut parts, None).await?;
157//! # Ok(())
158//! # }
159//! ```
160//!
161//! ## Traits
162//!
163//! This crate defines several important traits:
164//!
165//! - [`FileRead`]: For asynchronous file reading
166//! - [`HttpSend`]: For sending HTTP requests
167//! - [`Env`]: For environment variable access
168//! - [`ProvideCredential`]: For loading credentials from various sources
169//! - [`SignRequest`]: For building service-specific signing requests
170//! - [`SigningCredential`]: For validating credentials
171//!
172//! ## Utilities
173//!
174//! The crate also provides utility modules:
175//!
176//! - [`hash`]: Cryptographic hashing utilities
177//! - [`time`]: Time manipulation utilities
178//! - [`utils`]: General utilities including data redaction
179
180// Make sure all our public APIs have docs.
181#![warn(missing_docs)]
182
183/// Error types for reqsign operations
184pub mod error;
185mod futures_util;
186pub mod hash;
187#[cfg(all(not(target_arch = "wasm32"), feature = "jwt"))]
188pub mod jwt;
189pub mod time;
190pub mod utils;
191
192pub use error::{Error, ErrorKind, Result};
193pub use futures_util::BoxedFuture;
194pub use futures_util::MaybeSend;
195
196mod context;
197pub use context::CommandExecute;
198pub use context::CommandExecuteDyn;
199pub use context::CommandOutput;
200pub use context::Context;
201pub use context::Env;
202pub use context::FileRead;
203pub use context::FileReadDyn;
204pub use context::HttpSend;
205pub use context::HttpSendDyn;
206pub use context::NoopCommandExecute;
207pub use context::NoopEnv;
208pub use context::NoopFileRead;
209pub use context::NoopHttpSend;
210pub use context::OsEnv;
211pub use context::StaticEnv;
212
213mod api;
214pub use api::GrantCredential;
215pub use api::GrantCredentialDyn;
216pub use api::ProvideCredential;
217pub use api::ProvideCredentialChain;
218pub use api::ProvideCredentialDyn;
219pub use api::SignRequest;
220pub use api::SignRequestDyn;
221pub use api::SigningCredential;
222mod request;
223pub use request::{SigningMethod, SigningRequest};
224mod signer;
225pub use signer::Signer;
226mod granter;
227pub use granter::Granter;