Skip to content

Commit 3cd1938

Browse files
crepererumwaynr
authored andcommitted
feat: add Extensions to object store GetOptions
Closes #7155.
1 parent 2c84f24 commit 3cd1938

File tree

2 files changed

+249
-0
lines changed

2 files changed

+249
-0
lines changed

object_store/src/extensions.rs

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
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+
//! Implementation of [`Extensions`].
19+
use std::{
20+
any::{Any, TypeId},
21+
collections::HashMap,
22+
fmt::Debug,
23+
};
24+
25+
/// Holds opaque extensions.
26+
#[derive(Default)]
27+
pub struct Extensions {
28+
inner: HashMap<TypeId, Box<dyn Extension>>,
29+
}
30+
31+
impl Extensions {
32+
/// Create new, empty extensions collection.
33+
pub fn new() -> Self {
34+
Self::default()
35+
}
36+
37+
/// Set extensions by type.
38+
///
39+
/// Returns existing extension if there was one.
40+
pub fn set<T>(&mut self, t: T) -> Option<T>
41+
where
42+
T: Clone + Debug + Send + Sync + 'static,
43+
{
44+
let ext = ExtensionImpl { inner: t };
45+
let existing = self.inner.insert(TypeId::of::<T>(), Box::new(ext));
46+
existing.map(|ext| *ext.to_any().downcast::<T>().expect("type ID is correct"))
47+
}
48+
49+
/// Get immutable reference to extension by type.
50+
pub fn get<T>(&self) -> Option<&T>
51+
where
52+
T: Clone + Debug + Send + Sync + 'static,
53+
{
54+
self.inner.get(&TypeId::of::<T>()).map(|ext| {
55+
ext.as_any()
56+
.downcast_ref::<T>()
57+
.expect("type ID is correct")
58+
})
59+
}
60+
61+
/// Get mutable reference to extension by type.
62+
pub fn get_mut<T>(&mut self) -> Option<&mut T>
63+
where
64+
T: Clone + Debug + Send + Sync + 'static,
65+
{
66+
self.inner.get_mut(&TypeId::of::<T>()).map(|ext| {
67+
ext.as_any_mut()
68+
.downcast_mut::<T>()
69+
.expect("type ID is correct")
70+
})
71+
}
72+
73+
/// Remove extension by type.
74+
pub fn remove<T>(&mut self) -> Option<T>
75+
where
76+
T: Clone + Debug + Send + Sync + 'static,
77+
{
78+
let existing = self.inner.remove(&TypeId::of::<T>());
79+
existing.map(|ext| *ext.to_any().downcast::<T>().expect("type ID is correct"))
80+
}
81+
}
82+
83+
impl Debug for Extensions {
84+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85+
f.debug_set().entries(self.inner.values()).finish()
86+
}
87+
}
88+
89+
impl Clone for Extensions {
90+
fn clone(&self) -> Self {
91+
Self {
92+
inner: self
93+
.inner
94+
.iter()
95+
.map(|(ty_id, ext)| (*ty_id, ext.as_ref().clone()))
96+
.collect(),
97+
}
98+
}
99+
}
100+
101+
/// Helper trait to capture relevant trait bounds for extensions into a vtable.
102+
trait Extension: Debug + Send + Sync + 'static {
103+
/// Dyn-compatible [`Clone`].
104+
fn clone(&self) -> Box<dyn Extension>;
105+
106+
/// Converts to boxed [`Any`].
107+
fn to_any(self: Box<Self>) -> Box<dyn Any>;
108+
109+
/// Converts to [`Any`] reference.
110+
fn as_any(&self) -> &dyn Any;
111+
112+
/// Converts to [`Any`] mutable reference.
113+
fn as_any_mut(&mut self) -> &mut dyn Any;
114+
}
115+
116+
/// Our one-and-only implementation of [`Extension`].
117+
struct ExtensionImpl<T>
118+
where
119+
T: Clone + Debug + Send + Sync + 'static,
120+
{
121+
inner: T,
122+
}
123+
124+
impl<T> Debug for ExtensionImpl<T>
125+
where
126+
T: Clone + Debug + Send + Sync + 'static,
127+
{
128+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129+
self.inner.fmt(f)
130+
}
131+
}
132+
133+
impl<T> Extension for ExtensionImpl<T>
134+
where
135+
T: Clone + Debug + Send + Sync + 'static,
136+
{
137+
fn clone(&self) -> Box<dyn Extension> {
138+
Box::new(Self {
139+
inner: self.inner.clone(),
140+
})
141+
}
142+
143+
fn to_any(self: Box<Self>) -> Box<dyn Any> {
144+
let this = *self;
145+
Box::new(this.inner) as _
146+
}
147+
148+
fn as_any(&self) -> &dyn Any {
149+
&self.inner
150+
}
151+
152+
fn as_any_mut(&mut self) -> &mut dyn Any {
153+
&mut self.inner
154+
}
155+
}
156+
157+
#[cfg(test)]
158+
mod tests {
159+
use super::*;
160+
161+
#[test]
162+
fn test_extensions_traits() {
163+
let ext = Extensions::default();
164+
assert_send(&ext);
165+
assert_sync(&ext);
166+
}
167+
168+
#[test]
169+
fn test_debug() {
170+
let mut ext = Extensions::default();
171+
ext.set(String::from("foo"));
172+
ext.set(1u8);
173+
174+
let dbg_str = format!("{ext:?}");
175+
176+
// order is NOT deterministic
177+
let variant_a = r#"{"foo", 1}"#;
178+
let variant_b = r#"{1, "foo"}"#;
179+
assert!(
180+
(dbg_str == variant_a) || (dbg_str == variant_b),
181+
"'{dbg_str}' is neither '{variant_a}' nor '{variant_b}'",
182+
);
183+
}
184+
185+
#[test]
186+
fn test_get_set_remove() {
187+
let mut ext = Extensions::default();
188+
assert_eq!(ext.get::<String>(), None);
189+
assert_eq!(ext.get::<u8>(), None);
190+
assert_eq!(ext.get_mut::<String>(), None);
191+
assert_eq!(ext.get_mut::<u8>(), None);
192+
193+
assert_eq!(ext.set(String::from("foo")), None);
194+
assert_eq!(ext.get::<String>(), Some(&String::from("foo")));
195+
assert_eq!(ext.get::<u8>(), None);
196+
assert_eq!(ext.get_mut::<String>(), Some(&mut String::from("foo")));
197+
assert_eq!(ext.get_mut::<u8>(), None);
198+
199+
assert_eq!(ext.set(1u8), None);
200+
assert_eq!(ext.get::<String>(), Some(&String::from("foo")));
201+
assert_eq!(ext.get::<u8>(), Some(&1u8));
202+
assert_eq!(ext.get_mut::<String>(), Some(&mut String::from("foo")));
203+
assert_eq!(ext.get_mut::<u8>(), Some(&mut 1u8));
204+
205+
assert_eq!(ext.set(String::from("bar")), Some(String::from("foo")));
206+
assert_eq!(ext.get::<String>(), Some(&String::from("bar")));
207+
assert_eq!(ext.get::<u8>(), Some(&1u8));
208+
assert_eq!(ext.get_mut::<String>(), Some(&mut String::from("bar")));
209+
assert_eq!(ext.get_mut::<u8>(), Some(&mut 1u8));
210+
211+
ext.get_mut::<String>().unwrap().push_str("baz");
212+
assert_eq!(ext.get::<String>(), Some(&String::from("barbaz")));
213+
assert_eq!(ext.get::<u8>(), Some(&1u8));
214+
assert_eq!(ext.get_mut::<String>(), Some(&mut String::from("barbaz")));
215+
assert_eq!(ext.get_mut::<u8>(), Some(&mut 1u8));
216+
217+
assert_eq!(ext.remove::<String>(), Some(String::from("barbaz")));
218+
assert_eq!(ext.get::<String>(), None);
219+
assert_eq!(ext.get::<u8>(), Some(&1u8));
220+
assert_eq!(ext.get_mut::<String>(), None);
221+
assert_eq!(ext.get_mut::<u8>(), Some(&mut 1u8));
222+
assert_eq!(ext.remove::<String>(), None);
223+
}
224+
225+
#[test]
226+
fn test_clone() {
227+
let mut ext = Extensions::default();
228+
ext.set(String::from("foo"));
229+
230+
let ext2 = ext.clone();
231+
232+
ext.get_mut::<String>().unwrap().push_str("bar");
233+
ext.set(1u8);
234+
235+
assert_eq!(ext.get::<String>(), Some(&String::from("foobar")));
236+
assert_eq!(ext.get::<u8>(), Some(&1));
237+
assert_eq!(ext2.get::<String>(), Some(&String::from("foo")));
238+
assert_eq!(ext2.get::<u8>(), None);
239+
}
240+
241+
fn assert_send<T: Send>(_o: &T) {}
242+
fn assert_sync<T: Sync>(_o: &T) {}
243+
}

object_store/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ pub mod buffered;
508508
#[cfg(not(target_arch = "wasm32"))]
509509
pub mod chunked;
510510
pub mod delimited;
511+
pub mod extensions;
511512
#[cfg(feature = "gcp")]
512513
pub mod gcp;
513514
#[cfg(feature = "http")]
@@ -963,6 +964,11 @@ pub struct GetOptions {
963964
///
964965
/// <https://datatracker.ietf.org/doc/html/rfc9110#name-head>
965966
pub head: bool,
967+
/// Implementation-specific extensions. Intended for use by [`ObjectStore`] implementations
968+
/// that need to pass context-specific information (like tracing spans) via trait methods.
969+
///
970+
/// These extensions are ignored entirely by backends offered through this crate.
971+
pub extensions: extensions::Extensions,
966972
}
967973

968974
impl GetOptions {

0 commit comments

Comments
 (0)