use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeltaTableType {
Managed,
External,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeltaDataSourceFormat {
Delta,
Iceberg,
Hudi,
Parquet,
Csv,
Json,
Orc,
Avro,
Text,
UnityCatalog,
Deltasharing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum DeltaCredentialOperation {
Read,
ReadWrite,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaStorageCredentialConfig {
#[serde(rename = "s3.access-key-id", skip_serializing_if = "Option::is_none")]
pub s3_access_key_id: Option<String>,
#[serde(
rename = "s3.secret-access-key",
skip_serializing_if = "Option::is_none"
)]
pub s3_secret_access_key: Option<String>,
#[serde(rename = "s3.session-token", skip_serializing_if = "Option::is_none")]
pub s3_session_token: Option<String>,
#[serde(rename = "azure.sas-token", skip_serializing_if = "Option::is_none")]
pub azure_sas_token: Option<String>,
#[serde(rename = "gcs.oauth-token", skip_serializing_if = "Option::is_none")]
pub gcs_oauth_token: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaStorageCredential {
pub prefix: String,
pub operation: DeltaCredentialOperation,
pub config: DeltaStorageCredentialConfig,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expiration_time_ms: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaCredentialsResponse {
pub storage_credentials: Vec<DeltaStorageCredential>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaCatalogConfig {
pub endpoints: Vec<String>,
pub protocol_version: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum DeltaDataType {
Primitive(String),
Array(Box<DeltaArrayType>),
Map(Box<DeltaMapType>),
Struct(Box<DeltaStructType>),
Decimal(DeltaDecimalType),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ArrayTypeTag {
#[default]
Array,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MapTypeTag {
#[default]
Map,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DecimalTypeTag {
#[default]
Decimal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaArrayType {
#[serde(rename = "type", default)]
pub type_tag: ArrayTypeTag,
pub element_type: DeltaDataType,
pub contains_null: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaMapType {
#[serde(rename = "type", default)]
pub type_tag: MapTypeTag,
pub key_type: DeltaDataType,
pub value_type: DeltaDataType,
pub value_contains_null: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaDecimalType {
#[serde(rename = "type", default)]
pub type_tag: DecimalTypeTag,
pub precision: i32,
pub scale: i32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeltaStructField {
pub name: String,
#[serde(rename = "type")]
pub data_type: DeltaDataType,
pub nullable: bool,
pub metadata: BTreeMap<String, serde_json::Value>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeltaStructType {
#[serde(rename = "type", default = "struct_type_tag")]
pub type_tag: StructTypeTag,
pub fields: Vec<DeltaStructField>,
}
fn struct_type_tag() -> StructTypeTag {
StructTypeTag::Struct
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StructTypeTag {
#[default]
Struct,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaProtocol {
pub min_reader_version: i32,
pub min_writer_version: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub reader_features: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub writer_features: Option<Vec<String>>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaSuggestedProtocol {
#[serde(skip_serializing_if = "Option::is_none")]
pub reader_features: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub writer_features: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaClusteringDomainMetadata {
#[serde(rename = "clusteringColumns")]
pub clustering_columns: Vec<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaRowTrackingDomainMetadata {
#[serde(rename = "rowIdHighWaterMark")]
pub row_id_high_water_mark: i64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaDomainMetadataUpdates {
#[serde(rename = "delta.clustering", skip_serializing_if = "Option::is_none")]
pub delta_clustering: Option<DeltaClusteringDomainMetadata>,
#[serde(rename = "delta.rowTracking", skip_serializing_if = "Option::is_none")]
pub delta_row_tracking: Option<DeltaRowTrackingDomainMetadata>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaIcebergMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata_location: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub converted_delta_version: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub converted_delta_timestamp: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub base_converted_delta_version: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaUniformMetadata {
pub iceberg: DeltaIcebergMetadata,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaCommit {
pub version: i64,
pub timestamp: i64,
pub file_name: String,
pub file_size: i64,
pub file_modification_timestamp: i64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeltaCreateStagingTableRequest {
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaStagingTableResponse {
pub table_id: String,
pub table_type: DeltaTableType,
pub location: String,
pub storage_credentials: Vec<DeltaStorageCredential>,
pub required_protocol: DeltaProtocol,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggested_protocol: Option<DeltaSuggestedProtocol>,
pub required_properties: BTreeMap<String, Option<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub suggested_properties: Option<BTreeMap<String, Option<String>>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaCreateTableRequest {
pub name: String,
pub location: String,
pub table_type: DeltaTableType,
#[serde(skip_serializing_if = "Option::is_none")]
pub data_source_format: Option<DeltaDataSourceFormat>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comment: Option<String>,
pub columns: DeltaStructType,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition_columns: Option<Vec<String>>,
pub protocol: DeltaProtocol,
pub properties: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain_metadata: Option<DeltaDomainMetadataUpdates>,
pub last_commit_timestamp_ms: i64,
#[serde(skip_serializing_if = "Option::is_none")]
pub uniform: Option<DeltaUniformMetadata>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaTableMetadata {
pub etag: String,
pub table_type: DeltaTableType,
pub table_uuid: String,
pub location: String,
pub created_time: i64,
pub updated_time: i64,
pub columns: DeltaStructType,
#[serde(skip_serializing_if = "Option::is_none")]
pub partition_columns: Option<Vec<String>>,
pub properties: BTreeMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_commit_version: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_commit_timestamp_ms: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaLoadTableResponse {
pub metadata: DeltaTableMetadata,
#[serde(skip_serializing_if = "Option::is_none")]
pub commits: Option<Vec<DeltaCommit>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub uniform: Option<DeltaUniformMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_table_version: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaRenameTableRequest {
pub new_name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum DeltaTableRequirement {
AssertTableUuid { uuid: String },
AssertEtag { etag: String },
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case")]
pub enum DeltaTableUpdate {
SetProperties {
updates: BTreeMap<String, String>,
},
RemoveProperties {
removals: Vec<String>,
},
SetColumns {
columns: DeltaStructType,
},
SetTableComment {
comment: String,
},
AddCommit {
commit: DeltaCommit,
#[serde(skip_serializing_if = "Option::is_none")]
uniform: Option<DeltaUniformMetadata>,
},
SetLatestBackfilledVersion {
#[serde(rename = "latest-published-version")]
latest_published_version: i64,
},
SetProtocol {
protocol: DeltaProtocol,
},
SetDomainMetadata {
updates: DeltaDomainMetadataUpdates,
},
RemoveDomainMetadata {
domains: Vec<String>,
},
SetPartitionColumns {
#[serde(rename = "partition-columns")]
partition_columns: Vec<String>,
},
UpdateMetadataSnapshotVersion {
#[serde(rename = "last-commit-version")]
last_commit_version: i64,
#[serde(rename = "last-commit-timestamp-ms")]
last_commit_timestamp_ms: i64,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DeltaUpdateTableRequest {
pub requirements: Vec<DeltaTableRequirement>,
pub updates: Vec<DeltaTableUpdate>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaFileSizeHistogram {
pub sorted_bin_boundaries: Vec<i64>,
pub file_counts: Vec<i64>,
pub total_bytes: Vec<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub commit_version: Option<i64>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaCommitReport {
#[serde(skip_serializing_if = "Option::is_none")]
pub num_files_added: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_bytes_added: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_files_removed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_bytes_removed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_rows_inserted: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_rows_removed: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub num_rows_updated: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub file_size_histogram: Option<DeltaFileSizeHistogram>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaReport {
#[serde(skip_serializing_if = "Option::is_none")]
pub commit_report: Option<DeltaCommitReport>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DeltaReportMetricsRequest {
pub table_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub report: Option<DeltaReport>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeltaErrorType {
BadRequestException,
InvalidParameterValueException,
UnsupportedTableFormatException,
NotAuthorizedException,
PermissionDeniedException,
NotFoundException,
NoSuchCatalogException,
NoSuchSchemaException,
NoSuchTableException,
AlreadyExistsException,
CommitVersionConflictException,
UpdateRequirementConflictException,
ResourceExhaustedException,
TooManyRequestsException,
CommitStateUnknownException,
InternalServerErrorException,
NotImplementedException,
}
impl DeltaErrorType {
pub fn is_not_found(&self) -> bool {
matches!(
self,
DeltaErrorType::NotFoundException
| DeltaErrorType::NoSuchCatalogException
| DeltaErrorType::NoSuchSchemaException
| DeltaErrorType::NoSuchTableException
)
}
pub fn is_already_exists(&self) -> bool {
matches!(self, DeltaErrorType::AlreadyExistsException)
}
pub fn is_commit_conflict(&self) -> bool {
matches!(self, DeltaErrorType::CommitVersionConflictException)
}
pub fn is_update_requirement_conflict(&self) -> bool {
matches!(self, DeltaErrorType::UpdateRequirementConflictException)
}
pub fn is_resource_exhausted(&self) -> bool {
matches!(
self,
DeltaErrorType::ResourceExhaustedException | DeltaErrorType::TooManyRequestsException
)
}
pub fn is_commit_state_unknown(&self) -> bool {
matches!(self, DeltaErrorType::CommitStateUnknownException)
}
pub fn is_unsupported_table_format(&self) -> bool {
matches!(self, DeltaErrorType::UnsupportedTableFormatException)
}
pub fn is_not_implemented(&self) -> bool {
matches!(self, DeltaErrorType::NotImplementedException)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaErrorModel {
pub message: String,
#[serde(rename = "type")]
pub error_type: DeltaErrorType,
pub code: u16,
#[serde(skip_serializing_if = "Option::is_none")]
pub stack: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeltaErrorResponse {
pub error: DeltaErrorModel,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{Value, json};
fn round_trip<T>(value: Value)
where
T: Serialize + for<'de> Deserialize<'de>,
{
let parsed: T = serde_json::from_value(value.clone())
.unwrap_or_else(|e| panic!("deserialize failed: {e}\njson: {value:#}"));
let reserialized = serde_json::to_value(&parsed).expect("serialize");
assert_eq!(
reserialized, value,
"round-trip mismatch\n expected: {value:#}\n got: {reserialized:#}"
);
}
#[test]
fn staging_table_response() {
round_trip::<DeltaStagingTableResponse>(json!({
"table-id": "123e4567-e89b-12d3-a456-426614174000",
"table-type": "MANAGED",
"location": "s3://bucket/warehouse/catalog/schema/table",
"storage-credentials": [{
"prefix": "s3://bucket/warehouse/catalog/schema/table/",
"operation": "READ_WRITE",
"config": {
"s3.access-key-id": "AK...example",
"s3.secret-access-key": "ExampleKey",
"s3.session-token": "token"
},
"expiration-time-ms": 1234567890000_i64
}],
"required-protocol": {
"min-reader-version": 3,
"min-writer-version": 7,
"reader-features": ["deletionVectors", "vacuumProtocolCheck"],
"writer-features": ["catalogManaged", "deletionVectors"]
},
"suggested-protocol": {
"reader-features": ["typeWidening"],
"writer-features": ["domainMetadata", "rowTracking"]
},
"required-properties": { "delta.checkpointPolicy": "v2" },
"suggested-properties": {
"delta.rowTracking.materializedRowIdColumnName": null,
"delta.rowTracking.materializedRowCommitVersionColumnName": null
}
}));
}
#[test]
fn create_table_request_with_decimal_and_partition() {
round_trip::<DeltaCreateTableRequest>(json!({
"name": "sales",
"location": "s3://bucket/warehouse/catalog/schema/sales",
"table-type": "MANAGED",
"columns": {
"type": "struct",
"fields": [
{ "name": "id", "type": "long", "nullable": false, "metadata": {} },
{
"name": "amount",
"type": { "type": "decimal", "precision": 10, "scale": 2 },
"nullable": true,
"metadata": {}
}
]
},
"partition-columns": ["id"],
"protocol": {
"min-reader-version": 3,
"min-writer-version": 7,
"reader-features": ["deletionVectors"],
"writer-features": ["deletionVectors", "invariants"]
},
"properties": { "delta.enableDeletionVectors": "true" },
"last-commit-timestamp-ms": 1704067400000_i64
}));
}
#[test]
fn struct_type_carries_type_tag() {
round_trip::<DeltaStructType>(json!({
"type": "struct",
"fields": [
{ "name": "id", "type": "long", "nullable": false, "metadata": {} },
{ "name": "name", "type": "string", "nullable": true, "metadata": {} }
]
}));
}
#[test]
fn nested_array_and_map_types() {
round_trip::<DeltaStructField>(json!({
"name": "tags",
"type": {
"type": "array",
"element-type": "string",
"contains-null": true
},
"nullable": true,
"metadata": {}
}));
round_trip::<DeltaStructField>(json!({
"name": "props",
"type": {
"type": "map",
"key-type": "string",
"value-type": "long",
"value-contains-null": false
},
"nullable": true,
"metadata": {}
}));
}
#[test]
fn load_table_response_with_commits() {
round_trip::<DeltaLoadTableResponse>(json!({
"metadata": {
"etag": "etag-1",
"table-type": "MANAGED",
"table-uuid": "123e4567-e89b-12d3-a456-426614174000",
"location": "s3://bucket/warehouse/catalog/schema/table",
"created-time": 1705600000000_i64,
"updated-time": 1705600000000_i64,
"columns": { "type": "struct", "fields": [] },
"properties": { "delta.checkpointPolicy": "v2" },
"last-commit-version": 0,
"last-commit-timestamp-ms": 1704067400000_i64
},
"commits": [{
"version": 1,
"timestamp": 1704067200000_i64,
"file-name": "00000000-0000-0000-0000-00000000002a.json",
"file-size": 2048,
"file-modification-timestamp": 1704067200000_i64
}],
"latest-table-version": 1
}));
}
#[test]
fn update_table_request_add_commit() {
round_trip::<DeltaUpdateTableRequest>(json!({
"requirements": [
{ "type": "assert-table-uuid", "uuid": "123e4567-e89b-12d3-a456-426614174000" },
{ "type": "assert-etag", "etag": "etag-1" }
],
"updates": [
{
"action": "add-commit",
"commit": {
"version": 1,
"timestamp": 1704067200000_i64,
"file-name": "v.uuid1.json",
"file-size": 2048,
"file-modification-timestamp": 1704067200000_i64
}
},
{ "action": "set-latest-backfilled-version", "latest-published-version": 0 }
]
}));
}
#[test]
fn update_table_request_metadata_actions() {
round_trip::<DeltaUpdateTableRequest>(json!({
"requirements": [],
"updates": [
{ "action": "set-properties", "updates": { "k": "v" } },
{ "action": "remove-properties", "removals": ["old"] },
{ "action": "set-table-comment", "comment": "hello" },
{
"action": "set-protocol",
"protocol": { "min-reader-version": 3, "min-writer-version": 7 }
},
{ "action": "set-partition-columns", "partition-columns": ["id"] },
{
"action": "update-metadata-snapshot-version",
"last-commit-version": 5,
"last-commit-timestamp-ms": 1704067400000_i64
}
]
}));
}
#[test]
fn update_table_domain_metadata_actions() {
round_trip::<DeltaUpdateTableRequest>(json!({
"requirements": [],
"updates": [
{
"action": "set-domain-metadata",
"updates": {
"delta.clustering": { "clusteringColumns": [["id"], ["address", "city"]] },
"delta.rowTracking": { "rowIdHighWaterMark": 42 }
}
},
{ "action": "remove-domain-metadata", "domains": ["delta.clustering"] }
]
}));
}
#[test]
fn credentials_response() {
round_trip::<DeltaCredentialsResponse>(json!({
"storage-credentials": [{
"prefix": "s3://bucket/path/",
"operation": "READ",
"config": { "s3.access-key-id": "AK", "s3.secret-access-key": "SK" },
"expiration-time-ms": 1234567890000_i64
}]
}));
}
#[test]
fn catalog_config() {
round_trip::<DeltaCatalogConfig>(json!({
"endpoints": ["GET /v1/temporary-path-credentials"],
"protocol-version": "1.0"
}));
}
#[test]
fn report_metrics_request() {
round_trip::<DeltaReportMetricsRequest>(json!({
"table-id": "123e4567-e89b-12d3-a456-426614174000",
"report": {
"commit-report": {
"num-files-added": 10,
"num-bytes-added": 104857600_i64,
"file-size-histogram": {
"sorted-bin-boundaries": [0, 1024, 2048],
"file-counts": [100, 40, 5],
"total-bytes": [104857600_i64, 167772160_i64, 83886080_i64],
"commit-version": 6
}
}
}
}));
}
#[test]
fn error_response_round_trips() {
round_trip::<DeltaErrorResponse>(json!({
"error": {
"message": "table not found",
"type": "NoSuchTableException",
"code": 404
}
}));
}
#[test]
fn error_response_with_stack_round_trips() {
round_trip::<DeltaErrorResponse>(json!({
"error": {
"message": "boom",
"type": "InternalServerErrorException",
"code": 500,
"stack": ["frame one", "frame two"]
}
}));
}
#[test]
fn uniform_metadata_round_trips() {
round_trip::<DeltaUniformMetadata>(json!({
"iceberg": {
"metadata-location": "s3://bucket/metadata/v1.json",
"converted-delta-version": 42,
"converted-delta-timestamp": 1704067200000_i64
}
}));
}
}