use crate::cloud::v1alpha1::ext::DataSourceKind;
use crate::{TypeConversionError, invalid_field, missing_field};
#[derive(Debug, Clone)]
pub struct ChunkKey {
pub chunk_id: re_chunk::ChunkId,
pub data_source_kind: DataSourceKind,
pub location: Vec<u8>,
pub etag: Option<ETag>,
pub registration_time: Option<jiff::Timestamp>,
}
impl ChunkKey {
pub fn as_bytes(&self) -> Vec<u8> {
use prost::Message as _;
let chunk_key: crate::cloud::v1alpha1::ChunkKey = self.clone().into();
chunk_key.encode_to_vec()
}
}
impl TryFrom<crate::cloud::v1alpha1::ChunkKey> for ChunkKey {
type Error = TypeConversionError;
fn try_from(value: crate::cloud::v1alpha1::ChunkKey) -> Result<Self, Self::Error> {
let tuid = value
.chunk_id
.ok_or(missing_field!(crate::cloud::v1alpha1::ChunkKey, "chunk_id"))?;
let id: re_tuid::Tuid = tuid.try_into()?;
let chunk_id = re_chunk::ChunkId::from_u128(id.as_u128());
let data_source_kind = DataSourceKind::try_from(value.data_source_kind)?;
let location = value
.location
.ok_or(missing_field!(crate::cloud::v1alpha1::ChunkKey, "location"))?
.as_ref()
.to_vec();
Ok(Self {
chunk_id,
data_source_kind,
location,
etag: value.etag.map(ETag::new),
registration_time: value
.registration_time_nanos
.and_then(|n| jiff::Timestamp::from_nanosecond(n as i128).ok()),
})
}
}
impl TryFrom<&[u8]> for ChunkKey {
type Error = TypeConversionError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
use prost::Message as _;
let proto_chunk_key = crate::cloud::v1alpha1::ChunkKey::decode(bytes)
.map_err(TypeConversionError::DecodeError)?;
proto_chunk_key.try_into()
}
}
impl From<ChunkKey> for crate::cloud::v1alpha1::ChunkKey {
fn from(value: ChunkKey) -> Self {
let tuid =
crate::common::v1alpha1::Tuid::from(re_tuid::Tuid::from_u128(value.chunk_id.as_u128()));
let location = prost::bytes::Bytes::from_owner(value.location);
let data_source_kind: crate::cloud::v1alpha1::DataSourceKind =
value.data_source_kind.into();
Self {
chunk_id: Some(tuid),
data_source_kind: data_source_kind as i32,
location: Some(location),
etag: value.etag.map(Into::into),
registration_time_nanos: value
.registration_time
.and_then(|t| i64::try_from(t.as_nanosecond()).ok()),
}
}
}
#[derive(Debug, Clone)]
pub struct RrdChunkLocation {
pub url: url::Url,
pub offset: u64,
pub length: u64,
}
impl RrdChunkLocation {
pub fn as_bytes(&self) -> Vec<u8> {
use prost::Message as _;
let rrd_location: crate::cloud::v1alpha1::RrdChunkLocation = self.clone().into();
rrd_location.encode_to_vec()
}
}
impl TryFrom<crate::cloud::v1alpha1::RrdChunkLocation> for RrdChunkLocation {
type Error = TypeConversionError;
fn try_from(value: crate::cloud::v1alpha1::RrdChunkLocation) -> Result<Self, Self::Error> {
let url = value
.url
.ok_or(missing_field!(
crate::cloud::v1alpha1::RrdChunkLocation,
"url"
))?
.parse()
.map_err(|err: url::ParseError| {
invalid_field!(
crate::cloud::v1alpha1::RrdChunkLocation,
"url",
err.to_string()
)
})?;
let offset = value.offset.ok_or(missing_field!(
crate::cloud::v1alpha1::RrdChunkLocation,
"offset"
))?;
let length = value.length.ok_or(missing_field!(
crate::cloud::v1alpha1::RrdChunkLocation,
"length"
))?;
Ok(Self {
url,
offset,
length,
})
}
}
impl From<RrdChunkLocation> for crate::cloud::v1alpha1::RrdChunkLocation {
fn from(value: RrdChunkLocation) -> Self {
Self {
url: Some(value.url.to_string()),
offset: Some(value.offset),
length: Some(value.length),
}
}
}
impl TryFrom<&[u8]> for RrdChunkLocation {
type Error = TypeConversionError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
use prost::Message as _;
let proto_location = crate::cloud::v1alpha1::RrdChunkLocation::decode(bytes)
.map_err(TypeConversionError::DecodeError)?;
proto_location.try_into()
}
}
pub const SOURCE_CHANGED_MESSAGE: &str = "the source object has changed since this dataset was registered; re-register to pick up the new version";
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ETag(String);
impl ETag {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into().trim().to_owned())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_strong(&self) -> bool {
!self.0.starts_with("W/")
}
pub fn as_if_match(&self) -> Option<&str> {
self.is_strong().then_some(self.as_str())
}
pub fn matches(&self, actual: &Self) -> bool {
let expected = self.0.as_str();
let actual = actual.0.as_str();
let exp_weak = expected.starts_with("W/");
let act_weak = actual.starts_with("W/");
if exp_weak != act_weak {
return false;
}
fn strip(s: &str) -> &str {
let s = s.strip_prefix("W/").unwrap_or(s);
s.trim_start_matches('"').trim_end_matches('"')
}
strip(expected) == strip(actual)
}
}
impl std::fmt::Display for ETag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
impl From<String> for ETag {
fn from(s: String) -> Self {
Self::new(s)
}
}
impl From<&str> for ETag {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<ETag> for String {
fn from(e: ETag) -> Self {
e.0
}
}
pub fn url_strip_query(url: &str) -> &str {
url.split_once('?').map_or(url, |(prefix, _)| prefix)
}
#[cfg(test)]
mod tests {
use super::*;
fn et(s: &str) -> ETag {
ETag::new(s)
}
#[test]
fn etag_matches_strong_identical() {
assert!(et("\"abc\"").matches(&et("\"abc\"")));
}
#[test]
fn etag_matches_weak_identical() {
assert!(et("W/\"abc\"").matches(&et("W/\"abc\"")));
}
#[test]
fn etag_matches_strong_to_weak_downgrade_is_drift() {
assert!(!et("\"abc\"").matches(&et("W/\"abc\"")));
}
#[test]
fn etag_matches_weak_to_strong_upgrade_is_drift() {
assert!(!et("W/\"abc\"").matches(&et("\"abc\"")));
}
#[test]
fn etag_matches_different_values() {
assert!(!et("\"abc\"").matches(&et("\"def\"")));
assert!(!et("W/\"abc\"").matches(&et("W/\"def\"")));
assert!(!et("\"abc\"").matches(&et("W/\"def\"")));
assert!(!et("W/\"abc\"").matches(&et("\"def\"")));
}
#[test]
fn etag_matches_whitespace_tolerant() {
assert!(et(" \"abc\" ").matches(&et("\"abc\"")));
assert!(et("\"abc\"").matches(&et("\t\"abc\"\n")));
}
#[test]
fn etag_is_strong_basics() {
assert!(et("\"abc\"").is_strong());
assert!(!et("W/\"abc\"").is_strong());
assert!(!et(" W/\"abc\"").is_strong());
}
#[test]
fn etag_as_if_match_gates_on_strong() {
assert_eq!(et("\"abc\"").as_if_match(), Some("\"abc\""));
assert_eq!(et("W/\"abc\"").as_if_match(), None);
}
#[test]
fn url_strip_query_basics() {
assert_eq!(
url_strip_query("https://bucket.s3/key?x=1&y=2"),
"https://bucket.s3/key"
);
assert_eq!(
url_strip_query("https://bucket.s3/key"),
"https://bucket.s3/key"
);
assert_eq!(url_strip_query(""), "");
}
}