Skip to main content

deltalake_catalog_unity/
models.rs

1//! Api models for databricks unity catalog APIs
2use chrono::serde::*;
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7use std::sync::Once;
8
9/// Error response from unity API
10#[derive(Deserialize, Debug)]
11pub struct ErrorResponse {
12    /// The error code
13    pub error_code: String,
14    /// The error message
15    pub message: String,
16    #[serde(default)]
17    pub details: Vec<ErrorDetails>,
18}
19
20#[derive(Deserialize, Debug)]
21pub struct TokenErrorResponse {
22    /// The error code
23    pub error: String,
24    /// The error message
25    pub error_id: String,
26    /// The details of the error that happens
27    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/// List catalogs response
60#[derive(Deserialize, Debug)]
61#[serde(untagged)]
62pub enum ListCatalogsResponse {
63    /// Successful response
64    Success {
65        /// The schemas within the parent catalog
66        catalogs: Vec<Catalog>,
67        next_page_token: Option<String>,
68    },
69    /// Error response
70    Error(ErrorResponse),
71}
72
73/// List schemas response
74#[derive(Deserialize, Debug)]
75#[serde(untagged)]
76pub enum ListSchemasResponse {
77    /// Successful response
78    Success {
79        /// The schemas within the parent catalog
80        schemas: Vec<Schema>,
81    },
82    /// Error response
83    Error(ErrorResponse),
84}
85
86/// Get table response
87#[derive(Deserialize, Debug)]
88#[serde(untagged)]
89pub enum GetTableResponse {
90    /// Successful response
91    Success(Table),
92    /// Error response
93    Error(ErrorResponse),
94}
95
96/// List schemas response
97#[derive(Deserialize, Debug)]
98#[serde(untagged)]
99pub enum GetSchemaResponse {
100    /// Successful response
101    Success(Box<Schema>),
102    /// Error response
103    Error(ErrorResponse),
104}
105
106/// List table summaries response
107#[derive(Deserialize, Debug)]
108#[serde(untagged)]
109pub enum ListTableSummariesResponse {
110    /// Successful response
111    Success {
112        /// Basic table infos
113        #[serde(default)]
114        tables: Vec<TableSummary>,
115        /// Continuation token
116        next_page_token: Option<String>,
117    },
118    /// Error response
119    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)]
132/// Whether the current securable is accessible from all workspaces or a specific set of workspaces.
133pub 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)]
143/// The type of the catalog.
144pub enum CatalogType {
145    #[default]
146    Undefined,
147    ManagedCatalog,
148    DeltasharingCatalog,
149    SystemCatalog,
150}
151
152/// A catalog within a metastore
153#[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/// A schema within a catalog
209#[derive(Deserialize, Default, Debug)]
210pub struct Schema {
211    /// Username of schema creator.
212    #[serde(default)]
213    pub created_by: String,
214
215    /// Name of schema, relative to parent catalog.
216    pub name: String,
217
218    /// Username of user who last modified schema.
219    #[serde(default)]
220    pub updated_by: String,
221
222    /// Full name of schema, in form of catalog_name.schema_name.
223    pub full_name: String,
224
225    /// The type of the parent catalog.
226    pub catalog_type: String,
227
228    /// Name of parent catalog.
229    pub catalog_name: String,
230
231    /// Storage root URL for managed tables within schema.
232    #[serde(default)]
233    pub storage_root: String,
234
235    /// Storage location for managed tables within schema.
236    #[serde(default)]
237    pub storage_location: String,
238
239    /// A map of key-value properties attached to the securable.
240    #[serde(default)]
241    pub properties: HashMap<String, String>,
242
243    /// User-provided free-form text description.
244    #[serde(default)]
245    pub comment: String,
246
247    /// Time at which this schema was created, in epoch milliseconds.
248    #[serde(default)]
249    pub created_at: i64,
250
251    /// Username of current owner of schema.
252    #[serde(default)]
253    pub owner: String,
254
255    /// Time at which this schema was created, in epoch milliseconds.
256    #[serde(default)]
257    pub updated_at: i64,
258
259    /// Unique identifier of parent metastore.
260    pub metastore_id: String,
261}
262
263#[derive(Deserialize, Default, Debug)]
264#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
265#[allow(missing_docs)]
266/// Possible data source formats for unity tables
267#[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/// Possible data source formats for unity tables
300#[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)]
312/// Summary of the table
313pub struct TableSummary {
314    /// The full name of the table.
315    pub full_name: String,
316    /// Type of table
317    pub table_type: TableType,
318}
319
320/// A table within a schema
321#[derive(Clone, Debug, PartialEq, Default, Deserialize)]
322pub struct Table {
323    pub name: String,
324    /// Name of parent catalog.
325    pub catalog_name: String,
326    /// Name of parent schema relative to its parent catalog.
327    pub schema_name: String,
328    pub table_type: TableType,
329    pub data_source_format: DataSourceFormat,
330    /// The array of __ColumnInfo__ definitions of the table's columns.
331    pub columns: Vec<ColumnInfo>,
332    /// Storage root URL for table (for **MANAGED**, **EXTERNAL** tables)
333    pub storage_location: String,
334    /// User-provided free-form text description.
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub comment: Option<String>,
337    /// A map of key-value properties attached to the securable.
338    #[serde(skip_serializing_if = "HashMap::is_empty")]
339    pub properties: HashMap<String, String>,
340    /// Time at which this table was created, in epoch milliseconds.
341    #[serde(with = "ts_milliseconds")]
342    pub created_at: DateTime<Utc>,
343    /// Time at which this table was last modified, in epoch milliseconds.
344    #[serde(with = "ts_milliseconds")]
345    pub updated_at: DateTime<Utc>,
346    /// Unique identifier for the table.
347    pub table_id: String,
348}
349
350#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)]
351pub struct ColumnInfo {
352    /// Name of Column.
353    pub name: String,
354    /// Full data type specification as SQL/catalogString text.
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub type_text: Option<String>,
357    /// Full data type specification, JSON-serialized.
358    #[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    /// Digits of precision; required for DecimalTypes.
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub type_precision: Option<i32>,
365    /// Digits to right of decimal; Required for DecimalTypes.
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub type_scale: Option<i32>,
368    /// Format of IntervalType.
369    #[serde(skip_serializing_if = "Option::is_none")]
370    pub type_interval_type: Option<String>,
371    /// Ordinal position of column (starting at position 0).
372    pub position: u32,
373    /// User-provided free-form text description.
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub comment: Option<String>,
376    /// Whether field may be Null.
377    pub nullable: bool,
378    /// Partition index for column.
379    #[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}