use keldra_api::v1::administration_service_client::AdministrationServiceClient;
use keldra_api::v1::authz_service_client::AuthzServiceClient;
use keldra_api::v1::credential_service_client::CredentialServiceClient;
use keldra_api::v1::index_service_client::IndexServiceClient;
use keldra_api::v1::object_service_client::ObjectServiceClient;
use keldra_api::v1::personal_db_service_client::PersonalDbServiceClient;
use keldra_api::v1::{
AccessToken, ExchangeClientCredentialsRequest, MutationReceipt, PutHeader, PutRequest,
};
use tonic::metadata::{Ascii, MetadataValue};
use tonic::service::Interceptor;
use tonic::transport::{Channel, Endpoint};
use tonic::{Request, Status};
mod typed_json_index;
mod typed_json_query;
pub use typed_json_index::{
BooleanField, FloatField, IndexDefinitionError, IndexOrderToken, KeywordField,
SignedIntegerField, TextField, TypedJsonIndexBuilder, UnsignedIntegerField,
};
pub use typed_json_query::{PredicateBuildError, PredicateExpression, PredicateScalar};
const MAX_MESSAGE_BYTES: usize = 72 * 1024 * 1024;
pub use keldra_api::v1;
#[derive(Clone)]
pub struct BearerToken {
value: MetadataValue<Ascii>,
}
impl BearerToken {
pub fn new(token: &str) -> Result<Self, tonic::metadata::errors::InvalidMetadataValue> {
format!("Bearer {token}")
.parse()
.map(|value| Self { value })
}
}
impl Interceptor for BearerToken {
fn call(&mut self, mut request: Request<()>) -> Result<Request<()>, Status> {
request
.metadata_mut()
.insert("authorization", self.value.clone());
Ok(request)
}
}
pub type RawClient =
ObjectServiceClient<tonic::service::interceptor::InterceptedService<Channel, BearerToken>>;
pub type RawAdministrationClient = AdministrationServiceClient<
tonic::service::interceptor::InterceptedService<Channel, BearerToken>,
>;
pub type RawAuthzClient =
AuthzServiceClient<tonic::service::interceptor::InterceptedService<Channel, BearerToken>>;
pub type RawIndexClient =
IndexServiceClient<tonic::service::interceptor::InterceptedService<Channel, BearerToken>>;
pub type RawPersonalDbClient =
PersonalDbServiceClient<tonic::service::interceptor::InterceptedService<Channel, BearerToken>>;
pub fn object_client(
channel: Channel,
token: &str,
) -> Result<RawClient, tonic::metadata::errors::InvalidMetadataValue> {
Ok(
ObjectServiceClient::with_interceptor(channel, BearerToken::new(token)?)
.max_encoding_message_size(MAX_MESSAGE_BYTES)
.max_decoding_message_size(MAX_MESSAGE_BYTES),
)
}
pub fn administration_client(
channel: Channel,
token: &str,
) -> Result<RawAdministrationClient, tonic::metadata::errors::InvalidMetadataValue> {
Ok(
AdministrationServiceClient::with_interceptor(channel, BearerToken::new(token)?)
.max_encoding_message_size(MAX_MESSAGE_BYTES)
.max_decoding_message_size(MAX_MESSAGE_BYTES),
)
}
pub fn authz_client(
channel: Channel,
token: &str,
) -> Result<RawAuthzClient, tonic::metadata::errors::InvalidMetadataValue> {
Ok(
AuthzServiceClient::with_interceptor(channel, BearerToken::new(token)?)
.max_encoding_message_size(MAX_MESSAGE_BYTES)
.max_decoding_message_size(MAX_MESSAGE_BYTES),
)
}
pub fn index_client(
channel: Channel,
token: &str,
) -> Result<RawIndexClient, tonic::metadata::errors::InvalidMetadataValue> {
Ok(
IndexServiceClient::with_interceptor(channel, BearerToken::new(token)?)
.max_encoding_message_size(MAX_MESSAGE_BYTES)
.max_decoding_message_size(MAX_MESSAGE_BYTES),
)
}
pub fn personaldb_client(
channel: Channel,
token: &str,
) -> Result<RawPersonalDbClient, tonic::metadata::errors::InvalidMetadataValue> {
Ok(
PersonalDbServiceClient::with_interceptor(channel, BearerToken::new(token)?)
.max_encoding_message_size(MAX_MESSAGE_BYTES)
.max_decoding_message_size(MAX_MESSAGE_BYTES),
)
}
pub async fn connect(
endpoint: impl AsRef<str>,
token: &str,
) -> Result<RawClient, Box<dyn std::error::Error + Send + Sync>> {
let channel = connect_channel(endpoint).await?;
Ok(object_client(channel, token)?)
}
pub async fn connect_channel(
endpoint: impl AsRef<str>,
) -> Result<Channel, Box<dyn std::error::Error + Send + Sync>> {
Ok(Endpoint::from_shared(endpoint.as_ref().to_owned())?
.connect()
.await?)
}
pub async fn exchange_client_credentials(
channel: Channel,
client_id: impl Into<String>,
client_secret: impl Into<String>,
) -> Result<AccessToken, tonic::Status> {
CredentialServiceClient::new(channel)
.exchange_client_credentials(ExchangeClientCredentialsRequest {
client_id: client_id.into(),
client_secret: client_secret.into(),
})
.await
.map(tonic::Response::into_inner)
}
pub async fn connect_with_credentials(
endpoint: impl AsRef<str>,
client_id: impl Into<String>,
client_secret: impl Into<String>,
) -> Result<RawClient, Box<dyn std::error::Error + Send + Sync>> {
let channel = connect_channel(endpoint).await?;
let token = exchange_client_credentials(channel.clone(), client_id, client_secret).await?;
Ok(object_client(channel, &token.access_token)?)
}
pub async fn put_chunks<I>(
client: &mut RawClient,
header: PutHeader,
chunks: I,
) -> Result<MutationReceipt, tonic::Status>
where
I: IntoIterator<Item = Vec<u8>>,
I::IntoIter: Send + 'static,
{
let token = client.start_put(header).await?.into_inner();
let mut chunks = chunks.into_iter().peekable();
if chunks.peek().is_none() {
let ready = client
.put(tokio_stream::iter([PutRequest {
token: Some(token),
chunk: Vec::new(),
}]))
.await
.map(tonic::Response::into_inner)?;
return client.put_end(ready).await.map(tonic::Response::into_inner);
}
let requests = chunks.map(move |chunk| PutRequest {
token: Some(token.clone()),
chunk,
});
let ready = client
.put(tokio_stream::iter(requests))
.await
.map(tonic::Response::into_inner)?;
client.put_end(ready).await.map(tonic::Response::into_inner)
}
#[cfg(test)]
mod tests {
use tonic::transport::Endpoint;
use super::v1::{
CreateBucketRequest, DeleteIfVersionRequest, DeleteVersionRequest, DeleteVersionResponse,
Durability, ObjectVersioning, PutHeader,
};
use super::{
MAX_MESSAGE_BYTES, RawAdministrationClient, RawAuthzClient, RawClient, RawIndexClient,
RawPersonalDbClient, administration_client, authz_client, index_client, object_client,
personaldb_client,
};
#[test]
fn versioning_defaults_to_unversioned_and_delete_operations_stay_distinct() {
assert_eq!(
CreateBucketRequest::default().versioning,
ObjectVersioning::Unversioned as i32
);
let conditional_current_delete = DeleteIfVersionRequest {
expected_version: 9,
..Default::default()
};
let retained_delete = DeleteVersionRequest {
version: 8,
..Default::default()
};
assert_eq!(conditional_current_delete.expected_version, 9);
assert_eq!(retained_delete.version, 8);
assert_eq!(
DeleteVersionResponse {
deleted: true,
replacement_tombstone_version: Some(10),
}
.replacement_tombstone_version,
Some(10)
);
}
#[test]
fn replicated_durability_is_preserved_for_the_server_to_reject() {
let header = PutHeader {
durability: Durability::Replicated as i32,
..Default::default()
};
assert_eq!(header.durability, Durability::Replicated as i32);
assert_ne!(header.durability, Durability::Local as i32);
}
#[tokio::test]
async fn every_authenticated_service_constructor_uses_the_shared_transport_surface() {
assert_eq!(MAX_MESSAGE_BYTES, 72 * 1024 * 1024);
let channel = Endpoint::from_static("http://127.0.0.1:50051").connect_lazy();
let _: RawClient = object_client(channel.clone(), "token").unwrap();
let _: RawAuthzClient = authz_client(channel.clone(), "token").unwrap();
let _: RawIndexClient = index_client(channel.clone(), "token").unwrap();
let _: RawAdministrationClient = administration_client(channel.clone(), "token").unwrap();
let _: RawPersonalDbClient = personaldb_client(channel, "token").unwrap();
}
}