weaviate_community/
schema.rs

1use crate::collections::error::SchemaError;
2use crate::collections::schema::{
3    Class, Classes, Property, Shard, ShardStatus, Shards, Tenant, Tenants,
4};
5use reqwest::Url;
6use std::error::Error;
7use std::sync::Arc;
8
9/// All schema related endpoints and functionality described in
10/// [Weaviate schema API documentation](https://weaviate.io/developers/weaviate/api/rest/schema)
11#[derive(Debug)]
12pub struct Schema {
13    endpoint: Url,
14    client: Arc<reqwest::Client>,
15}
16
17impl Schema {
18    /// Create a new Schema object. The schema object is intended to like inside the WeaviateClient
19    /// and be called through the WeaviateClient.
20    pub(super) fn new(url: &Url, client: Arc<reqwest::Client>) -> Result<Self, Box<dyn Error>> {
21        let endpoint = url.join("/v1/schema/")?;
22        Ok(Schema { endpoint, client })
23    }
24
25    /// Facilitates the retrieval of the configuration for a single class in the schema.
26    ///
27    /// GET /v1/schema/{class_name}
28    /// ```no_run
29    /// use weaviate_community::WeaviateClient;
30    ///
31    /// #[tokio::main]
32    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
33    ///     let client = WeaviateClient::builder("http://localhost:8080").build()?;
34    ///     let response = client.schema.get_class("Library").await;
35    ///     assert!(response.is_err());
36    ///     Ok(())
37    /// }
38    /// ```
39    pub async fn get_class(&self, class_name: &str) -> Result<Class, Box<dyn Error>> {
40        let endpoint = self.endpoint.join(class_name)?;
41        let res = self.client.get(endpoint).send().await?;
42
43        match res.status() {
44            reqwest::StatusCode::OK => {
45                let res: Class = res.json().await?;
46                Ok(res)
47            },
48            _ => Err(self.get_err_msg("get class", res).await),
49        }
50    }
51
52    /// Facilitates the retrieval of the full Weaviate schema.
53    ///
54    /// GET /v1/schema
55    /// ```no_run
56    /// use weaviate_community::WeaviateClient;
57    ///
58    /// #[tokio::main]
59    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
60    ///     let client = WeaviateClient::builder("http://localhost:8080").build()?;
61    ///     let schema = client.schema.get().await?;
62    ///     println!("{:#?}", &schema);
63    ///     Ok(())
64    /// }
65    /// ```
66    pub async fn get(&self) -> Result<Classes, Box<dyn Error>> {
67        let res = self.client.get(self.endpoint.clone()).send().await?;
68        match res.status() {
69            reqwest::StatusCode::OK => {
70                let res: Classes = res.json().await?;
71                Ok(res)
72            }
73            _ => Err(self.get_err_msg("get schema", res).await),
74        }
75    }
76
77    /// Create a new data object class in the schema.
78    ///
79    /// Note that from 1.5.0, creating a schema is optional, as Auto Schema is available. See for
80    /// more info:
81    /// [Weaviate auto-schema documentation](https://weaviate.io/developers/weaviate/config-refs/schema#auto-schema)
82    ///
83    /// POST /v1/schema
84    /// ```no_run
85    /// use weaviate_community::WeaviateClient;
86    /// use weaviate_community::collections::schema::Class;
87    ///
88    /// #[tokio::main]
89    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
90    ///     let class = Class::builder("Library").build();
91    ///     let client = WeaviateClient::builder("http://localhost:8080").build()?;
92    ///     let res = client.schema.create_class(&class).await?;
93    ///
94    ///     Ok(())
95    /// }
96    /// ```
97    pub async fn create_class(&self, class: &Class) -> Result<Class, Box<dyn Error>> {
98        let payload = serde_json::to_value(&class).unwrap();
99        let res = self
100            .client
101            .post(self.endpoint.clone())
102            .json(&payload)
103            .send()
104            .await?;
105        match res.status() {
106            reqwest::StatusCode::OK => {
107                let res: Class = res.json().await?;
108                Ok(res)
109            }
110            _ => Err(self.get_err_msg("create class", res).await),
111        }
112    }
113
114    ///
115    /// Remove a class (and all data in the instances) from the schema.
116    ///
117    /// DELETE v1/schema/{class_name}
118    /// ```no_run
119    /// use weaviate_community::WeaviateClient;
120    ///
121    /// #[tokio::main]
122    /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
123    ///     let client = WeaviateClient::builder("http://localhost:8080").build()?;
124    ///     let response = client.schema.delete("Library").await;
125    ///
126    ///     Ok(())
127    /// }
128    /// ```
129    ///
130    pub async fn delete(&self, class_name: &str) -> Result<bool, Box<dyn Error>> {
131        let endpoint = self.endpoint.join(class_name)?;
132        let res = self.client.delete(endpoint).send().await?;
133        match res.status() {
134            reqwest::StatusCode::OK => Ok(true),
135            _ => Err(self.get_err_msg("delete class", res).await),
136        }
137    }
138
139    /// Update settings of an existing schema class.
140    ///
141    /// Use this endpoint to alter an existing class in the schema. Note that not all settings are
142    /// mutable. If an error about immutable fields is returned and you still need to update this
143    /// particular setting, you will have to delete the class (and the underlying data) and
144    /// recreate. This endpoint cannot be used to modify properties.
145    //  Instead, use POST /v1/schema/{ClassName}/properties (add_property method).
146    //
147    /// A typical use case for this endpoint is to update configuration, such as the
148    /// vectorIndexConfig. Note that even in mutable sections, such as vectorIndexConfig,
149    /// some fields may be immutable.
150    ///
151    /// You should attach a body to this PUT request with the entire new configuration of the class
152    pub async fn update(&self, class: &Class) -> Result<Class, Box<dyn Error>> {
153        let endpoint = self.endpoint.join(&class.class)?;
154        let payload = serde_json::to_value(&class)?;
155        let res = self.client.put(endpoint).json(&payload).send().await?;
156        match res.status() {
157            reqwest::StatusCode::OK => {
158                let res: Class = res.json().await?;
159                Ok(res)
160            }
161            _ => Err(self.get_err_msg("update class", res).await),
162        }
163    }
164
165    ///
166    /// Add a property to an existing class in the schema.
167    ///
168    pub async fn add_property(
169        &self,
170        class_name: &str,
171        property: &Property,
172    ) -> Result<Property, Box<dyn Error>> {
173        let mut endpoint = class_name.to_string();
174        endpoint.push_str("/properties");
175        let endpoint = self.endpoint.join(&endpoint)?;
176        let payload = serde_json::to_value(&property)?;
177        let res = self.client.post(endpoint).json(&payload).send().await?;
178        match res.status() {
179            reqwest::StatusCode::OK => {
180                let res: Property = res.json().await?;
181                Ok(res)
182            }
183            _ => Err(self.get_err_msg("add property", res).await),
184        }
185    }
186
187    ///
188    /// View all of the shards for a particular class.
189    ///
190    pub async fn get_shards(&self, class_name: &str) -> Result<Shards, Box<dyn Error>> {
191        let mut endpoint = class_name.to_string();
192        endpoint.push_str("/shards");
193        let endpoint = self.endpoint.join(&endpoint)?;
194        let res = self.client.get(endpoint).send().await?;
195        match res.status() {
196            reqwest::StatusCode::OK => {
197                let shards = res.json::<Vec<Shard>>().await?;
198                let shards = Shards { shards };
199                Ok(shards)
200            }
201            _ => Err(self.get_err_msg("get shards", res).await),
202        }
203    }
204
205    ///
206    /// Update shard status
207    ///
208    pub async fn update_class_shard(
209        &self,
210        class_name: &str,
211        shard_name: &str,
212        status: ShardStatus,
213    ) -> Result<Shard, Box<dyn Error>> {
214        let mut endpoint = class_name.to_string();
215        endpoint.push_str("/shards/");
216        endpoint.push_str(shard_name);
217        let endpoint = self.endpoint.join(&endpoint)?;
218        let payload = serde_json::json!({ "status": status });
219        let res = self.client.put(endpoint).json(&payload).send().await?;
220        match res.status() {
221            reqwest::StatusCode::OK => Ok(Shard {
222                name: shard_name.into(),
223                status,
224            }),
225            _ => Err(self.get_err_msg("update class shard", res).await),
226        }
227    }
228
229    ///
230    /// List tenants
231    ///
232    pub async fn list_tenants(&self, class_name: &str) -> Result<Tenants, Box<dyn Error>> {
233        let mut endpoint = class_name.to_string();
234        endpoint.push_str("/tenants");
235        let endpoint = self.endpoint.join(&endpoint)?;
236        let res = self.client.get(endpoint).send().await?;
237        match res.status() {
238            reqwest::StatusCode::OK => {
239                let tenants = res.json::<Vec<Tenant>>().await?;
240                let tenants = Tenants { tenants };
241                Ok(tenants)
242            }
243            _ => Err(self.get_err_msg("list tenants", res).await),
244        }
245    }
246
247    ///
248    /// Add tenant
249    ///
250    pub async fn add_tenants(
251        &self,
252        class_name: &str,
253        tenants: &Tenants,
254    ) -> Result<Tenants, Box<dyn Error>> {
255        let mut endpoint = class_name.to_string();
256        endpoint.push_str("/tenants");
257        let endpoint = self.endpoint.join(&endpoint)?;
258        let payload = serde_json::to_value(&tenants.tenants)?;
259        let res = self.client.post(endpoint).json(&payload).send().await?;
260        match res.status() {
261            reqwest::StatusCode::OK => {
262                let tenants = res.json::<Vec<Tenant>>().await?;
263                let tenants = Tenants { tenants };
264                Ok(tenants)
265            }
266            _ => Err(self.get_err_msg("add tenants", res).await),
267        }
268    }
269
270    ///
271    /// Remove tenants
272    ///
273    pub async fn remove_tenants(
274        &self,
275        class_name: &str,
276        tenants: &Vec<&str>,
277    ) -> Result<bool, Box<dyn Error>> {
278        let mut endpoint = class_name.to_string();
279        endpoint.push_str("/tenants");
280        let endpoint = self.endpoint.join(&endpoint)?;
281        let payload = serde_json::to_value(&tenants)?;
282        let res = self.client.delete(endpoint).json(&payload).send().await?;
283        match res.status() {
284            reqwest::StatusCode::OK => Ok(true),
285            _ => Err(self.get_err_msg("remove tenants", res).await),
286        }
287    }
288
289    ///
290    /// Update tenants
291    ///
292    /// For updating tenants, both `name` and `activity_status` are required.
293    ///
294    /// Note that tenant activity status setting is only available from Weaviate v1.21
295    ///
296    pub async fn update_tenants(
297        &self,
298        class_name: &str,
299        tenants: &Tenants,
300    ) -> Result<Tenants, Box<dyn Error>> {
301        let mut endpoint = class_name.to_string();
302        endpoint.push_str("/tenants");
303        let endpoint = self.endpoint.join(&endpoint)?;
304        let payload = serde_json::to_value(&tenants.tenants)?;
305        let res = self.client.put(endpoint).json(&payload).send().await?;
306        match res.status() {
307            reqwest::StatusCode::OK => {
308                let tenants = res.json::<Vec<Tenant>>().await?;
309                let tenants = Tenants { tenants };
310                Ok(tenants)
311            }
312            _ => Err(self.get_err_msg("update tenants", res).await),
313        }
314    }
315
316    /// Get the error message for the endpoint
317    ///
318    /// Made to reduce the boilerplate error message building
319    async fn get_err_msg(&self, endpoint: &str, res: reqwest::Response) -> Box<SchemaError> {
320        let status_code = res.status();
321        let msg: Result<serde_json::Value, reqwest::Error> = res.json().await;
322        let r_str: String;
323        if let Ok(json) = msg {
324            r_str = format!(
325                "Status code `{}` received when calling {} endpoint. Response: {}",
326                status_code,
327                endpoint,
328                json,
329            );
330        } else {
331            r_str = format!(
332                "Status code `{}` received when calling {} endpoint.",
333                status_code,
334                endpoint
335            );
336        }
337        Box::new(SchemaError(r_str))
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    // Tests currently require a weaviate instance to be running on localhost, as I have not yet
344    // implemented anything to mock the database. In future, actual tests will run as integration
345    // tests in a container as part of the CICD process.
346    use crate::collections::schema::{
347        ActivityStatus, Class, ClassBuilder, Classes, Property, Shard, ShardStatus, Shards, Tenant,
348        Tenants,
349    };
350    use crate::WeaviateClient;
351
352    /// Helper function for generating a testing class
353    fn test_class(class_name: &str) -> Class {
354        ClassBuilder::new(class_name)
355            .with_description("Test")
356            .build()
357    }
358
359    fn test_classes() -> Classes {
360        let class_a = test_class("Test1");
361        let class_b = test_class("Test1");
362        Classes::new(vec![class_a, class_b])
363    }
364
365    fn test_shard() -> Shard {
366        Shard::new("abcd", ShardStatus::READY)
367    }
368
369    /// Helper function for generating a testing property
370    fn test_property(property_name: &str) -> Property {
371        Property::builder(property_name, vec!["boolean"])
372            .with_description("test property")
373            .build()
374    }
375
376    /// Helper function for generating some test tenants, as shown on the weaviate API webpage.
377    fn test_tenants() -> Tenants {
378        Tenants::new(vec![
379            Tenant::builder("TENANT_A").build(),
380            Tenant::builder("TENANT_B")
381                .with_activity_status(ActivityStatus::COLD)
382                .build(),
383        ])
384    }
385
386    fn test_shards() -> Shards {
387        Shards::new(vec![Shard::new("1D3PBjtz9W7r", ShardStatus::READY)])
388    }
389
390    fn get_test_harness() -> (mockito::ServerGuard, WeaviateClient) {
391        let mock_server = mockito::Server::new();
392        let mut host = "http://".to_string();
393        host.push_str(&mock_server.host_with_port());
394        let client = WeaviateClient::builder(&host).build().unwrap();
395        (mock_server, client)
396    }
397
398    fn mock_post(
399        server: &mut mockito::ServerGuard,
400        endpoint: &str,
401        status_code: usize,
402        body: &str,
403    ) -> mockito::Mock {
404        server
405            .mock("POST", endpoint)
406            .with_status(status_code)
407            .with_header("content-type", "application/json")
408            .with_body(body)
409            .create()
410    }
411
412    fn mock_put(
413        server: &mut mockito::ServerGuard,
414        endpoint: &str,
415        status_code: usize,
416        body: &str,
417    ) -> mockito::Mock {
418        server
419            .mock("PUT", endpoint)
420            .with_status(status_code)
421            .with_header("content-type", "application/json")
422            .with_body(body)
423            .create()
424    }
425
426    fn mock_get(
427        server: &mut mockito::ServerGuard,
428        endpoint: &str,
429        status_code: usize,
430        body: &str,
431    ) -> mockito::Mock {
432        server
433            .mock("GET", endpoint)
434            .with_status(status_code)
435            .with_header("content-type", "application/json")
436            .with_body(body)
437            .create()
438    }
439
440    fn mock_delete(
441        server: &mut mockito::ServerGuard,
442        endpoint: &str,
443        status_code: usize,
444    ) -> mockito::Mock {
445        server
446            .mock("DELETE", endpoint)
447            .with_status(status_code)
448            .create()
449    }
450
451    #[tokio::test]
452    async fn test_create_class_ok() {
453        let class = test_class("UnitClass");
454        let class_str = serde_json::to_string(&class).unwrap();
455        let (mut mock_server, client) = get_test_harness();
456        let mock = mock_post(&mut mock_server, "/v1/schema/", 200, &class_str);
457        let res = client.schema.create_class(&class).await;
458        mock.assert();
459        assert!(res.is_ok());
460        assert_eq!(class.class, res.unwrap().class);
461    }
462
463    #[tokio::test]
464    async fn test_create_class_err() {
465        let class = test_class("UnitClass");
466        let (mut mock_server, client) = get_test_harness();
467        let mock = mock_post(&mut mock_server, "/v1/schema/", 401, "");
468        let res = client.schema.create_class(&class).await;
469        mock.assert();
470        assert!(res.is_err());
471    }
472
473    #[tokio::test]
474    async fn test_get_all_classes_ok() {
475        let classes = test_classes();
476        let class_str = serde_json::to_string(&classes).unwrap();
477        let (mut mock_server, client) = get_test_harness();
478        let mock = mock_get(&mut mock_server, "/v1/schema/", 200, &class_str);
479        let res = client.schema.get().await;
480        mock.assert();
481        assert!(res.is_ok());
482        assert_eq!(classes.classes[0].class, res.unwrap().classes[0].class);
483    }
484
485    #[tokio::test]
486    async fn test_get_all_classes_err() {
487        let (mut mock_server, client) = get_test_harness();
488        let mock = mock_get(&mut mock_server, "/v1/schema/", 401, "");
489        let class = client.schema.get().await;
490        mock.assert();
491        assert!(class.is_err());
492    }
493
494    #[tokio::test]
495    async fn test_get_single_class_ok() {
496        let class = test_class("Test");
497        let class_str = serde_json::to_string(&class).unwrap();
498        let (mut mock_server, client) = get_test_harness();
499        let mock = mock_get(&mut mock_server, "/v1/schema/Test", 200, &class_str);
500        let res = client.schema.get_class("Test").await;
501        mock.assert();
502        assert!(res.is_ok());
503        assert_eq!(class.class, res.unwrap().class);
504    }
505
506    #[tokio::test]
507    async fn test_get_single_class_err() {
508        let (mut mock_server, client) = get_test_harness();
509        let mock = mock_get(&mut mock_server, "/v1/schema/Test", 401, "");
510        let class = client.schema.get_class("Test").await;
511        mock.assert();
512        assert!(class.is_err());
513    }
514
515    #[tokio::test]
516    async fn test_get_delete_class_ok() {
517        let (mut mock_server, client) = get_test_harness();
518        let mock = mock_delete(&mut mock_server, "/v1/schema/Test", 200);
519        let res = client.schema.delete("Test").await;
520        mock.assert();
521        assert!(res.is_ok());
522        assert!(res.unwrap());
523    }
524
525    #[tokio::test]
526    async fn test_get_delete_class_err() {
527        let (mut mock_server, client) = get_test_harness();
528        let mock = mock_delete(&mut mock_server, "/v1/schema/Test", 401);
529        let class = client.schema.delete("Test").await;
530        mock.assert();
531        assert!(class.is_err());
532    }
533
534    #[tokio::test]
535    async fn test_update_class_ok() {
536        let class = test_class("Test");
537        let class_str = serde_json::to_string(&class).unwrap();
538        let (mut mock_server, client) = get_test_harness();
539        let mock = mock_put(&mut mock_server, "/v1/schema/Test", 200, &class_str);
540        let res = client.schema.update(&class).await;
541        mock.assert();
542        assert!(res.is_ok());
543        assert_eq!(class.class, res.unwrap().class);
544    }
545
546    #[tokio::test]
547    async fn test_update_class_err() {
548        let class = test_class("Test");
549        let (mut mock_server, client) = get_test_harness();
550        let mock = mock_put(&mut mock_server, "/v1/schema/Test", 401, "");
551        let res = client.schema.update(&class).await;
552        mock.assert();
553        assert!(res.is_err());
554    }
555
556    #[tokio::test]
557    async fn test_add_property_ok() {
558        let property = test_property("Test");
559        let property_str = serde_json::to_string(&property).unwrap();
560        let (mut mock_server, client) = get_test_harness();
561        let mock = mock_post(
562            &mut mock_server,
563            "/v1/schema/TestClass/properties",
564            200,
565            &property_str,
566        );
567        let res = client.schema.add_property("TestClass", &property).await;
568        mock.assert();
569        assert!(res.is_ok());
570        assert_eq!(property.name, res.unwrap().name);
571    }
572
573    #[tokio::test]
574    async fn test_add_property_err() {
575        let property = test_property("Test");
576        let (mut mock_server, client) = get_test_harness();
577        let mock = mock_post(&mut mock_server, "/v1/schema/TestClass/properties", 401, "");
578        let res = client.schema.add_property("TestClass", &property).await;
579        mock.assert();
580        assert!(res.is_err());
581    }
582
583    #[tokio::test]
584    async fn test_get_shards_ok() {
585        let shards = test_shards();
586        let shards_str = serde_json::to_string(&shards.shards).unwrap();
587        let (mut mock_server, client) = get_test_harness();
588        let mock = mock_get(&mut mock_server, "/v1/schema/Test/shards", 200, &shards_str);
589        let res = client.schema.get_shards("Test").await;
590        mock.assert();
591        assert!(res.is_ok());
592        assert_eq!(shards.shards[0].name, res.unwrap().shards[0].name);
593    }
594
595    #[tokio::test]
596    async fn test_get_shards_err() {
597        let (mut mock_server, client) = get_test_harness();
598        let mock = mock_get(&mut mock_server, "/v1/schema/Test/shards", 401, "");
599        let res = client.schema.get_shards("Test").await;
600        mock.assert();
601        assert!(res.is_err());
602    }
603
604    #[tokio::test]
605    async fn test_update_class_shard_ok() {
606        let shard = test_shard();
607        let shard_str = serde_json::to_string(&shard).unwrap();
608        let (mut mock_server, client) = get_test_harness();
609        let mock = mock_put(
610            &mut mock_server,
611            "/v1/schema/Test/shards/abcd",
612            200,
613            &shard_str,
614        );
615        let res = client
616            .schema
617            .update_class_shard("Test", "abcd", ShardStatus::READONLY)
618            .await;
619        mock.assert();
620        assert!(res.is_ok());
621        assert_eq!(shard.name, res.unwrap().name);
622    }
623
624    #[tokio::test]
625    async fn test_update_class_shard_err() {
626        let (mut mock_server, client) = get_test_harness();
627        let mock = mock_put(&mut mock_server, "/v1/schema/Test/shards/abcd", 401, "");
628        let res = client
629            .schema
630            .update_class_shard("Test", "abcd", ShardStatus::READONLY)
631            .await;
632        mock.assert();
633        assert!(res.is_err());
634    }
635
636    #[tokio::test]
637    async fn test_list_tenants_ok() {
638        let tenants = test_tenants();
639        let tenants_str = serde_json::to_string(&tenants.tenants).unwrap();
640        let (mut mock_server, client) = get_test_harness();
641        let mock = mock_get(
642            &mut mock_server,
643            "/v1/schema/Test/tenants",
644            200,
645            &tenants_str,
646        );
647        let res = client.schema.list_tenants("Test").await;
648        mock.assert();
649        assert!(res.is_ok());
650        assert_eq!(tenants.tenants[0].name, res.unwrap().tenants[0].name);
651    }
652
653    #[tokio::test]
654    async fn test_list_tenants_err() {
655        let (mut mock_server, client) = get_test_harness();
656        let mock = mock_get(&mut mock_server, "/v1/schema/Test/tenants", 422, "");
657        let res = client.schema.list_tenants("Test").await;
658        mock.assert();
659        assert!(res.is_err());
660    }
661
662    #[tokio::test]
663    async fn test_add_tenants_ok() {
664        let tenants = test_tenants();
665        let tenants_str = serde_json::to_string(&tenants.tenants).unwrap();
666        let (mut mock_server, client) = get_test_harness();
667        let mock = mock_post(
668            &mut mock_server,
669            "/v1/schema/Test/tenants",
670            200,
671            &tenants_str,
672        );
673        let res = client.schema.add_tenants("Test", &tenants).await;
674        mock.assert();
675        assert!(res.is_ok());
676        assert_eq!(tenants.tenants[0].name, res.unwrap().tenants[0].name);
677    }
678
679    #[tokio::test]
680    async fn test_add_tenants_err() {
681        let tenants = test_tenants();
682        let (mut mock_server, client) = get_test_harness();
683        let mock = mock_post(&mut mock_server, "/v1/schema/Test/tenants", 422, "");
684        let res = client.schema.add_tenants("Test", &tenants).await;
685        mock.assert();
686        assert!(res.is_err());
687    }
688
689    #[tokio::test]
690    async fn test_remove_tenants_ok() {
691        let (mut mock_server, client) = get_test_harness();
692        let mock = mock_delete(&mut mock_server, "/v1/schema/Test/tenants", 200);
693        let res = client
694            .schema
695            .remove_tenants("Test", &vec!["TestTenant"])
696            .await;
697        mock.assert();
698        assert!(res.is_ok());
699        assert!(res.unwrap());
700    }
701
702    #[tokio::test]
703    async fn test_remove_tenants_err() {
704        let (mut mock_server, client) = get_test_harness();
705        let mock = mock_delete(&mut mock_server, "/v1/schema/Test/tenants", 422);
706        let res = client
707            .schema
708            .remove_tenants("Test", &vec!["TestTenant"])
709            .await;
710        mock.assert();
711        assert!(res.is_err());
712    }
713
714    #[tokio::test]
715    async fn test_update_tenants_ok() {
716        let tenants = test_tenants();
717        let tenants_str = serde_json::to_string(&tenants.tenants).unwrap();
718        let (mut mock_server, client) = get_test_harness();
719        let mock = mock_put(
720            &mut mock_server,
721            "/v1/schema/Test/tenants",
722            200,
723            &tenants_str,
724        );
725        let res = client.schema.update_tenants("Test", &tenants).await;
726        mock.assert();
727        assert!(res.is_ok());
728        assert_eq!(tenants.tenants[0].name, res.unwrap().tenants[0].name);
729    }
730
731    #[tokio::test]
732    async fn test_update_tenants_err() {
733        let tenants = test_tenants();
734        let (mut mock_server, client) = get_test_harness();
735        let mock = mock_put(&mut mock_server, "/v1/schema/Test/tenants", 422, "");
736        let res = client.schema.update_tenants("Test", &tenants).await;
737        mock.assert();
738        assert!(res.is_err());
739    }
740}