replace Option CRDT by explicit CancelingOption and MergingOption types

This commit is contained in:
Alex Auvolat
2026-05-04 19:51:35 +02:00
parent eb37a3e11a
commit bf0a24ea69
20 changed files with 237 additions and 133 deletions
+4 -4
View File
@@ -244,8 +244,8 @@ fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInf
.expect("invalid timestamp stored in db"),
),
name: params.name.get().to_string(),
expiration: params.expiration.get().map(|x| {
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
expiration: params.expiration.get().inner().map(|x| {
DateTime::from_timestamp_millis(*x as i64).expect("invalid timestamp stored in db")
}),
expired: params.is_expired(now),
scope: params.scope.get().0.clone(),
@@ -279,10 +279,10 @@ fn apply_token_updates(
if let Some(expiration) = updates.expiration {
params
.expiration
.update(Some(expiration.timestamp_millis() as u64));
.update(Some(expiration.timestamp_millis() as u64).into());
}
if updates.never_expires {
params.expiration.update(None);
params.expiration.update(None.into());
}
if let Some(scope) = updates.scope {
params.scope.update(AdminApiTokenScope(scope));
+23 -20
View File
@@ -90,7 +90,7 @@ impl RequestHandler for GetBucketInfoRequest {
.bucket_alias_table
.get(&EmptyKey, &ga)
.await?
.and_then(|x| *x.state.get())
.and_then(|x| x.state.get().into_inner())
.ok_or_else(|| HelperError::NoSuchBucket(ga.to_string()))?,
(None, None, Some(search)) => {
let helper = garage.bucket_helper();
@@ -168,7 +168,7 @@ impl RequestHandler for CreateBucketRequest {
}
if let Some(alias) = garage.bucket_alias_table.get(&EmptyKey, ga).await? {
if alias.state.get().is_some() {
if alias.state.get().inner().is_some() {
return Err(CommonError::BucketAlreadyExists.into());
}
}
@@ -297,7 +297,7 @@ impl RequestHandler for UpdateBucketRequest {
let redirect_all = state
.website_config
.get()
.as_ref()
.inner()
.and_then(|wc| wc.redirect_all.clone());
let routing_rules = if let Some(rr) = wa.routing_rules {
@@ -311,26 +311,29 @@ impl RequestHandler for UpdateBucketRequest {
state
.website_config
.get()
.as_ref()
.inner()
.map(|wc| wc.routing_rules.clone())
.unwrap_or_default()
};
state.website_config.update(Some(WebsiteConfig {
index_document: wa.index_document.ok_or_bad_request(
"Please specify indexDocument when enabling website access.",
)?,
error_document: wa.error_document,
redirect_all,
routing_rules,
}));
state.website_config.update(
Some(WebsiteConfig {
index_document: wa.index_document.ok_or_bad_request(
"Please specify indexDocument when enabling website access.",
)?,
error_document: wa.error_document,
redirect_all,
routing_rules,
})
.into(),
);
} else {
if wa.index_document.is_some() || wa.error_document.is_some() {
return Err(Error::bad_request(
"Cannot specify indexDocument or errorDocument when disabling website access.",
));
}
state.website_config.update(None);
state.website_config.update(None.into());
}
}
@@ -353,7 +356,7 @@ impl RequestHandler for UpdateBucketRequest {
Some(cc.into_garage_cors_config()?)
};
state.cors_config.update(cors_config);
state.cors_config.update(cors_config.into());
}
if let Some(lr) = self.body.lifecycle_rules {
@@ -370,7 +373,7 @@ impl RequestHandler for UpdateBucketRequest {
)
};
state.lifecycle_config.update(lifecycle_config);
state.lifecycle_config.update(lifecycle_config.into());
}
garage.bucket_table.insert(&bucket).await?;
@@ -739,8 +742,8 @@ async fn bucket_info_results(
.filter(|(_, _, a)| *a)
.map(|(n, _, _)| n.to_string())
.collect::<Vec<_>>(),
website_access: state.website_config.get().is_some(),
website_config: state.website_config.get().clone().map(|wsc| {
website_access: state.website_config.get().inner().is_some(),
website_config: state.website_config.get().inner().cloned().map(|wsc| {
GetBucketInfoWebsiteResponse {
index_document: wsc.index_document,
error_document: wsc.error_document,
@@ -752,13 +755,13 @@ async fn bucket_info_results(
),
}
}),
cors_rules: state.cors_config.get().as_ref().map(|rules| {
cors_rules: state.cors_config.get().inner().map(|rules| {
rules
.iter()
.map(xml::cors::CorsRule::from_garage_cors_rule)
.collect::<Vec<_>>()
}),
lifecycle_rules: state.lifecycle_config.get().as_ref().map(|lc| {
lifecycle_rules: state.lifecycle_config.get().inner().map(|lc| {
lc.iter()
.map(xml::lifecycle::LifecycleRule::from_garage_lifecycle_rule)
.collect::<Vec<_>>()
@@ -784,7 +787,7 @@ async fn bucket_info_results(
.local_aliases
.items()
.iter()
.filter(|(_, _, b)| *b == Some(bucket.id))
.filter(|(_, _, b)| b.into_inner() == Some(bucket.id))
.map(|(n, _, _)| n.to_string())
.collect::<Vec<_>>(),
})
+7 -7
View File
@@ -40,8 +40,8 @@ impl RequestHandler for ListKeysRequest {
DateTime::from_timestamp_millis(x as i64)
.expect("invalid timestamp stored in db")
}),
expiration: p.expiration.get().map(|x| {
DateTime::from_timestamp_millis(x as i64)
expiration: p.expiration.get().inner().map(|x| {
DateTime::from_timestamp_millis(*x as i64)
.expect("invalid timestamp stored in db")
}),
expired: p.is_expired(now),
@@ -201,7 +201,7 @@ async fn key_info_results(
.local_aliases
.items()
.iter()
.filter_map(|(_, _, v)| v.as_ref()),
.filter_map(|(_, _, v)| v.inner()),
) {
if !relevant_buckets.contains_key(id) {
if let Some(b) = garage.bucket_table.get(&EmptyKey, id).await? {
@@ -217,8 +217,8 @@ async fn key_info_results(
created: key_state.created.map(|x| {
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
}),
expiration: key_state.expiration.get().map(|x| {
DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
expiration: key_state.expiration.get().inner().map(|x| {
DateTime::from_timestamp_millis(*x as i64).expect("invalid timestamp stored in db")
}),
expired: key_state.is_expired(now_msec()),
access_key_id: key.key_id.clone(),
@@ -283,10 +283,10 @@ fn apply_key_updates(key: &mut Key, updates: UpdateKeyRequestBody) -> Result<(),
if let Some(expiration) = updates.expiration {
key_state
.expiration
.update(Some(expiration.timestamp_millis() as u64));
.update(Some(expiration.timestamp_millis() as u64).into());
}
if updates.never_expires {
key_state.expiration.update(None);
key_state.expiration.update(None.into());
}
if let Some(allow) = updates.allow {
if allow.create_bucket {
+1 -1
View File
@@ -164,7 +164,7 @@ async fn check_domain(garage: &Arc<Garage>, domain: &str) -> Result<bool, Error>
}
let bucket_state = bucket.state.as_option().unwrap();
let bucket_website_config = bucket_state.website_config.get();
let bucket_website_config = bucket_state.website_config.get().inner();
match bucket_website_config {
Some(_v) => Ok(true),
+13 -10
View File
@@ -19,7 +19,7 @@ pub fn find_matching_cors_rule<'a, B>(
bucket_params: &'a BucketParams,
req: &'a Request<B>,
) -> Result<Option<(&'a GarageCorsRule, &'a str)>, CommonError> {
if let Some(cors_config) = bucket_params.cors_config.get() {
if let Some(cors_config) = bucket_params.cors_config.get().inner() {
if let Some(origin) = req.headers().get("Origin") {
let origin = origin.to_str()?;
let request_headers = match req.headers().get(ACCESS_CONTROL_REQUEST_HEADERS) {
@@ -158,7 +158,7 @@ pub fn handle_options_for_bucket<B>(
None => vec![],
};
if let Some(cors_config) = bucket_params.cors_config.get() {
if let Some(cors_config) = bucket_params.cors_config.get().inner() {
let matching_rule = cors_config
.iter()
.find(|rule| cors_rule_matches(rule, origin, request_method, request_headers.iter()));
@@ -192,14 +192,17 @@ mod tests {
fn bucket_params_with_rule(allow_origins: Vec<&str>) -> BucketParams {
let mut bucket_params = BucketParams::default();
bucket_params.cors_config.update(Some(vec![GarageCorsRule {
id: Some("cors-test".into()),
max_age_seconds: None,
allow_origins: allow_origins.into_iter().map(str::to_string).collect(),
allow_methods: vec!["GET".into(), "PUT".into()],
allow_headers: vec!["*".into()],
expose_headers: vec![],
}]));
bucket_params.cors_config.update(
Some(vec![GarageCorsRule {
id: Some("cors-test".into()),
max_age_seconds: None,
allow_origins: allow_origins.into_iter().map(str::to_string).collect(),
allow_methods: vec!["GET".into(), "PUT".into()],
allow_headers: vec!["*".into()],
expose_headers: vec![],
}])
.into(),
);
bucket_params
}
+6 -3
View File
@@ -122,7 +122,7 @@ pub async fn handle_list_buckets(
for (alias, _, _active) in bucket.aliases().iter().filter(|(_, _, active)| *active) {
let alias_opt = garage.bucket_alias_table.get(&EmptyKey, alias).await?;
if let Some(alias_ent) = alias_opt {
if *alias_ent.state.get() == Some(*bucket_id) {
if alias_ent.state.get().inner() == Some(bucket_id) {
aliases.insert(alias_ent.name().to_string(), *bucket_id);
}
}
@@ -134,7 +134,7 @@ pub async fn handle_list_buckets(
}
for (alias, _, id_opt) in key_p.local_aliases.items() {
if let Some(id) = id_opt {
if let Some(id) = id_opt.inner() {
aliases.insert(alias.clone(), *id);
}
}
@@ -256,7 +256,10 @@ pub async fn handle_delete_bucket(ctx: ReqCtx) -> Result<Response<ResBody>, Erro
let key_params = api_key.params().unwrap();
let is_local_alias = matches!(key_params.local_aliases.get(bucket_name), Some(Some(_)));
let is_local_alias = matches!(
key_params.local_aliases.get(bucket_name).map(|x| x.inner()),
Some(Some(_))
);
// If the bucket has no other aliases, this is a true deletion.
// Otherwise, it is just an alias removal.
+3 -3
View File
@@ -13,7 +13,7 @@ use crate::xml::to_xml_with_header;
pub async fn handle_get_cors(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
let ReqCtx { bucket_params, .. } = ctx;
if let Some(cors) = bucket_params.cors_config.get() {
if let Some(cors) = bucket_params.cors_config.get().inner() {
let wc = CorsConfiguration {
xmlns: (),
cors_rules: cors
@@ -38,7 +38,7 @@ pub async fn handle_delete_cors(ctx: ReqCtx) -> Result<Response<ResBody>, Error>
mut bucket_params,
..
} = ctx;
bucket_params.cors_config.update(None);
bucket_params.cors_config.update(None.into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))
@@ -67,7 +67,7 @@ pub async fn handle_put_cors(
bucket_params
.cors_config
.update(Some(conf.into_garage_cors_config()?));
.update(Some(conf.into_garage_cors_config()?).into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))
+3 -3
View File
@@ -14,7 +14,7 @@ use garage_model::bucket_table::Bucket;
pub async fn handle_get_lifecycle(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
let ReqCtx { bucket_params, .. } = ctx;
if let Some(lifecycle) = bucket_params.lifecycle_config.get() {
if let Some(lifecycle) = bucket_params.lifecycle_config.get().inner() {
let wc = LifecycleConfiguration::from_garage_lifecycle_config(lifecycle);
let xml = to_xml_with_header(&wc)?;
Ok(Response::builder()
@@ -33,7 +33,7 @@ pub async fn handle_delete_lifecycle(ctx: ReqCtx) -> Result<Response<ResBody>, E
mut bucket_params,
..
} = ctx;
bucket_params.lifecycle_config.update(None);
bucket_params.lifecycle_config.update(None.into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))
@@ -62,7 +62,7 @@ pub async fn handle_put_lifecycle(
.validate_into_garage_lifecycle_config()
.ok_or_bad_request("Invalid lifecycle configuration")?;
bucket_params.lifecycle_config.update(Some(config));
bucket_params.lifecycle_config.update(Some(config).into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))
+3 -3
View File
@@ -16,7 +16,7 @@ pub const X_AMZ_WEBSITE_REDIRECT_LOCATION: HeaderName =
pub async fn handle_get_website(ctx: ReqCtx) -> Result<Response<ResBody>, Error> {
let ReqCtx { bucket_params, .. } = ctx;
if let Some(website) = bucket_params.website_config.get() {
if let Some(website) = bucket_params.website_config.get().inner() {
let wc = WebsiteConfiguration {
xmlns: (),
error_document: website.error_document.as_ref().map(|v| Key {
@@ -54,7 +54,7 @@ pub async fn handle_delete_website(ctx: ReqCtx) -> Result<Response<ResBody>, Err
mut bucket_params,
..
} = ctx;
bucket_params.website_config.update(None);
bucket_params.website_config.update(None.into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))
@@ -83,7 +83,7 @@ pub async fn handle_put_website(
bucket_params
.website_config
.update(Some(conf.into_garage_website_config()?));
.update(Some(conf.into_garage_website_config()?).into());
garage
.bucket_table
.insert(&Bucket::present(bucket_id, bucket_params))