1use std::collections::BTreeMap;
16
17use serde::{Deserialize, Serialize};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
26pub enum DeltaTableType {
27 Managed,
28 External,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
40pub enum DeltaDataSourceFormat {
41 Delta,
42 Iceberg,
43 Hudi,
44 Parquet,
45 Csv,
46 Json,
47 Orc,
48 Avro,
49 Text,
50 UnityCatalog,
51 Deltasharing,
52}
53
54pub use olai_storage_credentials::wire::delta_api::Operation as DeltaCredentialOperation;
56
57pub use olai_storage_credentials::wire::delta_api::StorageCredential as DeltaStorageCredential;
66
67pub use olai_storage_credentials::wire::delta_api::CredentialsResponse as DeltaCredentialsResponse;
69
70#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "kebab-case")]
77pub struct DeltaCatalogConfig {
78 pub endpoints: Vec<String>,
80 pub protocol_version: String,
82}
83
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97#[serde(untagged)]
98pub enum DeltaDataType {
99 Primitive(String),
101 Array(Box<DeltaArrayType>),
102 Map(Box<DeltaMapType>),
103 Struct(Box<DeltaStructType>),
104 Decimal(DeltaDecimalType),
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
111#[serde(rename_all = "lowercase")]
112pub enum ArrayTypeTag {
113 #[default]
114 Array,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
119#[serde(rename_all = "lowercase")]
120pub enum MapTypeTag {
121 #[default]
122 Map,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
127#[serde(rename_all = "lowercase")]
128pub enum DecimalTypeTag {
129 #[default]
130 Decimal,
131}
132
133#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135#[serde(rename_all = "kebab-case")]
136pub struct DeltaArrayType {
137 #[serde(rename = "type", default)]
138 pub type_tag: ArrayTypeTag,
139 pub element_type: DeltaDataType,
140 pub contains_null: bool,
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145#[serde(rename_all = "kebab-case")]
146pub struct DeltaMapType {
147 #[serde(rename = "type", default)]
148 pub type_tag: MapTypeTag,
149 pub key_type: DeltaDataType,
150 pub value_type: DeltaDataType,
151 pub value_contains_null: bool,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
159pub struct DeltaDecimalType {
160 #[serde(rename = "type", default)]
161 pub type_tag: DecimalTypeTag,
162 pub precision: i32,
163 pub scale: i32,
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct DeltaStructField {
169 pub name: String,
170 #[serde(rename = "type")]
171 pub data_type: DeltaDataType,
172 pub nullable: bool,
173 pub metadata: BTreeMap<String, serde_json::Value>,
176}
177
178#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
182pub struct DeltaStructType {
183 #[serde(rename = "type", default = "struct_type_tag")]
184 pub type_tag: StructTypeTag,
185 pub fields: Vec<DeltaStructField>,
186}
187
188fn struct_type_tag() -> StructTypeTag {
189 StructTypeTag::Struct
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
194#[serde(rename_all = "lowercase")]
195pub enum StructTypeTag {
196 #[default]
197 Struct,
198}
199
200#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
206#[serde(rename_all = "kebab-case")]
207pub struct DeltaProtocol {
208 pub min_reader_version: i32,
209 pub min_writer_version: i32,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub reader_features: Option<Vec<String>>,
212 #[serde(skip_serializing_if = "Option::is_none")]
213 pub writer_features: Option<Vec<String>>,
214}
215
216#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
219#[serde(rename_all = "kebab-case")]
220pub struct DeltaSuggestedProtocol {
221 #[serde(skip_serializing_if = "Option::is_none")]
222 pub reader_features: Option<Vec<String>>,
223 #[serde(skip_serializing_if = "Option::is_none")]
224 pub writer_features: Option<Vec<String>>,
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
233pub struct DeltaClusteringDomainMetadata {
234 #[serde(rename = "clusteringColumns")]
237 pub clustering_columns: Vec<Vec<String>>,
238}
239
240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
242pub struct DeltaRowTrackingDomainMetadata {
243 #[serde(rename = "rowIdHighWaterMark")]
245 pub row_id_high_water_mark: i64,
246}
247
248#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
250pub struct DeltaDomainMetadataUpdates {
251 #[serde(rename = "delta.clustering", skip_serializing_if = "Option::is_none")]
252 pub delta_clustering: Option<DeltaClusteringDomainMetadata>,
253 #[serde(rename = "delta.rowTracking", skip_serializing_if = "Option::is_none")]
254 pub delta_row_tracking: Option<DeltaRowTrackingDomainMetadata>,
255}
256
257#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263#[serde(rename_all = "kebab-case")]
264pub struct DeltaIcebergMetadata {
265 #[serde(skip_serializing_if = "Option::is_none")]
266 pub metadata_location: Option<String>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 pub converted_delta_version: Option<i64>,
269 #[serde(skip_serializing_if = "Option::is_none")]
270 pub converted_delta_timestamp: Option<i64>,
271 #[serde(skip_serializing_if = "Option::is_none")]
272 pub base_converted_delta_version: Option<i64>,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277pub struct DeltaUniformMetadata {
278 pub iceberg: DeltaIcebergMetadata,
279}
280
281#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
287#[serde(rename_all = "kebab-case")]
288pub struct DeltaCommit {
289 pub version: i64,
290 pub timestamp: i64,
292 pub file_name: String,
294 pub file_size: i64,
295 pub file_modification_timestamp: i64,
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
305pub struct DeltaCreateStagingTableRequest {
306 pub name: String,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
313#[serde(rename_all = "kebab-case")]
314pub struct DeltaStagingTableResponse {
315 pub table_id: String,
316 pub table_type: DeltaTableType,
317 pub location: String,
318 pub storage_credentials: Vec<DeltaStorageCredential>,
319 pub required_protocol: DeltaProtocol,
320 #[serde(skip_serializing_if = "Option::is_none")]
321 pub suggested_protocol: Option<DeltaSuggestedProtocol>,
322 pub required_properties: BTreeMap<String, Option<String>>,
324 #[serde(skip_serializing_if = "Option::is_none")]
327 pub suggested_properties: Option<BTreeMap<String, Option<String>>>,
328}
329
330#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
332#[serde(rename_all = "kebab-case")]
333pub struct DeltaCreateTableRequest {
334 pub name: String,
335 pub location: String,
336 pub table_type: DeltaTableType,
337 #[serde(skip_serializing_if = "Option::is_none")]
344 pub data_source_format: Option<DeltaDataSourceFormat>,
345 #[serde(skip_serializing_if = "Option::is_none")]
346 pub comment: Option<String>,
347 pub columns: DeltaStructType,
348 #[serde(skip_serializing_if = "Option::is_none")]
349 pub partition_columns: Option<Vec<String>>,
350 pub protocol: DeltaProtocol,
351 pub properties: BTreeMap<String, String>,
352 #[serde(skip_serializing_if = "Option::is_none")]
353 pub domain_metadata: Option<DeltaDomainMetadataUpdates>,
354 pub last_commit_timestamp_ms: i64,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 pub uniform: Option<DeltaUniformMetadata>,
359}
360
361#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
363#[serde(rename_all = "kebab-case")]
364pub struct DeltaTableMetadata {
365 pub etag: String,
367 pub table_type: DeltaTableType,
368 pub table_uuid: String,
369 pub location: String,
370 pub created_time: i64,
371 pub updated_time: i64,
372 pub columns: DeltaStructType,
373 #[serde(skip_serializing_if = "Option::is_none")]
374 pub partition_columns: Option<Vec<String>>,
375 pub properties: BTreeMap<String, String>,
376 #[serde(skip_serializing_if = "Option::is_none")]
379 pub last_commit_version: Option<i64>,
380 #[serde(skip_serializing_if = "Option::is_none")]
383 pub last_commit_timestamp_ms: Option<i64>,
384}
385
386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
388#[serde(rename_all = "kebab-case")]
389pub struct DeltaLoadTableResponse {
390 pub metadata: DeltaTableMetadata,
391 #[serde(skip_serializing_if = "Option::is_none")]
393 pub commits: Option<Vec<DeltaCommit>>,
394 #[serde(skip_serializing_if = "Option::is_none")]
395 pub uniform: Option<DeltaUniformMetadata>,
396 #[serde(skip_serializing_if = "Option::is_none")]
399 pub latest_table_version: Option<i64>,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404#[serde(rename_all = "kebab-case")]
405pub struct DeltaRenameTableRequest {
406 pub new_name: String,
407}
408
409#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
415#[serde(tag = "type", rename_all = "kebab-case")]
416pub enum DeltaTableRequirement {
417 AssertTableUuid { uuid: String },
419 AssertEtag { etag: String },
421}
422
423#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
426#[serde(tag = "action", rename_all = "kebab-case")]
427pub enum DeltaTableUpdate {
428 SetProperties {
429 updates: BTreeMap<String, String>,
430 },
431 RemoveProperties {
432 removals: Vec<String>,
433 },
434 SetColumns {
435 columns: DeltaStructType,
436 },
437 SetTableComment {
438 comment: String,
439 },
440 AddCommit {
441 commit: DeltaCommit,
442 #[serde(skip_serializing_if = "Option::is_none")]
443 uniform: Option<DeltaUniformMetadata>,
444 },
445 SetLatestBackfilledVersion {
446 #[serde(rename = "latest-published-version")]
447 latest_published_version: i64,
448 },
449 SetProtocol {
450 protocol: DeltaProtocol,
451 },
452 SetDomainMetadata {
453 updates: DeltaDomainMetadataUpdates,
454 },
455 RemoveDomainMetadata {
456 domains: Vec<String>,
457 },
458 SetPartitionColumns {
459 #[serde(rename = "partition-columns")]
460 partition_columns: Vec<String>,
461 },
462 UpdateMetadataSnapshotVersion {
463 #[serde(rename = "last-commit-version")]
464 last_commit_version: i64,
465 #[serde(rename = "last-commit-timestamp-ms")]
466 last_commit_timestamp_ms: i64,
467 },
468}
469
470#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
472pub struct DeltaUpdateTableRequest {
473 pub requirements: Vec<DeltaTableRequirement>,
474 pub updates: Vec<DeltaTableUpdate>,
475}
476
477#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
483#[serde(rename_all = "kebab-case")]
484pub struct DeltaFileSizeHistogram {
485 pub sorted_bin_boundaries: Vec<i64>,
486 pub file_counts: Vec<i64>,
487 pub total_bytes: Vec<i64>,
488 #[serde(skip_serializing_if = "Option::is_none")]
489 pub commit_version: Option<i64>,
490}
491
492#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
494#[serde(rename_all = "kebab-case")]
495pub struct DeltaCommitReport {
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub num_files_added: Option<i64>,
498 #[serde(skip_serializing_if = "Option::is_none")]
499 pub num_bytes_added: Option<i64>,
500 #[serde(skip_serializing_if = "Option::is_none")]
501 pub num_files_removed: Option<i64>,
502 #[serde(skip_serializing_if = "Option::is_none")]
503 pub num_bytes_removed: Option<i64>,
504 #[serde(skip_serializing_if = "Option::is_none")]
505 pub num_rows_inserted: Option<i64>,
506 #[serde(skip_serializing_if = "Option::is_none")]
507 pub num_rows_removed: Option<i64>,
508 #[serde(skip_serializing_if = "Option::is_none")]
509 pub num_rows_updated: Option<i64>,
510 #[serde(skip_serializing_if = "Option::is_none")]
511 pub file_size_histogram: Option<DeltaFileSizeHistogram>,
512}
513
514#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
516#[serde(rename_all = "kebab-case")]
517pub struct DeltaReport {
518 #[serde(skip_serializing_if = "Option::is_none")]
519 pub commit_report: Option<DeltaCommitReport>,
520}
521
522#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
524#[serde(rename_all = "kebab-case")]
525pub struct DeltaReportMetricsRequest {
526 pub table_id: String,
528 #[serde(skip_serializing_if = "Option::is_none")]
529 pub report: Option<DeltaReport>,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
539pub enum DeltaErrorType {
540 BadRequestException,
541 InvalidParameterValueException,
542 UnsupportedTableFormatException,
543 NotAuthorizedException,
544 PermissionDeniedException,
545 NotFoundException,
546 NoSuchCatalogException,
547 NoSuchSchemaException,
548 NoSuchTableException,
549 AlreadyExistsException,
550 CommitVersionConflictException,
551 UpdateRequirementConflictException,
552 ResourceExhaustedException,
553 TooManyRequestsException,
554 CommitStateUnknownException,
555 InternalServerErrorException,
556 NotImplementedException,
557}
558
559impl DeltaErrorType {
560 pub fn is_not_found(&self) -> bool {
562 matches!(
563 self,
564 DeltaErrorType::NotFoundException
565 | DeltaErrorType::NoSuchCatalogException
566 | DeltaErrorType::NoSuchSchemaException
567 | DeltaErrorType::NoSuchTableException
568 )
569 }
570
571 pub fn is_already_exists(&self) -> bool {
573 matches!(self, DeltaErrorType::AlreadyExistsException)
574 }
575
576 pub fn is_commit_conflict(&self) -> bool {
579 matches!(self, DeltaErrorType::CommitVersionConflictException)
580 }
581
582 pub fn is_update_requirement_conflict(&self) -> bool {
585 matches!(self, DeltaErrorType::UpdateRequirementConflictException)
586 }
587
588 pub fn is_resource_exhausted(&self) -> bool {
592 matches!(
593 self,
594 DeltaErrorType::ResourceExhaustedException | DeltaErrorType::TooManyRequestsException
595 )
596 }
597
598 pub fn is_commit_state_unknown(&self) -> bool {
601 matches!(self, DeltaErrorType::CommitStateUnknownException)
602 }
603
604 pub fn is_unsupported_table_format(&self) -> bool {
608 matches!(self, DeltaErrorType::UnsupportedTableFormatException)
609 }
610
611 pub fn is_not_implemented(&self) -> bool {
615 matches!(self, DeltaErrorType::NotImplementedException)
616 }
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
621pub struct DeltaErrorModel {
622 pub message: String,
623 #[serde(rename = "type")]
624 pub error_type: DeltaErrorType,
625 pub code: u16,
627 #[serde(skip_serializing_if = "Option::is_none")]
629 pub stack: Option<Vec<String>>,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct DeltaErrorResponse {
635 pub error: DeltaErrorModel,
636}
637
638#[cfg(test)]
639mod tests {
640 use super::*;
648 use serde_json::{Value, json};
649
650 fn round_trip<T>(value: Value)
653 where
654 T: Serialize + for<'de> Deserialize<'de>,
655 {
656 let parsed: T = serde_json::from_value(value.clone())
657 .unwrap_or_else(|e| panic!("deserialize failed: {e}\njson: {value:#}"));
658 let reserialized = serde_json::to_value(&parsed).expect("serialize");
659 assert_eq!(
660 reserialized, value,
661 "round-trip mismatch\n expected: {value:#}\n got: {reserialized:#}"
662 );
663 }
664
665 #[test]
666 fn staging_table_response() {
667 round_trip::<DeltaStagingTableResponse>(json!({
668 "table-id": "123e4567-e89b-12d3-a456-426614174000",
669 "table-type": "MANAGED",
670 "location": "s3://bucket/warehouse/catalog/schema/table",
671 "storage-credentials": [{
672 "prefix": "s3://bucket/warehouse/catalog/schema/table/",
673 "operation": "READ_WRITE",
674 "config": {
675 "s3.access-key-id": "AK...example",
676 "s3.secret-access-key": "ExampleKey",
677 "s3.session-token": "token"
678 },
679 "expiration-time-ms": 1234567890000_i64
680 }],
681 "required-protocol": {
682 "min-reader-version": 3,
683 "min-writer-version": 7,
684 "reader-features": ["deletionVectors", "vacuumProtocolCheck"],
685 "writer-features": ["catalogManaged", "deletionVectors"]
686 },
687 "suggested-protocol": {
688 "reader-features": ["typeWidening"],
689 "writer-features": ["domainMetadata", "rowTracking"]
690 },
691 "required-properties": { "delta.checkpointPolicy": "v2" },
692 "suggested-properties": {
693 "delta.rowTracking.materializedRowIdColumnName": null,
694 "delta.rowTracking.materializedRowCommitVersionColumnName": null
695 }
696 }));
697 }
698
699 #[test]
700 fn create_table_request_with_decimal_and_partition() {
701 round_trip::<DeltaCreateTableRequest>(json!({
702 "name": "sales",
703 "location": "s3://bucket/warehouse/catalog/schema/sales",
704 "table-type": "MANAGED",
705 "columns": {
706 "type": "struct",
707 "fields": [
708 { "name": "id", "type": "long", "nullable": false, "metadata": {} },
709 {
710 "name": "amount",
711 "type": { "type": "decimal", "precision": 10, "scale": 2 },
712 "nullable": true,
713 "metadata": {}
714 }
715 ]
716 },
717 "partition-columns": ["id"],
718 "protocol": {
719 "min-reader-version": 3,
720 "min-writer-version": 7,
721 "reader-features": ["deletionVectors"],
722 "writer-features": ["deletionVectors", "invariants"]
723 },
724 "properties": { "delta.enableDeletionVectors": "true" },
725 "last-commit-timestamp-ms": 1704067400000_i64
726 }));
727 }
728
729 #[test]
730 fn struct_type_carries_type_tag() {
731 round_trip::<DeltaStructType>(json!({
733 "type": "struct",
734 "fields": [
735 { "name": "id", "type": "long", "nullable": false, "metadata": {} },
736 { "name": "name", "type": "string", "nullable": true, "metadata": {} }
737 ]
738 }));
739 }
740
741 #[test]
742 fn nested_array_and_map_types() {
743 round_trip::<DeltaStructField>(json!({
744 "name": "tags",
745 "type": {
746 "type": "array",
747 "element-type": "string",
748 "contains-null": true
749 },
750 "nullable": true,
751 "metadata": {}
752 }));
753 round_trip::<DeltaStructField>(json!({
754 "name": "props",
755 "type": {
756 "type": "map",
757 "key-type": "string",
758 "value-type": "long",
759 "value-contains-null": false
760 },
761 "nullable": true,
762 "metadata": {}
763 }));
764 }
765
766 #[test]
767 fn load_table_response_with_commits() {
768 round_trip::<DeltaLoadTableResponse>(json!({
769 "metadata": {
770 "etag": "etag-1",
771 "table-type": "MANAGED",
772 "table-uuid": "123e4567-e89b-12d3-a456-426614174000",
773 "location": "s3://bucket/warehouse/catalog/schema/table",
774 "created-time": 1705600000000_i64,
775 "updated-time": 1705600000000_i64,
776 "columns": { "type": "struct", "fields": [] },
777 "properties": { "delta.checkpointPolicy": "v2" },
778 "last-commit-version": 0,
779 "last-commit-timestamp-ms": 1704067400000_i64
780 },
781 "commits": [{
782 "version": 1,
783 "timestamp": 1704067200000_i64,
784 "file-name": "00000000-0000-0000-0000-00000000002a.json",
785 "file-size": 2048,
786 "file-modification-timestamp": 1704067200000_i64
787 }],
788 "latest-table-version": 1
789 }));
790 }
791
792 #[test]
793 fn update_table_request_add_commit() {
794 round_trip::<DeltaUpdateTableRequest>(json!({
795 "requirements": [
796 { "type": "assert-table-uuid", "uuid": "123e4567-e89b-12d3-a456-426614174000" },
797 { "type": "assert-etag", "etag": "etag-1" }
798 ],
799 "updates": [
800 {
801 "action": "add-commit",
802 "commit": {
803 "version": 1,
804 "timestamp": 1704067200000_i64,
805 "file-name": "v.uuid1.json",
806 "file-size": 2048,
807 "file-modification-timestamp": 1704067200000_i64
808 }
809 },
810 { "action": "set-latest-backfilled-version", "latest-published-version": 0 }
811 ]
812 }));
813 }
814
815 #[test]
816 fn update_table_request_metadata_actions() {
817 round_trip::<DeltaUpdateTableRequest>(json!({
818 "requirements": [],
819 "updates": [
820 { "action": "set-properties", "updates": { "k": "v" } },
821 { "action": "remove-properties", "removals": ["old"] },
822 { "action": "set-table-comment", "comment": "hello" },
823 {
824 "action": "set-protocol",
825 "protocol": { "min-reader-version": 3, "min-writer-version": 7 }
826 },
827 { "action": "set-partition-columns", "partition-columns": ["id"] },
828 {
829 "action": "update-metadata-snapshot-version",
830 "last-commit-version": 5,
831 "last-commit-timestamp-ms": 1704067400000_i64
832 }
833 ]
834 }));
835 }
836
837 #[test]
838 fn update_table_domain_metadata_actions() {
839 round_trip::<DeltaUpdateTableRequest>(json!({
840 "requirements": [],
841 "updates": [
842 {
843 "action": "set-domain-metadata",
844 "updates": {
845 "delta.clustering": { "clusteringColumns": [["id"], ["address", "city"]] },
846 "delta.rowTracking": { "rowIdHighWaterMark": 42 }
847 }
848 },
849 { "action": "remove-domain-metadata", "domains": ["delta.clustering"] }
850 ]
851 }));
852 }
853
854 #[test]
855 fn credentials_response() {
856 round_trip::<DeltaCredentialsResponse>(json!({
857 "storage-credentials": [{
858 "prefix": "s3://bucket/path/",
859 "operation": "READ",
860 "config": { "s3.access-key-id": "AK", "s3.secret-access-key": "SK" },
861 "expiration-time-ms": 1234567890000_i64
862 }]
863 }));
864 }
865
866 #[test]
867 fn catalog_config() {
868 round_trip::<DeltaCatalogConfig>(json!({
869 "endpoints": ["GET /v1/temporary-path-credentials"],
870 "protocol-version": "1.0"
871 }));
872 }
873
874 #[test]
875 fn report_metrics_request() {
876 round_trip::<DeltaReportMetricsRequest>(json!({
877 "table-id": "123e4567-e89b-12d3-a456-426614174000",
878 "report": {
879 "commit-report": {
880 "num-files-added": 10,
881 "num-bytes-added": 104857600_i64,
882 "file-size-histogram": {
883 "sorted-bin-boundaries": [0, 1024, 2048],
884 "file-counts": [100, 40, 5],
885 "total-bytes": [104857600_i64, 167772160_i64, 83886080_i64],
886 "commit-version": 6
887 }
888 }
889 }
890 }));
891 }
892
893 #[test]
894 fn error_response_round_trips() {
895 round_trip::<DeltaErrorResponse>(json!({
896 "error": {
897 "message": "table not found",
898 "type": "NoSuchTableException",
899 "code": 404
900 }
901 }));
902 }
903
904 #[test]
905 fn error_response_with_stack_round_trips() {
906 round_trip::<DeltaErrorResponse>(json!({
907 "error": {
908 "message": "boom",
909 "type": "InternalServerErrorException",
910 "code": 500,
911 "stack": ["frame one", "frame two"]
912 }
913 }));
914 }
915
916 #[test]
917 fn uniform_metadata_round_trips() {
918 round_trip::<DeltaUniformMetadata>(json!({
919 "iceberg": {
920 "metadata-location": "s3://bucket/metadata/v1.json",
921 "converted-delta-version": 42,
922 "converted-delta-timestamp": 1704067200000_i64
923 }
924 }));
925 }
926}