use http::{
StatusCode, Uri,
uri::{Builder, InvalidUri},
};
use ocm_types::{
common::OcmAddress,
discovery::{Capability, Discovery},
error::{Error, ValidationError},
share::{NewShare, Protocol, ShareCreationResponse},
};
use crate::{
common::HttpClient,
drivers::{protocols::MultiProtocol, resources::Resource, shares::ShareRepoError},
};
use super::SHARE_ENDPOINT;
pub async fn send_share(
client: &impl HttpClient,
provider_id: String,
sending_party: OcmAddress,
receiving_server: &Discovery,
receiving_party: OcmAddress,
resource: &impl Resource,
protocol: MultiProtocol,
) -> Result<(NewShare, ShareCreationResponse), SendShareError> {
let receiving_server_endpoint: Uri = receiving_server
.end_point
.as_str()
.try_into()
.map_err(SendShareError::InvalidOcmEndpoint)?;
if !receiving_party.get_server_url().ends_with(
receiving_server_endpoint
.authority()
.map(|host| host.as_str())
.unwrap_or(""),
) {
Err(SendShareError::InvalidShareWith(format!(
"FQDN of shareWith must match receiving server: {} <-> {}",
receiving_server_endpoint
.authority()
.map(|host| host.as_str())
.unwrap_or(""),
receiving_party
)))?
}
if !receiving_server.enabled {
Err(SendShareError::RecievingServerNotEnabled)?
};
let protocol = try_convert_to_legacy_share(protocol, receiving_server)?;
let new_share = NewShare {
share_with: receiving_party,
name: resource.name().to_string(),
description: None,
provider_id,
owner: sending_party.clone(),
sender: sending_party,
owner_display_name: None,
sender_display_name: None,
share_type: ocm_types::common::ShareType::User,
resource_type: resource.resource_type().to_owned(),
expiration: None,
protocol,
};
let scheme = receiving_server_endpoint
.scheme_str()
.unwrap_or("https")
.to_owned();
let path = receiving_server_endpoint
.path()
.strip_suffix("/")
.unwrap_or(receiving_server_endpoint.path())
.to_string();
let resp = client
.post(
&Builder::from(receiving_server_endpoint)
.scheme(scheme.as_str())
.path_and_query(path + SHARE_ENDPOINT)
.build()
.unwrap(),
serde_json::to_value(&new_share).unwrap(),
)
.await
.map_err(SendShareError::RequestError)?;
let share_creation_response: ShareCreationResponse =
serde_json::from_str(&resp).map_err(|e| SendShareError::RequestError(e.to_string()))?;
Ok((new_share, share_creation_response))
}
#[derive(Debug)]
pub enum SendShareError {
InvalidOcmEndpoint(InvalidUri),
RecievingServerNotEnabled,
VersionCompatiblity(String),
RequestError(String),
StoringShareFailed(ShareRepoError),
InvalidShareWith(String),
InvalidSender(),
}
impl SendShareError {
pub fn status_code(&self) -> http::StatusCode {
match self {
SendShareError::RecievingServerNotEnabled => StatusCode::BAD_GATEWAY,
SendShareError::VersionCompatiblity(_) => StatusCode::BAD_GATEWAY,
SendShareError::RequestError(_) => StatusCode::BAD_GATEWAY,
SendShareError::StoringShareFailed(_) => StatusCode::INTERNAL_SERVER_ERROR,
SendShareError::InvalidShareWith(_) => StatusCode::NOT_ACCEPTABLE,
SendShareError::InvalidSender() => StatusCode::NOT_ACCEPTABLE,
SendShareError::InvalidOcmEndpoint(_) => StatusCode::NOT_ACCEPTABLE,
}
}
}
impl From<SendShareError> for ocm_types::error::Error {
fn from(value: SendShareError) -> Self {
match value {
SendShareError::RecievingServerNotEnabled => Error {
message: "DISABLED_OCM_SERVER".to_string(),
validation_errors: vec![],
},
SendShareError::VersionCompatiblity(v) => Error {
message: "UNSUPPORTED_OCM_VERSION".to_string(),
validation_errors: vec![ValidationError {
name: Some("UNSUPPORTED_OCM_VERSION".to_string()),
message: Some(v),
}],
},
SendShareError::RequestError(e) => Error {
message: "REQUEST_ERROR".to_string(),
validation_errors: vec![ValidationError {
name: Some("OCM Server rejected request".to_string()),
message: Some(e.to_string()),
}],
},
SendShareError::StoringShareFailed(_share_repo_error) => Error {
message: "STORAGE_ERROR".to_string(),
validation_errors: vec![ValidationError {
name: Some("Failed to store sent share".to_string()),
message: None,
}],
},
SendShareError::InvalidSender() => Error {
message: "INVALID_SENDER".to_string(),
validation_errors: vec![],
},
SendShareError::InvalidShareWith(e) => Error {
message: "INVALID_RECIPIENT".to_string(),
validation_errors: vec![ValidationError {
name: None,
message: Some(e.to_string()),
}],
},
SendShareError::InvalidOcmEndpoint(e) => Error {
message: "INVALID_OCM_ENDPOINT".to_string(),
validation_errors: vec![ValidationError {
name: Some("INVALID_OCM_ENDPOINT".to_string()),
message: Some(e.to_string()),
}],
},
}
}
}
impl From<ShareRepoError> for SendShareError {
fn from(value: ShareRepoError) -> Self {
Self::StoringShareFailed(value)
}
}
#[allow(deprecated)] fn try_convert_to_legacy_share(
protocol: MultiProtocol,
recieving_server: &ocm_types::discovery::Discovery,
) -> Result<Protocol, SendShareError> {
if recieving_server
.capabilities
.as_ref()
.is_none_or(|c| !c.contains(&Capability::ProtocolObject))
{
if protocol.webdav.is_some()
&& (protocol.webapp.is_some()
|| protocol.ssh.is_some()
|| !protocol.additional_protocols.is_empty())
{
Err(SendShareError::VersionCompatiblity(
"OCM v1.0 does not support multi protocol".to_string(),
))
} else if let Some(webdav) = protocol.webdav.clone() {
Ok(Protocol{
name: "webdav".to_string(),
options: Some(
webdav
.try_into()
.map_err(|e| SendShareError::VersionCompatiblity(
format!("failed to convert multi webdav share to legacy webdav share to support v1.0 OCM recipient: {e}"))
)?,
),
..Default::default()
})
} else {
panic!("Got empty protocol object: {protocol:?}");
}
} else {
Ok(protocol.into())
}
}