1use std::{error::Error, fs::File};
36
37use tracing::{debug, error, info, instrument, span, Level};
38
39use api::{
40 authentication::{LoginRequest, TokenResponse, User},
41 client::{BaserowClient, RequestTracing},
42};
43use error::{FileUploadError, TokenAuthError};
44use mapper::TableMapper;
45use reqwest::{
46 header::AUTHORIZATION,
47 multipart::{self, Form},
48 Body, Client, StatusCode,
49};
50use serde::{Deserialize, Serialize};
51use tokio_util::codec::{BytesCodec, FramedRead};
52
53pub mod api;
54
55#[macro_use]
56extern crate async_trait;
57
58pub mod error;
59pub mod filter;
60pub mod mapper;
61
62#[derive(Clone, Debug)]
67pub struct Configuration {
68 base_url: String,
69
70 email: Option<String>,
71 password: Option<String>,
72 jwt: Option<String>,
73
74 database_token: Option<String>,
75 access_token: Option<String>,
76 refresh_token: Option<String>,
77
78 user: Option<User>,
79}
80
81#[derive(Default)]
95pub struct ConfigBuilder {
96 base_url: Option<String>,
97 api_key: Option<String>,
98 email: Option<String>,
99 password: Option<String>,
100}
101
102impl ConfigBuilder {
103 pub fn new() -> Self {
104 Self {
105 base_url: None,
106 api_key: None,
107 email: None,
108 password: None,
109 }
110 }
111
112 pub fn base_url(mut self, base_url: &str) -> Self {
113 self.base_url = Some(base_url.to_string());
114 self
115 }
116
117 pub fn api_key(mut self, api_key: &str) -> Self {
118 self.api_key = Some(api_key.to_string());
119 self
120 }
121
122 pub fn email(mut self, email: &str) -> Self {
123 self.email = Some(email.to_string());
124 self
125 }
126
127 pub fn password(mut self, password: &str) -> Self {
128 self.password = Some(password.to_string());
129 self
130 }
131
132 pub fn build(self) -> Configuration {
133 Configuration {
134 base_url: self.base_url.unwrap(),
135
136 email: self.email,
137 password: self.password,
138 jwt: None,
139
140 database_token: self.api_key,
141 access_token: None,
142 refresh_token: None,
143
144 user: None,
145 }
146 }
147}
148
149#[derive(Clone, Debug)]
154pub struct Baserow {
155 configuration: Configuration,
156 client: Client,
157}
158
159impl Baserow {
160 pub fn with_configuration(configuration: Configuration) -> Self {
161 let span = span!(Level::INFO, "baserow_init");
162 let _enter = span.enter();
163
164 info!("Initializing Baserow client with configuration");
165 debug!(?configuration, "Configuration details");
166
167 Self {
168 configuration,
169 client: Client::new(),
170 }
171 }
172
173 pub fn with_database_token(self, token: String) -> Self {
174 let mut configuration = self.configuration.clone();
175 configuration.database_token = Some(token);
176
177 Self {
178 configuration,
179 client: self.client,
180 }
181 }
182
183 fn with_access_token(&self, access_token: String) -> Self {
184 let mut configuration = self.configuration.clone();
185 configuration.access_token = Some(access_token);
186
187 Self {
188 configuration,
189 client: self.client.clone(),
190 }
191 }
192
193 fn with_refresh_token(&self, refresh_token: String) -> Self {
194 let mut configuration = self.configuration.clone();
195 configuration.refresh_token = Some(refresh_token);
196
197 Self {
198 configuration,
199 client: self.client.clone(),
200 }
201 }
202
203 fn with_user(&self, user: User) -> Self {
204 let mut configuration = self.configuration.clone();
205 configuration.user = Some(user);
206
207 Self {
208 configuration,
209 client: self.client.clone(),
210 }
211 }
212}
213
214#[async_trait]
215impl BaserowClient for Baserow {
216 fn get_configuration(&self) -> Configuration {
217 self.configuration.clone()
218 }
219
220 fn get_client(&self) -> Client {
221 self.client.clone()
222 }
223
224 #[instrument(skip(self), err)]
225 async fn token_auth(&self) -> Result<Box<dyn BaserowClient>, TokenAuthError> {
226 let url = format!("{}/api/user/token-auth/", &self.configuration.base_url);
227
228 let email = self
229 .configuration
230 .email
231 .as_ref()
232 .ok_or(TokenAuthError::MissingCredentials("email"))?;
233
234 let password = self
235 .configuration
236 .password
237 .as_ref()
238 .ok_or(TokenAuthError::MissingCredentials("password"))?;
239
240 let auth_request = LoginRequest {
241 email: email.clone(),
242 password: password.clone(),
243 };
244
245 let req = Client::new().post(url).json(&auth_request);
246
247 debug!("Sending token authentication request");
248 let resp = self.trace_request(&self.client, req.build()?).await?;
249
250 match resp.status() {
251 StatusCode::OK => {
252 info!("Token authentication successful");
253 let token_response: TokenResponse = resp.json().await?;
254 let client = self
255 .clone()
256 .with_database_token(token_response.token)
257 .with_access_token(token_response.access_token)
258 .with_refresh_token(token_response.refresh_token)
259 .with_user(token_response.user);
260 Ok(Box::new(client) as Box<dyn BaserowClient>)
261 }
262 _status => {
263 let error_text = resp.text().await?;
264 let error = TokenAuthError::AuthenticationFailed(error_text);
265 error.log();
266 Err(error)
267 }
268 }
269 }
270
271 #[instrument(skip(self), err)]
272 async fn table_fields(&self, table_id: u64) -> Result<Vec<TableField>, Box<dyn Error>> {
273 let url = format!(
274 "{}/api/database/fields/table/{}/",
275 &self.configuration.base_url, table_id
276 );
277
278 let mut req = self.client.get(url);
279
280 if let Some(token) = &self.configuration.jwt {
281 req = req.header(AUTHORIZATION, format!("JWT {}", token));
282 } else if let Some(token) = &self.configuration.database_token {
283 req = req.header(AUTHORIZATION, format!("Token {}", token));
284 } else {
285 return Err("No authentication token provided".into());
286 }
287
288 debug!("Sending request to fetch table fields");
289 let resp = self.trace_request(&self.client, req.build()?).await?;
290 match resp.status() {
291 StatusCode::OK => {
292 let fields: Vec<TableField> = resp.json().await?;
293 info!(
294 field_count = fields.len(),
295 "Successfully retrieved table fields"
296 );
297 debug!(?fields, "Retrieved field details");
298 Ok(fields)
299 }
300 status => {
301 let error_text = resp.text().await?;
302 error!(%status, error = %error_text, "Failed to retrieve table fields");
303 Err(format!(
304 "Failed to retrieve table fields (status: {}): {}",
305 status, error_text
306 )
307 .into())
308 }
309 }
310 }
311
312 fn table_by_id(&self, id: u64) -> BaserowTable {
313 BaserowTable::default()
314 .with_id(id)
315 .with_baserow(self.clone())
316 }
317
318 #[instrument(skip(self, file), fields(filename = %filename), err)]
319 async fn upload_file(
320 &self,
321 file: File,
322 filename: String,
323 ) -> Result<api::file::File, FileUploadError> {
324 let url = format!(
325 "{}/api/user-files/upload-file/",
326 &self.configuration.base_url
327 );
328
329 let file = tokio::fs::File::from_std(file);
330 let stream = FramedRead::new(file, BytesCodec::new());
331 let file_body = Body::wrap_stream(stream);
332
333 let mime_type = mime_guess::from_path(&filename).first_or_octet_stream();
334
335 let file_part = multipart::Part::stream(file_body)
336 .file_name(filename)
337 .mime_str(mime_type.as_ref())?;
338
339 let form = Form::new().part("file", file_part);
340
341 let mut req = self.client.post(url);
342
343 if let Some(token) = &self.configuration.jwt {
344 req = req.header(AUTHORIZATION, format!("JWT {}", token));
345 } else if let Some(api_key) = &self.configuration.database_token {
346 req = req.header(AUTHORIZATION, format!("Token {}", api_key));
347 }
348
349 let resp = self
350 .trace_request(&self.client, req.multipart(form).build()?)
351 .await;
352
353 match resp {
354 Ok(resp) => match resp.status() {
355 StatusCode::OK => {
356 let json: api::file::File = resp.json().await?;
357 info!("File upload successful");
358 debug!(?json, "Upload response details");
359 Ok(json)
360 }
361 status => {
362 let error = FileUploadError::UnexpectedStatusCode(status);
363 error.log();
364 Err(error)
365 }
366 },
367 Err(e) => {
368 let error = FileUploadError::UploadError(e);
369 error.log();
370 Err(error)
371 }
372 }
373 }
374
375 #[instrument(skip(self), err)]
376 async fn upload_file_via_url(&self, url: &str) -> Result<api::file::File, FileUploadError> {
377 let file_url = url
379 .parse::<reqwest::Url>()
380 .map_err(|_| FileUploadError::InvalidURL(url.to_string()))?
381 .to_string();
382
383 let upload_request = api::file::UploadFileViaUrlRequest {
384 url: file_url.clone(),
385 };
386
387 let url = format!(
388 "{}/api/user-files/upload-via-url/",
389 &self.configuration.base_url
390 );
391
392 let mut req = self.client.post(url).json(&upload_request);
393
394 if let Some(token) = &self.configuration.jwt {
395 req = req.header(AUTHORIZATION, format!("JWT {}", token));
396 } else if let Some(api_key) = &self.configuration.database_token {
397 req = req.header(AUTHORIZATION, format!("Token {}", api_key));
398 }
399
400 let resp = self.trace_request(&self.client, req.build()?).await;
401
402 match resp {
403 Ok(resp) => match resp.status() {
404 StatusCode::OK => {
405 let json: api::file::File = resp.json().await?;
406 info!("File upload via URL successful");
407 debug!(?json, "Upload response details");
408 Ok(json)
409 }
410 status => {
411 let error = FileUploadError::UnexpectedStatusCode(status);
412 error.log();
413 Err(error)
414 }
415 },
416 Err(e) => {
417 let error = FileUploadError::UploadError(e);
418 error.log();
419 Err(error)
420 }
421 }
422 }
423}
424
425#[derive(Deserialize, Serialize, Default, Clone)]
430pub struct BaserowTable {
431 #[serde(skip)]
432 baserow: Option<Baserow>,
433
434 #[serde(skip)]
435 mapper: Option<TableMapper>,
436
437 id: Option<u64>,
438 pub name: Option<String>,
439 order: Option<i64>,
440 database_id: Option<i64>,
441}
442
443impl BaserowTable {
444 fn with_baserow(mut self, baserow: Baserow) -> BaserowTable {
445 self.baserow = Some(baserow);
446 self
447 }
448
449 fn with_id(mut self, id: u64) -> BaserowTable {
450 self.id = Some(id);
451 self
452 }
453}
454
455pub use api::table_operations::BaserowTableOperations;
456
457#[derive(Clone, Deserialize, Serialize, Debug)]
461pub struct TableField {
462 pub id: u64,
463 pub table_id: u64,
464 pub name: String,
465 pub order: u32,
466 pub r#type: String,
467 pub primary: bool,
468 pub read_only: bool,
469 pub description: Option<String>,
470}
471
472#[derive(Clone, Debug)]
476pub enum OrderDirection {
477 Asc,
478 Desc,
479}
480
481#[cfg(test)]
482mod tests {
483 use super::*;
484 use serde_json::Value;
485 use std::collections::HashMap;
486
487 #[test]
488 fn test() {
489 let configuration = Configuration {
490 base_url: "https://baserow.io".to_string(),
491 database_token: Some("123".to_string()),
492 email: None,
493 password: None,
494 jwt: None,
495 access_token: None,
496 refresh_token: None,
497 user: None,
498 };
499 let baserow = Baserow::with_configuration(configuration);
500 let _table = baserow.table_by_id(1234);
501 }
502
503 #[tokio::test]
504 async fn test_create_record() {
505 let mut server = mockito::Server::new_async().await;
506 let mock_url = server.url();
507
508 let mock = server
509 .mock("POST", "/api/database/rows/table/1234/")
510 .with_status(200)
511 .with_header("Content-Type", "application/json")
512 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
513 .with_body(r#"{"id": 1234, "field_1": "test"}"#)
514 .create();
515
516 let configuration = Configuration {
517 base_url: mock_url,
518 database_token: Some("123".to_string()),
519 email: None,
520 password: None,
521 jwt: None,
522 access_token: None,
523 refresh_token: None,
524 user: None,
525 };
526 let baserow = Baserow::with_configuration(configuration);
527 let table = baserow.table_by_id(1234);
528
529 let mut record = HashMap::new();
530 record.insert("field_1".to_string(), Value::String("test".to_string()));
531
532 let result = table.create_one(record, None).await;
533 assert!(result.is_ok());
534
535 let created_record = result.unwrap();
536 assert_eq!(created_record["field_1"], Value::String("test".to_string()));
537
538 mock.assert();
539 }
540
541 #[tokio::test]
542 async fn test_get_record() {
543 let mut server = mockito::Server::new_async().await;
544 let mock_url = server.url();
545
546 let mock = server
547 .mock("GET", "/api/database/rows/table/1234/5678/")
548 .with_status(200)
549 .with_header("Content-Type", "application/json")
550 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
551 .with_body(r#"{"id": 5678, "field_1": "test"}"#)
552 .create();
553
554 let configuration = Configuration {
555 base_url: mock_url,
556 database_token: Some("123".to_string()),
557 email: None,
558 password: None,
559 jwt: None,
560 access_token: None,
561 refresh_token: None,
562 user: None,
563 };
564 let baserow = Baserow::with_configuration(configuration);
565 let table = baserow.table_by_id(1234);
566
567 let result: Result<HashMap<String, Value>, Box<dyn Error>> =
568 table.get_one(5678, None).await;
569 assert!(result.is_ok());
570
571 let record = result.unwrap();
572 assert_eq!(record["id"], Value::Number(5678.into()));
573 assert_eq!(record["field_1"], Value::String("test".to_string()));
574
575 mock.assert();
576 }
577
578 #[tokio::test]
579 async fn test_update_record() {
580 let mut server = mockito::Server::new_async().await;
581 let mock_url = server.url();
582
583 let mock = server
584 .mock("PATCH", "/api/database/rows/table/1234/5678/")
585 .with_status(200)
586 .with_header("Content-Type", "application/json")
587 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
588 .with_body(r#"{"id": 5678, "field_1": "updated"}"#)
589 .create();
590
591 let configuration = Configuration {
592 base_url: mock_url,
593 database_token: Some("123".to_string()),
594 email: None,
595 password: None,
596 jwt: None,
597 access_token: None,
598 refresh_token: None,
599 user: None,
600 };
601 let baserow = Baserow::with_configuration(configuration);
602 let table = baserow.table_by_id(1234);
603
604 let mut record = HashMap::new();
605 record.insert("field_1".to_string(), Value::String("updated".to_string()));
606
607 let result = table.update(5678, record, None).await;
608 assert!(result.is_ok());
609
610 let updated_record = result.unwrap();
611 assert_eq!(
612 updated_record["field_1"],
613 Value::String("updated".to_string())
614 );
615
616 mock.assert();
617 }
618
619 #[tokio::test]
620 async fn test_delete_record() {
621 let mut server = mockito::Server::new_async().await;
622 let mock_url = server.url();
623
624 let mock = server
625 .mock("DELETE", "/api/database/rows/table/1234/5678/")
626 .with_status(200)
627 .with_header("Content-Type", "application/json")
628 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
629 .create();
630
631 let configuration = Configuration {
632 base_url: mock_url,
633 database_token: Some("123".to_string()),
634 email: None,
635 password: None,
636 jwt: None,
637 access_token: None,
638 refresh_token: None,
639 user: None,
640 };
641 let baserow = Baserow::with_configuration(configuration);
642 let table = baserow.table_by_id(1234);
643
644 let result = table.delete(5678).await;
645 assert!(result.is_ok());
646
647 mock.assert();
648 }
649
650 #[tokio::test]
651 async fn test_upload_file_via_url() {
652 let mut server = mockito::Server::new_async().await;
653 let mock_url = server.url();
654
655 let mock = server
656 .mock("POST", "/api/user-files/upload-via-url/")
657 .with_status(200)
658 .with_header("Content-Type", "application/json")
659 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
660 .with_body(r#"{
661 "url": "https://files.baserow.io/user_files/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
662 "thumbnails": {
663 "tiny": {
664 "url": "https://files.baserow.io/media/thumbnails/tiny/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
665 "width": 21,
666 "height": 21
667 },
668 "small": {
669 "url": "https://files.baserow.io/media/thumbnails/small/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
670 "width": 48,
671 "height": 48
672 }
673 },
674 "name": "VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
675 "size": 229940,
676 "mime_type": "image/png",
677 "is_image": true,
678 "image_width": 1280,
679 "image_height": 585,
680 "uploaded_at": "2020-11-17T12:16:10.035234+00:00"
681}"#)
682 .create();
683
684 let configuration = Configuration {
685 base_url: mock_url,
686 database_token: Some("123".to_string()),
687 email: None,
688 password: None,
689 jwt: None,
690 access_token: None,
691 refresh_token: None,
692 user: None,
693 };
694 let baserow = Baserow::with_configuration(configuration);
695
696 let result = baserow
697 .upload_file_via_url("https://example.com/test.txt")
698 .await;
699 assert!(result.is_ok());
700
701 let uploaded_file = result.unwrap();
702 assert_eq!(uploaded_file.name, "VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png".to_string());
703
704 mock.assert();
705 }
706
707 #[tokio::test]
708 async fn test_token_auth() {
709 let mut server = mockito::Server::new_async().await;
710 let mock_url = server.url();
711
712 let mock = server
713 .mock("POST", "/api/user/token-auth/")
714 .with_status(200)
715 .with_header("Content-Type", "application/json")
716 .with_body(
717 r#"{
718 "user": {
719 "first_name": "string",
720 "username": "user@example.com",
721 "language": "string"
722 },
723 "token": "string",
724 "access_token": "string",
725 "refresh_token": "string"
726}"#,
727 )
728 .create();
729
730 let configuration = ConfigBuilder::new()
731 .base_url(&mock_url)
732 .email("test@example.com")
733 .password("password")
734 .build();
735 let baserow = Baserow::with_configuration(configuration);
736
737 let result = baserow.token_auth().await;
738 assert!(result.is_ok());
739
740 let logged_in_baserow = result.unwrap();
741 assert_eq!(
742 logged_in_baserow
743 .get_configuration()
744 .database_token
745 .unwrap(),
746 "string"
747 );
748
749 mock.assert();
750 }
751
752 #[tokio::test]
753 async fn test_upload_file() {
754 let mut server = mockito::Server::new_async().await;
755 let mock_url = server.url();
756
757 let mock = server
758 .mock("POST", "/api/user-files/upload-file/")
759 .with_status(200)
760 .with_header("Content-Type", "application/json")
761 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
762 .with_body(r#"{
763 "url": "https://files.baserow.io/user_files/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
764 "thumbnails": {
765 "tiny": {
766 "url": "https://files.baserow.io/media/thumbnails/tiny/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
767 "width": 21,
768 "height": 21
769 },
770 "small": {
771 "url": "https://files.baserow.io/media/thumbnails/small/VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
772 "width": 48,
773 "height": 48
774 }
775 },
776 "name": "VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png",
777 "size": 229940,
778 "mime_type": "image/png",
779 "is_image": true,
780 "image_width": 1280,
781 "image_height": 585,
782 "uploaded_at": "2020-11-17T12:16:10.035234+00:00"
783}"#)
784 .create();
785
786 let configuration = Configuration {
787 base_url: mock_url,
788 database_token: Some("123".to_string()),
789 email: None,
790 password: None,
791 jwt: None,
792 access_token: None,
793 refresh_token: None,
794 user: None,
795 };
796 let baserow = Baserow::with_configuration(configuration);
797
798 let file = File::open(".gitignore").unwrap();
799 let result = baserow.upload_file(file, "image.png".to_string()).await;
800 assert!(result.is_ok());
801
802 let uploaded_file = result.unwrap();
803 assert_eq!(uploaded_file.name, "VXotniBOVm8tbstZkKsMKbj2Qg7KmPvn_39d354a76abe56baaf569ad87d0333f58ee4bf3eed368e3b9dc736fd18b09dfd.png".to_string());
804
805 mock.assert();
806 }
807
808 #[tokio::test]
809 async fn test_view_query() {
810 let mut server = mockito::Server::new_async().await;
811 let mock_url = server.url();
812
813 let mock = server
814 .mock("GET", "/api/database/rows/table/1234/")
815 .match_query(mockito::Matcher::UrlEncoded("view_id".into(), "5678".into()))
816 .with_status(200)
817 .with_header("Content-Type", "application/json")
818 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
819 .with_body(r#"{"count": 1, "next": null, "previous": null, "results": [{"id": 1, "field_1": "test"}]}"#)
820 .create();
821
822 let configuration = Configuration {
823 base_url: mock_url,
824 database_token: Some("123".to_string()),
825 email: None,
826 password: None,
827 jwt: None,
828 access_token: None,
829 refresh_token: None,
830 user: None,
831 };
832 let baserow = Baserow::with_configuration(configuration);
833 let table = baserow.table_by_id(1234);
834
835 let result = table
836 .query()
837 .view(5678)
838 .get::<HashMap<String, Value>>()
839 .await;
840
841 assert!(result.is_ok());
842 let response = result.unwrap();
843 assert_eq!(response.count, Some(1));
844 assert_eq!(
845 response.results[0]["field_1"],
846 Value::String("test".to_string())
847 );
848
849 mock.assert();
850 }
851
852 #[tokio::test]
853 async fn test_view_query_with_filters() {
854 let mut server = mockito::Server::new_async().await;
855 let mock_url = server.url();
856
857 let mock = server
858 .mock("GET", "/api/database/rows/table/1234/")
859 .match_query(mockito::Matcher::AllOf(vec![
860 mockito::Matcher::UrlEncoded("view_id".into(), "5678".into()),
861 mockito::Matcher::UrlEncoded("filter__field_1__equal".into(), "test".into()),
862 mockito::Matcher::UrlEncoded("order_by".into(), "field_1".into()),
863 ]))
864 .with_status(200)
865 .with_header("Content-Type", "application/json")
866 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
867 .with_body(r#"{"count": 1, "next": null, "previous": null, "results": [{"id": 1, "field_1": "test"}]}"#)
868 .create();
869
870 let configuration = Configuration {
871 base_url: mock_url,
872 database_token: Some("123".to_string()),
873 email: None,
874 password: None,
875 jwt: None,
876 access_token: None,
877 refresh_token: None,
878 user: None,
879 };
880 let baserow = Baserow::with_configuration(configuration);
881 let table = baserow.table_by_id(1234);
882
883 let result = table
884 .query()
885 .view(5678)
886 .filter_by("field_1", filter::Filter::Equal, "test")
887 .order_by("field_1", OrderDirection::Asc)
888 .get::<HashMap<String, Value>>()
889 .await;
890
891 assert!(result.is_ok());
892 let response = result.unwrap();
893 assert_eq!(response.count, Some(1));
894 assert_eq!(
895 response.results[0]["field_1"],
896 Value::String("test".to_string())
897 );
898
899 mock.assert();
900 }
901
902 #[tokio::test]
903 async fn test_view_query_with_pagination() {
904 let mut server = mockito::Server::new_async().await;
905 let mock_url = server.url();
906
907 let mock = server
908 .mock("GET", "/api/database/rows/table/1234/")
909 .match_query(mockito::Matcher::AllOf(vec![
910 mockito::Matcher::UrlEncoded("view_id".into(), "5678".into()),
911 mockito::Matcher::UrlEncoded("size".into(), "2".into()),
912 mockito::Matcher::UrlEncoded("page".into(), "1".into()),
913 ]))
914 .with_status(200)
915 .with_header("Content-Type", "application/json")
916 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
917 .with_body(r#"{"count": 3, "next": "http://example.com/next", "previous": "http://example.com/prev", "results": [{"id": 2, "field_1": "test2"}, {"id": 3, "field_1": "test3"}]}"#)
918 .create();
919
920 let configuration = Configuration {
921 base_url: mock_url,
922 database_token: Some("123".to_string()),
923 email: None,
924 password: None,
925 jwt: None,
926 access_token: None,
927 refresh_token: None,
928 user: None,
929 };
930 let baserow = Baserow::with_configuration(configuration);
931 let table = baserow.table_by_id(1234);
932
933 let result = table
934 .query()
935 .view(5678)
936 .size(2)
937 .page(1)
938 .get::<HashMap<String, Value>>()
939 .await;
940
941 assert!(result.is_ok());
942 let response = result.unwrap();
943 assert_eq!(response.count, Some(3));
944 assert_eq!(response.next, Some("http://example.com/next".to_string()));
945 assert_eq!(
946 response.previous,
947 Some("http://example.com/prev".to_string())
948 );
949 assert_eq!(response.results.len(), 2);
950 assert_eq!(
951 response.results[0]["field_1"],
952 Value::String("test2".to_string())
953 );
954 assert_eq!(
955 response.results[1]["field_1"],
956 Value::String("test3".to_string())
957 );
958
959 mock.assert();
960 }
961
962 #[tokio::test]
963 async fn test_query_with_user_field_names() {
964 let mut server = mockito::Server::new_async().await;
965 let mock_url = server.url();
966
967 let mock = server
968 .mock("GET", "/api/database/rows/table/1234/")
969 .match_query(mockito::Matcher::UrlEncoded(
970 "user_field_names".into(),
971 "true".into(),
972 ))
973 .with_status(200)
974 .with_header("Content-Type", "application/json")
975 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
976 .with_body(r#"{"count": 1, "next": null, "previous": null, "results": [{"User Name": "test"}]}"#)
977 .create();
978
979 let configuration = Configuration {
980 base_url: mock_url,
981 database_token: Some("123".to_string()),
982 email: None,
983 password: None,
984 jwt: None,
985 access_token: None,
986 refresh_token: None,
987 user: None,
988 };
989 let baserow = Baserow::with_configuration(configuration);
990 let table = baserow.table_by_id(1234);
991
992 let result = table
993 .query()
994 .user_field_names(true)
995 .get::<HashMap<String, Value>>()
996 .await;
997
998 assert!(result.is_ok());
999 let response = result.unwrap();
1000 assert_eq!(response.count, Some(1));
1001 assert_eq!(
1002 response.results[0]["User Name"],
1003 Value::String("test".to_string())
1004 );
1005
1006 mock.assert();
1007 }
1008
1009 #[tokio::test]
1010 async fn test_view_query_invalid_view() {
1011 let mut server = mockito::Server::new_async().await;
1012 let mock_url = server.url();
1013
1014 let mock = server
1015 .mock("GET", "/api/database/rows/table/1234/")
1016 .match_query(mockito::Matcher::UrlEncoded(
1017 "view_id".into(),
1018 "9999".into(),
1019 ))
1020 .with_status(404)
1021 .with_header("Content-Type", "application/json")
1022 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
1023 .with_body(r#"{"error": "View does not exist."}"#)
1024 .create();
1025
1026 let configuration = Configuration {
1027 base_url: mock_url,
1028 database_token: Some("123".to_string()),
1029 email: None,
1030 password: None,
1031 jwt: None,
1032 access_token: None,
1033 refresh_token: None,
1034 user: None,
1035 };
1036 let baserow = Baserow::with_configuration(configuration);
1037 let table = baserow.table_by_id(1234);
1038
1039 let result = table
1040 .query()
1041 .view(9999)
1042 .get::<HashMap<String, Value>>()
1043 .await;
1044
1045 assert!(result.is_err());
1046 mock.assert();
1047 }
1048
1049 #[tokio::test]
1050 async fn test_table_fields() {
1051 let mut server = mockito::Server::new_async().await;
1052 let mock_url = server.url();
1053
1054 let mock = server
1055 .mock("GET", "/api/database/fields/table/1234/")
1056 .with_status(200)
1057 .with_header("Content-Type", "application/json")
1058 .with_header(AUTHORIZATION, format!("Token {}", "123").as_str())
1059 .with_body(
1060 r#"[
1061 {
1062 "id": 1529,
1063 "table_id": 1234,
1064 "name": "Name",
1065 "order": 0,
1066 "type": "text",
1067 "primary": true,
1068 "read_only": false,
1069 "description": "A sample description"
1070 },
1071 {
1072 "id": 6499,
1073 "table_id": 1234,
1074 "name": "Field 2",
1075 "order": 1,
1076 "type": "last_modified",
1077 "primary": false,
1078 "read_only": true,
1079 "description": "A sample description"
1080 },
1081 {
1082 "id": 6500,
1083 "table_id": 1234,
1084 "name": "Datei",
1085 "order": 2,
1086 "type": "file",
1087 "primary": false,
1088 "read_only": false,
1089 "description": "A sample description"
1090 }
1091]"#,
1092 )
1093 .create();
1094
1095 let configuration = Configuration {
1096 base_url: mock_url,
1097 database_token: Some("123".to_string()),
1098 email: None,
1099 password: None,
1100 jwt: None,
1101 access_token: None,
1102 refresh_token: None,
1103 user: None,
1104 };
1105 let baserow = Baserow::with_configuration(configuration);
1106
1107 let result = baserow.table_fields(1234).await;
1108
1109 print!("result: {:#?}", result);
1110
1111 assert!(result.is_ok());
1112
1113 let fields = result.unwrap();
1114 assert_eq!(fields.len(), 3);
1115 assert_eq!(fields[0].id, 1529);
1116 assert_eq!(fields[0].table_id, 1234);
1117 assert_eq!(fields[0].name, "Name");
1118 assert_eq!(fields[1].id, 6499);
1119 assert_eq!(fields[1].table_id, 1234);
1120 assert_eq!(fields[1].name, "Field 2");
1121
1122 mock.assert();
1123 }
1124}