use crate::providers::ResourceProvider;
use crate::resource::RequestContext;
use crate::resource::version::{ConditionalResult, RawVersion, VersionConflict};
use crate::resource::versioned::VersionedResource;
use serde_json::Value;
use std::future::Future;
pub trait ConditionalOperations: ResourceProvider {
fn conditional_update_resource(
&self,
resource_type: &str,
id: &str,
data: Value,
expected_version: &RawVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<VersionedResource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(current_resource) => {
let versioned_current = current_resource;
let current_version = versioned_current.version();
if current_version != expected_version {
return Ok(ConditionalResult::VersionMismatch(
VersionConflict::standard_message(
expected_version.clone(),
current_version.clone(),
),
));
}
match self
.update_resource(resource_type, id, data, None, context)
.await
{
Ok(updated_resource) => {
let versioned_result = updated_resource;
Ok(ConditionalResult::Success(versioned_result))
}
Err(e) => Err(e),
}
}
None => Ok(ConditionalResult::NotFound),
}
}
}
fn conditional_delete_resource(
&self,
resource_type: &str,
id: &str,
expected_version: &RawVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<()>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(current_resource) => {
let versioned_current = current_resource;
let current_version = versioned_current.version();
if current_version != expected_version {
return Ok(ConditionalResult::VersionMismatch(
VersionConflict::standard_message(
expected_version.clone(),
current_version.clone(),
),
));
}
match self.delete_resource(resource_type, id, None, context).await {
Ok(_) => Ok(ConditionalResult::Success(())),
Err(e) => Err(e),
}
}
None => Ok(ConditionalResult::NotFound),
}
}
}
fn conditional_patch_resource(
&self,
resource_type: &str,
id: &str,
patch_request: &Value,
expected_version: &RawVersion,
context: &RequestContext,
) -> impl Future<Output = Result<ConditionalResult<VersionedResource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(current_resource) => {
let versioned_current = current_resource;
let current_version = versioned_current.version();
if current_version != expected_version {
return Ok(ConditionalResult::VersionMismatch(
VersionConflict::standard_message(
expected_version.clone(),
current_version.clone(),
),
));
}
match self
.patch_resource(resource_type, id, patch_request, None, context)
.await
{
Ok(patched_resource) => {
let versioned_result = patched_resource;
Ok(ConditionalResult::Success(versioned_result))
}
Err(e) => Err(e),
}
}
None => Ok(ConditionalResult::NotFound),
}
}
}
fn get_versioned_resource(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<VersionedResource>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(versioned_resource) => Ok(Some(versioned_resource)),
None => Ok(None),
}
}
}
fn create_versioned_resource(
&self,
resource_type: &str,
data: Value,
context: &RequestContext,
) -> impl Future<Output = Result<VersionedResource, Self::Error>> + Send
where
Self: Sync,
{
async move {
let versioned_resource = self.create_resource(resource_type, data, context).await?;
Ok(versioned_resource)
}
}
fn check_resource_version(
&self,
resource_type: &str,
id: &str,
expected_version: &RawVersion,
context: &RequestContext,
) -> impl Future<Output = Result<Option<bool>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(versioned_resource) => {
Ok(Some(versioned_resource.version() == expected_version))
}
None => Ok(None),
}
}
}
fn get_resource_version(
&self,
resource_type: &str,
id: &str,
context: &RequestContext,
) -> impl Future<Output = Result<Option<RawVersion>, Self::Error>> + Send
where
Self: Sync,
{
async move {
match self.get_resource(resource_type, id, context).await? {
Some(versioned_resource) => Ok(Some(versioned_resource.version().clone())),
None => Ok(None),
}
}
}
fn is_valid_version(&self, version: &RawVersion) -> bool {
!version.as_str().trim().is_empty()
}
fn create_version_conflict(
&self,
expected: RawVersion,
current: RawVersion,
resource_info: Option<&str>,
) -> VersionConflict {
let message = match resource_info {
Some(info) => format!(
"Resource {} was modified by another client. Expected version {}, current version {}",
info,
expected.as_str(),
current.as_str()
),
None => format!(
"Resource was modified by another client. Expected version {}, current version {}",
expected.as_str(),
current.as_str()
),
};
VersionConflict::new(expected, current, message)
}
}
impl<T: ResourceProvider> ConditionalOperations for T {}