1use chrono::serde::*;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7use std::sync::Once;
8
9#[derive(Deserialize, Debug)]
11pub struct ErrorResponse {
12 pub error_code: String,
14 pub message: String,
16 #[serde(default)]
17 pub details: Vec<ErrorDetails>,
18}
19
20#[derive(Deserialize, Debug)]
21pub struct TokenErrorResponse {
22 pub error: String,
24 pub error_id: String,
26 pub error_description: String,
28}
29
30impl fmt::Display for TokenErrorResponse {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(
33 f,
34 "{}: [{}] {}",
35 self.error, self.error_id, self.error_description
36 )
37 }
38}
39
40#[derive(Deserialize, Default, Debug)]
41#[serde(default)]
42pub struct ErrorDetails {
43 #[serde(rename = "@type")]
44 tpe: String,
45 reason: String,
46 domain: String,
47 metadata: HashMap<String, String>,
48 request_id: String,
49 serving_data: String,
50}
51
52impl fmt::Display for ErrorResponse {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 writeln!(f, "[{}] {}", self.error_code, self.message)
55 }
56}
57impl std::error::Error for ErrorResponse {}
58
59#[derive(Deserialize, Debug)]
61#[serde(untagged)]
62pub enum ListCatalogsResponse {
63 Success {
65 catalogs: Vec<Catalog>,
67 next_page_token: Option<String>,
68 },
69 Error(ErrorResponse),
71}
72
73#[derive(Deserialize, Debug)]
75#[serde(untagged)]
76pub enum ListSchemasResponse {
77 Success {
79 schemas: Vec<Schema>,
81 },
82 Error(ErrorResponse),
84}
85
86#[derive(Deserialize, Debug)]
88#[serde(untagged)]
89pub enum GetTableResponse {
90 Success(Table),
92 Error(ErrorResponse),
94}
95
96#[derive(Deserialize, Debug)]
98#[serde(untagged)]
99pub enum GetSchemaResponse {
100 Success(Box<Schema>),
102 Error(ErrorResponse),
104}
105
106#[derive(Deserialize, Debug)]
108#[serde(untagged)]
109pub enum ListTableSummariesResponse {
110 Success {
112 #[serde(default)]
114 tables: Vec<TableSummary>,
115 next_page_token: Option<String>,
117 },
118 Error(ErrorResponse),
120}
121
122#[derive(Deserialize, Debug)]
123#[serde(untagged)]
124pub enum TableTempCredentialsResponse {
125 Success(TemporaryTableCredentials),
126 Error(ErrorResponse),
127}
128
129#[derive(Deserialize, Default, Debug)]
130#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
131#[allow(missing_docs)]
132pub enum IsolationMode {
134 #[default]
135 Undefined,
136 Open,
137 Isolated,
138}
139
140#[derive(Deserialize, Default, Debug)]
141#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
142#[allow(missing_docs)]
143pub enum CatalogType {
145 #[default]
146 Undefined,
147 ManagedCatalog,
148 DeltasharingCatalog,
149 SystemCatalog,
150}
151
152#[derive(Deserialize, Default, Debug)]
154#[serde(default)]
155pub struct Catalog {
156 pub created_by: String,
157 pub name: String,
158 pub updated_by: String,
159 pub isolation_mode: IsolationMode,
160 pub catalog_type: CatalogType,
161 pub storage_root: String,
162 pub provider_name: String,
163 pub storage_location: String,
164 pub properties: HashMap<String, String>,
165 pub share_name: String,
166 pub comment: String,
167 pub created_at: i64,
168 pub owner: String,
169 pub updated_at: i64,
170 pub metastore_id: String,
171 pub enabled_predictive_optimization: String,
172 pub effective_predictive_optimization_flag: EffectivePredictiveOptimizationFlag,
173 pub connection_name: String,
174 pub full_name: String,
175 pub options: HashMap<String, String>,
176 pub securable_type: String,
177 pub provisioning_info: ProvisioningInfo,
178 pub browse_only: Option<bool>,
179 pub accessible_in_current_workspace: bool,
180 pub id: String,
181 pub securable_kind: String,
182 pub delta_sharing_valid_through_timestamp: u64,
183}
184
185#[allow(unused)]
186#[derive(Deserialize, Default, Debug)]
187pub struct ProvisioningInfo {
188 state: ProvisioningState,
189}
190
191#[derive(Deserialize, Debug, Default)]
192pub enum ProvisioningState {
193 #[default]
194 Provisioning,
195 Active,
196 Failed,
197 Deleting,
198 Updating,
199}
200
201#[derive(Deserialize, Default, Debug)]
202pub struct EffectivePredictiveOptimizationFlag {
203 pub value: String,
204 pub inherited_from_type: String,
205 pub inherited_from_name: String,
206}
207
208#[derive(Deserialize, Default, Debug)]
210pub struct Schema {
211 #[serde(default)]
213 pub created_by: String,
214
215 pub name: String,
217
218 #[serde(default)]
220 pub updated_by: String,
221
222 pub full_name: String,
224
225 pub catalog_type: String,
227
228 pub catalog_name: String,
230
231 #[serde(default)]
233 pub storage_root: String,
234
235 #[serde(default)]
237 pub storage_location: String,
238
239 #[serde(default)]
241 pub properties: HashMap<String, String>,
242
243 #[serde(default)]
245 pub comment: String,
246
247 #[serde(default)]
249 pub created_at: i64,
250
251 #[serde(default)]
253 pub owner: String,
254
255 #[serde(default)]
257 pub updated_at: i64,
258
259 pub metastore_id: String,
261}
262
263#[derive(Deserialize, Default, Debug)]
264#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
265#[allow(missing_docs)]
266#[derive(Clone, PartialEq)]
268pub enum DataSourceFormat {
269 #[default]
270 Undefined,
271 Delta,
272 Csv,
273 Json,
274 Avro,
275 Parquet,
276 Orc,
277 Text,
278 UnityCatalog,
279 Deltasharing,
280 DatabricksFormat,
281 MySQLFormat,
282 PostgreSQLFormat,
283 RedshiftFormat,
284 SnowflakeFormat,
285 SQLDWFormat,
286 SQLServerFormat,
287 SalesForceFormat,
288 BigQueryFormat,
289 NetSuiteFormat,
290 WorkdayRAASFormat,
291 HiveSerde,
292 HiveCustom,
293 VectorIndexFormat,
294}
295
296#[derive(Deserialize, Default, Debug)]
297#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
298#[allow(missing_docs)]
299#[derive(PartialEq, Clone)]
301pub enum TableType {
302 #[default]
303 Undefined,
304 Managed,
305 External,
306 View,
307 MaterializedView,
308 StreamingTable,
309}
310
311#[derive(Deserialize, Debug)]
312pub struct TableSummary {
314 pub full_name: String,
316 pub table_type: TableType,
318}
319
320#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
322pub struct Table {
323 pub name: String,
324 pub catalog_name: String,
326 pub schema_name: String,
328 pub table_type: TableType,
329 pub data_source_format: DataSourceFormat,
330 pub columns: Vec<ColumnInfo>,
332 pub storage_location: String,
334 #[serde(skip_serializing_if = "Option::is_none")]
336 pub comment: Option<String>,
337 #[serde(skip_serializing_if = "HashMap::is_empty")]
339 pub properties: HashMap<String, String>,
340 #[serde(with = "ts_milliseconds")]
342 pub created_at: DateTime<Utc>,
343 #[serde(with = "ts_milliseconds")]
345 pub updated_at: DateTime<Utc>,
346 pub table_id: String,
348}
349
350#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
351pub struct ColumnInfo {
352 pub name: String,
354 #[serde(skip_serializing_if = "Option::is_none")]
356 pub type_text: Option<String>,
357 #[serde(skip_serializing_if = "Option::is_none")]
359 pub type_json: Option<String>,
360 #[serde(skip_serializing_if = "Option::is_none")]
361 pub type_name: Option<ColumnTypeName>,
362 #[serde(skip_serializing_if = "Option::is_none")]
364 pub type_precision: Option<i32>,
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub type_scale: Option<i32>,
368 #[serde(skip_serializing_if = "Option::is_none")]
370 pub type_interval_type: Option<String>,
371 pub position: u32,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub comment: Option<String>,
376 pub nullable: bool,
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub partition_index: Option<i32>,
381}
382
383#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
384#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
385pub enum ColumnTypeName {
386 Boolean,
387 Byte,
388 Short,
389 Int,
390 Long,
391 Float,
392 Double,
393 Date,
394 Timestamp,
395 TimestampNtz,
396 String,
397 Binary,
398 Decimal,
399 Interval,
400 Array,
401 Struct,
402 Map,
403 Char,
404 Null,
405 UserDefinedType,
406 TableType,
407}
408
409#[derive(Deserialize, Default, Debug)]
410#[serde(default)]
411pub struct DeltaRuntimeProperties {
412 pub delta_runtime_properties: HashMap<String, String>,
413}
414
415#[derive(Deserialize, Debug, Clone)]
416pub struct TemporaryTableCredentials {
417 pub aws_temp_credentials: Option<AwsTempCredentials>,
418 pub azure_user_delegation_sas: Option<AzureUserDelegationSas>,
419 pub gcp_oauth_token: Option<GcpOauthToken>,
420 pub r2_temp_credentials: Option<R2TempCredentials>,
421 #[serde(with = "chrono::serde::ts_milliseconds")]
422 pub expiration_time: DateTime<Utc>,
423 pub url: String,
424}
425
426#[cfg(any(feature = "aws", feature = "r2"))]
427static INIT_AWS: Once = Once::new();
428#[cfg(feature = "azure")]
429static INIT_AZURE: Once = Once::new();
430#[cfg(feature = "gcp")]
431static INIT_GCP: Once = Once::new();
432
433impl TemporaryTableCredentials {
434 #[cfg(feature = "aws")]
435 pub fn get_aws_credentials(&self) -> Option<HashMap<String, String>> {
436 INIT_AWS.call_once(|| deltalake_aws::register_handlers(None));
437 self.aws_temp_credentials.clone().map(Into::into)
438 }
439
440 #[cfg(not(feature = "aws"))]
441 pub fn get_aws_credentials(&self) -> Option<HashMap<String, String>> {
442 tracing::warn!("AWS Credentials found, but the feature is not enabled.");
443 None
444 }
445
446 #[cfg(feature = "azure")]
447 pub fn get_azure_credentials(&self) -> Option<HashMap<String, String>> {
448 INIT_AZURE.call_once(|| deltalake_azure::register_handlers(None));
449 self.azure_user_delegation_sas.clone().map(Into::into)
450 }
451
452 #[cfg(not(feature = "azure"))]
453 pub fn get_azure_credentials(&self) -> Option<HashMap<String, String>> {
454 tracing::warn!("Azure credentials found, but the feature is not enabled.");
455 None
456 }
457
458 #[cfg(feature = "gcp")]
459 pub fn get_gcp_credentials(&self) -> Option<HashMap<String, String>> {
460 INIT_GCP.call_once(|| deltalake_gcp::register_handlers(None));
461 self.gcp_oauth_token.clone().map(Into::into)
462 }
463
464 #[cfg(not(feature = "gcp"))]
465 pub fn get_gcp_credentials(&self) -> Option<HashMap<String, String>> {
466 tracing::warn!("GCP credentials found, but the feature is not enabled.");
467 None
468 }
469
470 #[cfg(feature = "r2")]
471 pub fn get_r2_credentials(&self) -> Option<HashMap<String, String>> {
472 INIT_AWS.call_once(|| deltalake_aws::register_handlers(None));
473 self.r2_temp_credentials.clone().map(Into::into)
474 }
475
476 #[cfg(not(feature = "r2"))]
477 pub fn get_r2_credentials(&self) -> Option<HashMap<String, String>> {
478 tracing::warn!("r2 credentials found, but feature is not enabled.");
479 None
480 }
481
482 pub fn get_credentials(self) -> Option<HashMap<String, String>> {
483 self.get_aws_credentials()
484 .or(self.get_azure_credentials())
485 .or(self.get_gcp_credentials())
486 .or(self.get_r2_credentials())
487 }
488}
489
490#[derive(Deserialize, Debug, Clone)]
491pub struct AwsTempCredentials {
492 pub access_key_id: String,
493 pub secret_access_key: String,
494 pub session_token: Option<String>,
495 pub access_point: Option<String>,
496}
497
498#[cfg(feature = "aws")]
499impl From<AwsTempCredentials> for HashMap<String, String> {
500 fn from(value: AwsTempCredentials) -> Self {
501 let mut result = HashMap::from_iter([
502 (
503 deltalake_aws::constants::AWS_ACCESS_KEY_ID.to_string(),
504 value.access_key_id,
505 ),
506 (
507 deltalake_aws::constants::AWS_SECRET_ACCESS_KEY.to_string(),
508 value.secret_access_key,
509 ),
510 ]);
511 if let Some(st) = value.session_token {
512 result.insert(deltalake_aws::constants::AWS_SESSION_TOKEN.to_string(), st);
513 }
514 if let Some(ap) = value.access_point {
515 result.insert(deltalake_aws::constants::AWS_ENDPOINT_URL.to_string(), ap);
516 }
517 result
518 }
519}
520
521#[cfg(feature = "azure")]
522impl From<AzureUserDelegationSas> for HashMap<String, String> {
523 fn from(value: AzureUserDelegationSas) -> Self {
524 HashMap::from_iter([("azure_storage_sas_key".to_string(), value.sas_token)])
525 }
526}
527
528#[cfg(feature = "gcp")]
529impl From<GcpOauthToken> for HashMap<String, String> {
530 fn from(value: GcpOauthToken) -> Self {
531 HashMap::from_iter([(
532 "google_application_credentials".to_string(),
533 value.oauth_token,
534 )])
535 }
536}
537
538#[cfg(feature = "r2")]
539impl From<R2TempCredentials> for HashMap<String, String> {
540 fn from(value: R2TempCredentials) -> Self {
541 HashMap::from_iter([
542 (
543 deltalake_aws::constants::AWS_ACCESS_KEY_ID.to_string(),
544 value.access_key_id,
545 ),
546 (
547 deltalake_aws::constants::AWS_SECRET_ACCESS_KEY.to_string(),
548 value.secret_access_key,
549 ),
550 (
551 deltalake_aws::constants::AWS_SESSION_TOKEN.to_string(),
552 value.session_token,
553 ),
554 ])
555 }
556}
557
558#[derive(Deserialize, Debug, Clone)]
559pub struct AzureUserDelegationSas {
560 pub sas_token: String,
561}
562
563#[derive(Deserialize, Debug, Clone)]
564pub struct GcpOauthToken {
565 pub oauth_token: String,
566}
567
568#[derive(Deserialize, Debug, Clone)]
569pub struct R2TempCredentials {
570 pub access_key_id: String,
571 pub secret_access_key: String,
572 pub session_token: String,
573}
574
575#[derive(Serialize, Debug, Clone)]
576pub struct TemporaryTableCredentialsRequest {
577 pub table_id: String,
578 pub operation: String,
579}
580
581impl TemporaryTableCredentialsRequest {
582 pub fn new(table_id: &str, operation: &str) -> Self {
583 Self {
584 table_id: table_id.to_string(),
585 operation: operation.to_string(),
586 }
587 }
588}
589
590#[cfg(test)]
591pub(crate) mod tests {
592 use super::*;
593
594 pub(crate) const ERROR_RESPONSE: &str = r#"
595 {
596 "error_code": "404",
597 "message": "error message",
598 "details": []
599 }
600 "#;
601
602 pub(crate) const LIST_SCHEMAS_RESPONSE: &str = r#"
603 {
604 "schemas": [
605 {
606 "created_by": "string",
607 "name": "string",
608 "updated_by": "string",
609 "full_name": "string",
610 "catalog_type": "string",
611 "catalog_name": "string",
612 "schema_name": "string",
613 "storage_root": "string",
614 "storage_location": "string",
615 "properties": {
616 "property1": "string",
617 "property2": "string"
618 },
619 "comment": "string",
620 "created_at": 0,
621 "owner": "string",
622 "updated_at": 0,
623 "metastore_id": "string",
624 "table_id": "string"
625 }
626 ]
627 }"#;
628
629 pub(crate) const GET_SCHEMA_RESPONSE: &str = r#"
630 {
631 "created_by": "string",
632 "name": "schema_name",
633 "updated_by": "string",
634 "full_name": "catalog_name.schema_name",
635 "catalog_type": "string",
636 "catalog_name": "catalog_name",
637 "storage_root": "string",
638 "storage_location": "string",
639 "properties": {
640 "property1": "string",
641 "property2": "string"
642 },
643 "comment": "string",
644 "created_at": 0,
645 "owner": "string",
646 "updated_at": 0,
647 "metastore_id": "string"
648 }"#;
649
650 pub(crate) const GET_TABLE_RESPONSE: &str = r#"
651 {
652 "name": "string",
653 "catalog_name": "string",
654 "schema_name": "string",
655 "table_type": "MANAGED",
656 "data_source_format": "DELTA",
657 "columns": [
658 {
659 "name": "string",
660 "type_text": "string",
661 "type_name": "BOOLEAN",
662 "position": 0,
663 "type_precision": 0,
664 "type_scale": 0,
665 "type_interval_type": "string",
666 "type_json": "string",
667 "comment": "string",
668 "nullable": true,
669 "partition_index": 0,
670 "mask": {
671 "function_name": "string",
672 "using_column_names": [
673 "string"
674 ]
675 }
676 }
677 ],
678 "storage_location": "string",
679 "view_definition": "string",
680 "view_dependencies": {
681 "dependencies": [
682 {
683 "table": {
684 "table_full_name": "string"
685 },
686 "function": {
687 "function_full_name": "string"
688 }
689 }
690 ]
691 },
692 "sql_path": "string",
693 "owner": "string",
694 "comment": "string",
695 "properties": {
696 "property1": "string",
697 "property2": "string"
698 },
699 "storage_credential_name": "string",
700 "table_constraints": [
701 {
702 "primary_key_constraint": {
703 "name": "string",
704 "child_columns": [
705 "string"
706 ]
707 },
708 "foreign_key_constraint": {
709 "name": "string",
710 "child_columns": [
711 "string"
712 ],
713 "parent_table": "string",
714 "parent_columns": [
715 "string"
716 ]
717 },
718 "named_table_constraint": {
719 "name": "string"
720 }
721 }
722 ],
723 "row_filter": {
724 "function_name": "string",
725 "input_column_names": [
726 "string"
727 ]
728 },
729 "enable_predictive_optimization": "DISABLE",
730 "metastore_id": "string",
731 "full_name": "string",
732 "data_access_configuration_id": "string",
733 "created_at": 0,
734 "created_by": "string",
735 "updated_at": 0,
736 "updated_by": "string",
737 "deleted_at": 0,
738 "table_id": "string",
739 "delta_runtime_properties_kvpairs": {
740 "delta_runtime_properties": {
741 "property1": "string",
742 "property2": "string"
743 }
744 },
745 "effective_predictive_optimization_flag": {
746 "value": "DISABLE",
747 "inherited_from_type": "CATALOG",
748 "inherited_from_name": "string"
749 },
750 "access_point": "string",
751 "pipeline_id": "string",
752 "browse_only": true
753 }
754 "#;
755
756 pub(crate) const LIST_TABLES: &str = r#"
757 {
758 "tables": [{
759 "full_name": "catalog.schema.table_name",
760 "table_type": "MANAGED"
761 }]
762 }
763 "#;
764 pub(crate) const LIST_TABLES_EMPTY: &str = "{}";
765
766 #[test]
767 fn test_responses() {
768 let list_schemas: Result<ListSchemasResponse, _> =
769 serde_json::from_str(LIST_SCHEMAS_RESPONSE);
770 assert!(list_schemas.is_ok());
771 assert!(matches!(
772 list_schemas.unwrap(),
773 ListSchemasResponse::Success { .. }
774 ));
775
776 let get_table: Result<GetTableResponse, _> = serde_json::from_str(GET_TABLE_RESPONSE);
777 assert!(get_table.is_ok());
778 assert!(matches!(
779 get_table.unwrap(),
780 GetTableResponse::Success { .. }
781 ));
782
783 let list_tables: Result<ListTableSummariesResponse, _> = serde_json::from_str(LIST_TABLES);
784 assert!(list_tables.is_ok());
785 assert!(matches!(
786 list_tables.unwrap(),
787 ListTableSummariesResponse::Success { .. }
788 ));
789
790 let list_tables: Result<ListTableSummariesResponse, _> =
791 serde_json::from_str(LIST_TABLES_EMPTY);
792 assert!(list_tables.is_ok());
793 assert!(matches!(
794 list_tables.unwrap(),
795 ListTableSummariesResponse::Success { .. }
796 ));
797
798 let get_schema: Result<GetSchemaResponse, _> = serde_json::from_str(GET_SCHEMA_RESPONSE);
799 assert!(get_schema.is_ok());
800 assert!(matches!(get_schema.unwrap(), GetSchemaResponse::Success(_)))
801 }
802
803 #[test]
804 fn test_response_errors() {
805 let list_schemas: Result<ListSchemasResponse, _> = serde_json::from_str(ERROR_RESPONSE);
806 assert!(list_schemas.is_ok());
807 assert!(matches!(
808 list_schemas.unwrap(),
809 ListSchemasResponse::Error(_)
810 ));
811
812 let get_table: Result<GetTableResponse, _> = serde_json::from_str(ERROR_RESPONSE);
813 assert!(get_table.is_ok());
814 dbg!(&get_table);
815 assert!(matches!(get_table.unwrap(), GetTableResponse::Error(_)))
816 }
817}