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; provider errors are returned
57//! without retrying internally or falling back to the old cached credential.
58//!
59//! ## Example
60//!
61//! ```no_run
62//! use reqsign_core::{Context, OsEnv, ProvideCredential, Result, SignRequest, Signer, SigningCredential};
63//! use http::request::Parts;
64//! use std::time::Duration;
65//!
66//! // Define your credential type
67//! #[derive(Clone, Debug)]
68//! struct MyCredential {
69//!     key: String,
70//!     secret: String,
71//! }
72//!
73//! impl SigningCredential for MyCredential {
74//!     fn is_valid(&self) -> bool {
75//!         !self.key.is_empty() && !self.secret.is_empty()
76//!     }
77//! }
78//!
79//! // Implement credential loader
80//! #[derive(Debug)]
81//! struct MyLoader;
82//!
83//! impl ProvideCredential for MyLoader {
84//!     type Credential = MyCredential;
85//!
86//!     async fn provide_credential(&self, _: &Context) -> Result<Option<Self::Credential>> {
87//!         Ok(Some(MyCredential {
88//!             key: "my-access-key".to_string(),
89//!             secret: "my-secret-key".to_string(),
90//!         }))
91//!     }
92//! }
93//!
94//! // Implement request builder
95//! #[derive(Debug)]
96//! struct MyBuilder;
97//!
98//! impl SignRequest for MyBuilder {
99//!     type Credential = MyCredential;
100//!
101//!     async fn sign_request(
102//!         &self,
103//!         _ctx: &Context,
104//!         req: &mut Parts,
105//!         _cred: Option<&Self::Credential>,
106//!         _expires_in: Option<Duration>,
107//!     ) -> Result<()> {
108//!         // Add example header
109//!         req.headers.insert("x-custom-auth", "signed".parse()?);
110//!         Ok(())
111//!     }
112//! }
113//!
114//! # async fn example() -> Result<()> {
115//! # use reqsign_core::{FileRead, HttpSend};
116//! # use bytes::Bytes;
117//! #
118//! # // Mock implementations for the example
119//! # #[derive(Debug, Clone)]
120//! # struct MockFileRead;
121//! # impl FileRead for MockFileRead {
122//! #     async fn file_read(&self, _path: &str) -> Result<Vec<u8>> {
123//! #         Ok(vec![])
124//! #     }
125//! # }
126//! #
127//! # #[derive(Debug, Clone)]
128//! # struct MockHttpSend;
129//! # impl HttpSend for MockHttpSend {
130//! #     async fn http_send(&self, _req: http::Request<Bytes>) -> Result<http::Response<Bytes>> {
131//! #         Ok(http::Response::builder().status(200).body(Bytes::new())?)
132//! #     }
133//! # }
134//! #
135//! // Create a context with your implementations
136//! let ctx = Context::new()
137//!     .with_file_read(MockFileRead)
138//!     .with_http_send(MockHttpSend)
139//!     .with_env(OsEnv);
140//!
141//! // Create a signer
142//! let signer = Signer::new(ctx, MyLoader, MyBuilder);
143//!
144//! // Sign your requests
145//! let mut parts = http::Request::builder()
146//!     .method("GET")
147//!     .uri("https://example.com")
148//!     .body(())
149//!     .unwrap()
150//!     .into_parts()
151//!     .0;
152//!
153//! signer.sign(&mut parts, None).await?;
154//! # Ok(())
155//! # }
156//! ```
157//!
158//! ## Traits
159//!
160//! This crate defines several important traits:
161//!
162//! - [`FileRead`]: For asynchronous file reading
163//! - [`HttpSend`]: For sending HTTP requests
164//! - [`Env`]: For environment variable access
165//! - [`ProvideCredential`]: For loading credentials from various sources
166//! - [`SignRequest`]: For building service-specific signing requests
167//! - [`SigningCredential`]: For validating credentials
168//!
169//! ## Utilities
170//!
171//! The crate also provides utility modules:
172//!
173//! - [`hash`]: Cryptographic hashing utilities
174//! - [`time`]: Time manipulation utilities
175//! - [`utils`]: General utilities including data redaction
176
177// Make sure all our public APIs have docs.
178#![warn(missing_docs)]
179
180/// Error types for reqsign operations
181pub mod error;
182mod futures_util;
183pub mod hash;
184#[cfg(all(not(target_arch = "wasm32"), feature = "jwt"))]
185pub mod jwt;
186pub mod time;
187pub mod utils;
188
189pub use error::{Error, ErrorKind, Result};
190pub use futures_util::BoxedFuture;
191pub use futures_util::MaybeSend;
192
193mod context;
194pub use context::CommandExecute;
195pub use context::CommandExecuteDyn;
196pub use context::CommandOutput;
197pub use context::Context;
198pub use context::Env;
199pub use context::FileRead;
200pub use context::FileReadDyn;
201pub use context::HttpSend;
202pub use context::HttpSendDyn;
203pub use context::NoopCommandExecute;
204pub use context::NoopEnv;
205pub use context::NoopFileRead;
206pub use context::NoopHttpSend;
207pub use context::OsEnv;
208pub use context::StaticEnv;
209
210mod api;
211pub use api::ProvideCredential;
212pub use api::ProvideCredentialChain;
213pub use api::ProvideCredentialDyn;
214pub use api::SignRequest;
215pub use api::SignRequestDyn;
216pub use api::SigningCredential;
217mod request;
218pub use request::{SigningMethod, SigningRequest};
219mod signer;
220pub use signer::Signer;