style: use 2-space indentation across entire codebase
Add rustfmt.toml with tab_spaces = 2 and reformat all source files, tests, and README code examples to use 2-space indentation.
This commit is contained in:
@@ -85,8 +85,8 @@ use onspring::OnspringClient;
|
|||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
let client = OnspringClient::builder(env::var("API_KEY").unwrap())
|
let client = OnspringClient::builder(env::var("API_KEY").unwrap())
|
||||||
.base_url(env::var("BASE_URL").unwrap())
|
.base_url(env::var("BASE_URL").unwrap())
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Client Configuration
|
### Client Configuration
|
||||||
@@ -99,18 +99,18 @@ use std::time::Duration;
|
|||||||
|
|
||||||
// Custom timeout
|
// Custom timeout
|
||||||
let client = OnspringClient::builder("your-api-key")
|
let client = OnspringClient::builder("your-api-key")
|
||||||
.timeout(Duration::from_secs(30))
|
.timeout(Duration::from_secs(30))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Custom reqwest client
|
// Custom reqwest client
|
||||||
let http_client = reqwest::Client::builder()
|
let http_client = reqwest::Client::builder()
|
||||||
.timeout(Duration::from_secs(60))
|
.timeout(Duration::from_secs(60))
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let client = OnspringClient::builder("your-api-key")
|
let client = OnspringClient::builder("your-api-key")
|
||||||
.http_client(http_client)
|
.http_client(http_client)
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
### Error Handling
|
### Error Handling
|
||||||
@@ -124,11 +124,11 @@ All client methods return `onspring::Result<T>`, which is an alias for `Result<T
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
match client.get_app(130).await {
|
match client.get_app(130).await {
|
||||||
Ok(app) => println!("App: {}", app.name.unwrap_or_default()),
|
Ok(app) => println!("App: {}", app.name.unwrap_or_default()),
|
||||||
Err(onspring::OnspringError::Api { status_code, message }) => {
|
Err(onspring::OnspringError::Api { status_code, message }) => {
|
||||||
eprintln!("API error {}: {}", status_code, message);
|
eprintln!("API error {}: {}", status_code, message);
|
||||||
}
|
}
|
||||||
Err(e) => eprintln!("Error: {}", e),
|
Err(e) => eprintln!("Error: {}", e),
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ let res = client.list_apps(None).await?;
|
|||||||
let apps = res.items.unwrap_or_default();
|
let apps = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for app in &apps {
|
for app in &apps {
|
||||||
println!("{:?}", app);
|
println!("{:?}", app);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ let res = client.list_apps(Some(paging)).await?;
|
|||||||
let apps = res.items.unwrap_or_default();
|
let apps = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for app in &apps {
|
for app in &apps {
|
||||||
println!("{:?}", app);
|
println!("{:?}", app);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ let res = client.batch_get_apps(&[130, 131]).await?;
|
|||||||
let apps = res.items.unwrap_or_default();
|
let apps = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for app in &apps {
|
for app in &apps {
|
||||||
println!("{:?}", app);
|
println!("{:?}", app);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -220,7 +220,7 @@ let res = client.batch_get_fields(&[4793, 4801]).await?;
|
|||||||
let fields = res.items.unwrap_or_default();
|
let fields = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for field in &fields {
|
for field in &fields {
|
||||||
println!("{:?}", field);
|
println!("{:?}", field);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ let res = client.list_fields(132, None).await?;
|
|||||||
let fields = res.items.unwrap_or_default();
|
let fields = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for field in &fields {
|
for field in &fields {
|
||||||
println!("{:?}", field);
|
println!("{:?}", field);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@ let res = client.list_fields(132, Some(paging)).await?;
|
|||||||
let fields = res.items.unwrap_or_default();
|
let fields = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for field in &fields {
|
for field in &fields {
|
||||||
println!("{:?}", field);
|
println!("{:?}", field);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -275,7 +275,7 @@ println!("Content-Type: {:?}", file.content_type);
|
|||||||
println!("File Name: {:?}", file.file_name);
|
println!("File Name: {:?}", file.file_name);
|
||||||
|
|
||||||
if let Some(name) = &file.file_name {
|
if let Some(name) = &file.file_name {
|
||||||
fs::write(name, &file.data)?;
|
fs::write(name, &file.data)?;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -285,13 +285,13 @@ if let Some(name) = &file.file_name {
|
|||||||
use onspring::models::file::SaveFileRequest;
|
use onspring::models::file::SaveFileRequest;
|
||||||
|
|
||||||
let request = SaveFileRequest {
|
let request = SaveFileRequest {
|
||||||
record_id: 1,
|
record_id: 1,
|
||||||
field_id: 4806,
|
field_id: 4806,
|
||||||
notes: Some("notes".to_string()),
|
notes: Some("notes".to_string()),
|
||||||
modified_date: None,
|
modified_date: None,
|
||||||
file_name: "test-attachment.txt".to_string(),
|
file_name: "test-attachment.txt".to_string(),
|
||||||
file_data: std::fs::read("test-attachment.txt")?,
|
file_data: std::fs::read("test-attachment.txt")?,
|
||||||
content_type: "text/plain".to_string(),
|
content_type: "text/plain".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.upload_file(request).await?;
|
let res = client.upload_file(request).await?;
|
||||||
@@ -315,10 +315,10 @@ To add a list value don't provide an id value.
|
|||||||
use onspring::models::list::SaveListItemRequest;
|
use onspring::models::list::SaveListItemRequest;
|
||||||
|
|
||||||
let request = SaveListItemRequest {
|
let request = SaveListItemRequest {
|
||||||
id: None,
|
id: None,
|
||||||
name: "New Value".to_string(),
|
name: "New Value".to_string(),
|
||||||
numeric_value: Some(1.0),
|
numeric_value: Some(1.0),
|
||||||
color: Some("#000000".to_string()),
|
color: Some("#000000".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.save_list_item(638, request).await?;
|
let res = client.save_list_item(638, request).await?;
|
||||||
@@ -334,10 +334,10 @@ use uuid::Uuid;
|
|||||||
let item_id: Uuid = "35c79a46-04b8-4069-bbc1-161a175f962c".parse().unwrap();
|
let item_id: Uuid = "35c79a46-04b8-4069-bbc1-161a175f962c".parse().unwrap();
|
||||||
|
|
||||||
let request = SaveListItemRequest {
|
let request = SaveListItemRequest {
|
||||||
id: Some(item_id),
|
id: Some(item_id),
|
||||||
name: "Updated Value".to_string(),
|
name: "Updated Value".to_string(),
|
||||||
numeric_value: Some(1.0),
|
numeric_value: Some(1.0),
|
||||||
color: Some("#000000".to_string()),
|
color: Some("#000000".to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.save_list_item(638, request).await?;
|
let res = client.save_list_item(638, request).await?;
|
||||||
@@ -365,7 +365,7 @@ let res = client.list_records(130, None, None, None).await?;
|
|||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -376,15 +376,15 @@ use onspring::{DataFormat, PagingRequest};
|
|||||||
|
|
||||||
let paging = PagingRequest { page_number: 1, page_size: 10 };
|
let paging = PagingRequest { page_number: 1, page_size: 10 };
|
||||||
let res = client.list_records(
|
let res = client.list_records(
|
||||||
130,
|
130,
|
||||||
Some(paging),
|
Some(paging),
|
||||||
Some(&[4804]),
|
Some(&[4804]),
|
||||||
Some(DataFormat::Raw),
|
Some(DataFormat::Raw),
|
||||||
).await?;
|
).await?;
|
||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -414,17 +414,17 @@ Returns a collection of Onspring records based on the provided app id and record
|
|||||||
use onspring::models::record::BatchGetRecordsRequest;
|
use onspring::models::record::BatchGetRecordsRequest;
|
||||||
|
|
||||||
let request = BatchGetRecordsRequest {
|
let request = BatchGetRecordsRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
record_ids: vec![1, 2],
|
record_ids: vec![1, 2],
|
||||||
field_ids: None,
|
field_ids: None,
|
||||||
data_format: None,
|
data_format: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.batch_get_records(request).await?;
|
let res = client.batch_get_records(request).await?;
|
||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -434,17 +434,17 @@ You can also specify what field values to return and in what format (Raw vs. For
|
|||||||
use onspring::{DataFormat, models::record::BatchGetRecordsRequest};
|
use onspring::{DataFormat, models::record::BatchGetRecordsRequest};
|
||||||
|
|
||||||
let request = BatchGetRecordsRequest {
|
let request = BatchGetRecordsRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
record_ids: vec![1, 2],
|
record_ids: vec![1, 2],
|
||||||
field_ids: Some(vec![4804]),
|
field_ids: Some(vec![4804]),
|
||||||
data_format: Some(DataFormat::Formatted),
|
data_format: Some(DataFormat::Formatted),
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.batch_get_records(request).await?;
|
let res = client.batch_get_records(request).await?;
|
||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -456,17 +456,17 @@ Returns a paged collection of records based on a criteria that can be paged thro
|
|||||||
use onspring::models::record::QueryRecordsRequest;
|
use onspring::models::record::QueryRecordsRequest;
|
||||||
|
|
||||||
let request = QueryRecordsRequest {
|
let request = QueryRecordsRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
filter: "not (4745 eq 0)".to_string(),
|
filter: "not (4745 eq 0)".to_string(),
|
||||||
field_ids: None,
|
field_ids: None,
|
||||||
data_format: None,
|
data_format: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.query_records(request, None).await?;
|
let res = client.query_records(request, None).await?;
|
||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -476,10 +476,10 @@ You can set your own page size and page number (max is 1,000) as well. In additi
|
|||||||
use onspring::{DataFormat, PagingRequest, models::record::QueryRecordsRequest};
|
use onspring::{DataFormat, PagingRequest, models::record::QueryRecordsRequest};
|
||||||
|
|
||||||
let request = QueryRecordsRequest {
|
let request = QueryRecordsRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
filter: "not (4745 eq 0)".to_string(),
|
filter: "not (4745 eq 0)".to_string(),
|
||||||
field_ids: Some(vec![4804]),
|
field_ids: Some(vec![4804]),
|
||||||
data_format: Some(DataFormat::Formatted),
|
data_format: Some(DataFormat::Formatted),
|
||||||
};
|
};
|
||||||
|
|
||||||
let paging = PagingRequest { page_number: 1, page_size: 10 };
|
let paging = PagingRequest { page_number: 1, page_size: 10 };
|
||||||
@@ -487,7 +487,7 @@ let res = client.query_records(request, Some(paging)).await?;
|
|||||||
let records = res.items.unwrap_or_default();
|
let records = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for record in &records {
|
for record in &records {
|
||||||
println!("{:?}", record);
|
println!("{:?}", record);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -505,9 +505,9 @@ let mut fields = HashMap::new();
|
|||||||
fields.insert("4804".to_string(), serde_json::json!("Test"));
|
fields.insert("4804".to_string(), serde_json::json!("Test"));
|
||||||
|
|
||||||
let request = SaveRecordRequest {
|
let request = SaveRecordRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
record_id: None,
|
record_id: None,
|
||||||
fields,
|
fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.save_record(request).await?;
|
let res = client.save_record(request).await?;
|
||||||
@@ -524,9 +524,9 @@ let mut fields = HashMap::new();
|
|||||||
fields.insert("4804".to_string(), serde_json::json!("Updated"));
|
fields.insert("4804".to_string(), serde_json::json!("Updated"));
|
||||||
|
|
||||||
let request = SaveRecordRequest {
|
let request = SaveRecordRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
record_id: Some(607),
|
record_id: Some(607),
|
||||||
fields,
|
fields,
|
||||||
};
|
};
|
||||||
|
|
||||||
let res = client.save_record(request).await?;
|
let res = client.save_record(request).await?;
|
||||||
@@ -550,8 +550,8 @@ Delete a batch of records based upon their ids.
|
|||||||
use onspring::models::record::BatchDeleteRecordsRequest;
|
use onspring::models::record::BatchDeleteRecordsRequest;
|
||||||
|
|
||||||
let request = BatchDeleteRecordsRequest {
|
let request = BatchDeleteRecordsRequest {
|
||||||
app_id: 130,
|
app_id: 130,
|
||||||
record_ids: vec![608, 609],
|
record_ids: vec![608, 609],
|
||||||
};
|
};
|
||||||
|
|
||||||
client.batch_delete_records(request).await?;
|
client.batch_delete_records(request).await?;
|
||||||
@@ -575,9 +575,9 @@ You can also specify the format of the data in the report as well as whether you
|
|||||||
use onspring::{DataFormat, ReportDataType};
|
use onspring::{DataFormat, ReportDataType};
|
||||||
|
|
||||||
let report = client.get_report(
|
let report = client.get_report(
|
||||||
409,
|
409,
|
||||||
Some(DataFormat::Formatted),
|
Some(DataFormat::Formatted),
|
||||||
Some(ReportDataType::ChartData),
|
Some(ReportDataType::ChartData),
|
||||||
).await?;
|
).await?;
|
||||||
println!("{:?}", report);
|
println!("{:?}", report);
|
||||||
```
|
```
|
||||||
@@ -591,7 +591,7 @@ let res = client.list_reports(130, None).await?;
|
|||||||
let reports = res.items.unwrap_or_default();
|
let reports = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for report in &reports {
|
for report in &reports {
|
||||||
println!("{:?}", report);
|
println!("{:?}", report);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -605,6 +605,6 @@ let res = client.list_reports(130, Some(paging)).await?;
|
|||||||
let reports = res.items.unwrap_or_default();
|
let reports = res.items.unwrap_or_default();
|
||||||
|
|
||||||
for report in &reports {
|
for report in &reports {
|
||||||
println!("{:?}", report);
|
println!("{:?}", report);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
tab_spaces = 2
|
||||||
+180
-180
@@ -10,221 +10,221 @@ use crate::error::{OnspringError, Result};
|
|||||||
|
|
||||||
/// Client for interacting with the Onspring API v2.
|
/// Client for interacting with the Onspring API v2.
|
||||||
pub struct OnspringClient {
|
pub struct OnspringClient {
|
||||||
http_client: reqwest::Client,
|
http_client: reqwest::Client,
|
||||||
base_url: String,
|
base_url: String,
|
||||||
api_key: String,
|
api_key: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder for constructing an [`OnspringClient`].
|
/// Builder for constructing an [`OnspringClient`].
|
||||||
pub struct OnspringClientBuilder {
|
pub struct OnspringClientBuilder {
|
||||||
base_url: String,
|
base_url: String,
|
||||||
api_key: String,
|
api_key: String,
|
||||||
http_client: Option<reqwest::Client>,
|
http_client: Option<reqwest::Client>,
|
||||||
timeout: Option<Duration>,
|
timeout: Option<Duration>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OnspringClientBuilder {
|
impl OnspringClientBuilder {
|
||||||
/// Creates a new builder with the given API key.
|
/// Creates a new builder with the given API key.
|
||||||
pub fn new(api_key: impl Into<String>) -> Self {
|
pub fn new(api_key: impl Into<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base_url: "https://api.onspring.com".to_string(),
|
base_url: "https://api.onspring.com".to_string(),
|
||||||
api_key: api_key.into(),
|
api_key: api_key.into(),
|
||||||
http_client: None,
|
http_client: None,
|
||||||
timeout: None,
|
timeout: None,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the base URL for the API.
|
/// Sets the base URL for the API.
|
||||||
pub fn base_url(mut self, url: impl Into<String>) -> Self {
|
pub fn base_url(mut self, url: impl Into<String>) -> Self {
|
||||||
self.base_url = url.into();
|
self.base_url = url.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets a custom `reqwest::Client` to use for HTTP requests.
|
/// Sets a custom `reqwest::Client` to use for HTTP requests.
|
||||||
pub fn http_client(mut self, client: reqwest::Client) -> Self {
|
pub fn http_client(mut self, client: reqwest::Client) -> Self {
|
||||||
self.http_client = Some(client);
|
self.http_client = Some(client);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the request timeout.
|
/// Sets the request timeout.
|
||||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||||
self.timeout = Some(timeout);
|
self.timeout = Some(timeout);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the [`OnspringClient`].
|
/// Builds the [`OnspringClient`].
|
||||||
pub fn build(self) -> OnspringClient {
|
pub fn build(self) -> OnspringClient {
|
||||||
let http_client = self.http_client.unwrap_or_else(|| {
|
let http_client = self.http_client.unwrap_or_else(|| {
|
||||||
reqwest::Client::builder()
|
reqwest::Client::builder()
|
||||||
.timeout(self.timeout.unwrap_or(Duration::from_secs(120)))
|
.timeout(self.timeout.unwrap_or(Duration::from_secs(120)))
|
||||||
.build()
|
.build()
|
||||||
.expect("failed to build HTTP client")
|
.expect("failed to build HTTP client")
|
||||||
});
|
});
|
||||||
|
|
||||||
OnspringClient {
|
OnspringClient {
|
||||||
http_client,
|
http_client,
|
||||||
base_url: self.base_url.trim_end_matches('/').to_string(),
|
base_url: self.base_url.trim_end_matches('/').to_string(),
|
||||||
api_key: self.api_key,
|
api_key: self.api_key,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Creates a new [`OnspringClientBuilder`].
|
/// Creates a new [`OnspringClientBuilder`].
|
||||||
pub fn builder(api_key: impl Into<String>) -> OnspringClientBuilder {
|
pub fn builder(api_key: impl Into<String>) -> OnspringClientBuilder {
|
||||||
OnspringClientBuilder::new(api_key)
|
OnspringClientBuilder::new(api_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_headers(&self) -> HeaderMap {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(
|
||||||
|
"x-apikey",
|
||||||
|
HeaderValue::from_str(&self.api_key).expect("invalid API key"),
|
||||||
|
);
|
||||||
|
headers.insert("x-api-version", HeaderValue::from_static("2"));
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn request<T: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
path: &str,
|
||||||
|
query: &[(&str, String)],
|
||||||
|
body: Option<&impl Serialize>,
|
||||||
|
) -> Result<T> {
|
||||||
|
let url = format!("{}{}", self.base_url, path);
|
||||||
|
let mut req = self
|
||||||
|
.http_client
|
||||||
|
.request(method, &url)
|
||||||
|
.headers(self.default_headers())
|
||||||
|
.query(query);
|
||||||
|
|
||||||
|
if let Some(body) = body {
|
||||||
|
req = req.json(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_headers(&self) -> HeaderMap {
|
let response = req.send().await?;
|
||||||
let mut headers = HeaderMap::new();
|
let status = response.status();
|
||||||
headers.insert(
|
|
||||||
"x-apikey",
|
if !status.is_success() {
|
||||||
HeaderValue::from_str(&self.api_key).expect("invalid API key"),
|
let message = response
|
||||||
);
|
.json::<serde_json::Value>()
|
||||||
headers.insert("x-api-version", HeaderValue::from_static("2"));
|
.await
|
||||||
headers
|
.ok()
|
||||||
|
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
||||||
|
.unwrap_or_default();
|
||||||
|
return Err(OnspringError::Api {
|
||||||
|
status_code: status.as_u16(),
|
||||||
|
message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn request<T: DeserializeOwned>(
|
let body = response.text().await?;
|
||||||
&self,
|
serde_json::from_str(&body).map_err(OnspringError::Serialization)
|
||||||
method: Method,
|
}
|
||||||
path: &str,
|
|
||||||
query: &[(&str, String)],
|
|
||||||
body: Option<&impl Serialize>,
|
|
||||||
) -> Result<T> {
|
|
||||||
let url = format!("{}{}", self.base_url, path);
|
|
||||||
let mut req = self
|
|
||||||
.http_client
|
|
||||||
.request(method, &url)
|
|
||||||
.headers(self.default_headers())
|
|
||||||
.query(query);
|
|
||||||
|
|
||||||
if let Some(body) = body {
|
pub(crate) async fn request_no_content(
|
||||||
req = req.json(body);
|
&self,
|
||||||
}
|
method: Method,
|
||||||
|
path: &str,
|
||||||
|
query: &[(&str, String)],
|
||||||
|
body: Option<&impl Serialize>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let url = format!("{}{}", self.base_url, path);
|
||||||
|
let mut req = self
|
||||||
|
.http_client
|
||||||
|
.request(method, &url)
|
||||||
|
.headers(self.default_headers())
|
||||||
|
.query(query);
|
||||||
|
|
||||||
let response = req.send().await?;
|
if let Some(body) = body {
|
||||||
let status = response.status();
|
req = req.json(body);
|
||||||
|
|
||||||
if !status.is_success() {
|
|
||||||
let message = response
|
|
||||||
.json::<serde_json::Value>()
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Err(OnspringError::Api {
|
|
||||||
status_code: status.as_u16(),
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = response.text().await?;
|
|
||||||
serde_json::from_str(&body).map_err(OnspringError::Serialization)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn request_no_content(
|
let response = req.send().await?;
|
||||||
&self,
|
let status = response.status();
|
||||||
method: Method,
|
|
||||||
path: &str,
|
|
||||||
query: &[(&str, String)],
|
|
||||||
body: Option<&impl Serialize>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let url = format!("{}{}", self.base_url, path);
|
|
||||||
let mut req = self
|
|
||||||
.http_client
|
|
||||||
.request(method, &url)
|
|
||||||
.headers(self.default_headers())
|
|
||||||
.query(query);
|
|
||||||
|
|
||||||
if let Some(body) = body {
|
if !status.is_success() {
|
||||||
req = req.json(body);
|
let message = response
|
||||||
}
|
.json::<serde_json::Value>()
|
||||||
|
.await
|
||||||
let response = req.send().await?;
|
.ok()
|
||||||
let status = response.status();
|
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
||||||
|
.unwrap_or_default();
|
||||||
if !status.is_success() {
|
return Err(OnspringError::Api {
|
||||||
let message = response
|
status_code: status.as_u16(),
|
||||||
.json::<serde_json::Value>()
|
message,
|
||||||
.await
|
});
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Err(OnspringError::Api {
|
|
||||||
status_code: status.as_u16(),
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn request_bytes(
|
Ok(())
|
||||||
&self,
|
}
|
||||||
method: Method,
|
|
||||||
path: &str,
|
|
||||||
query: &[(&str, String)],
|
|
||||||
) -> Result<(StatusCode, HeaderMap, Bytes)> {
|
|
||||||
let url = format!("{}{}", self.base_url, path);
|
|
||||||
let response = self
|
|
||||||
.http_client
|
|
||||||
.request(method, &url)
|
|
||||||
.headers(self.default_headers())
|
|
||||||
.query(query)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let status = response.status();
|
pub(crate) async fn request_bytes(
|
||||||
|
&self,
|
||||||
|
method: Method,
|
||||||
|
path: &str,
|
||||||
|
query: &[(&str, String)],
|
||||||
|
) -> Result<(StatusCode, HeaderMap, Bytes)> {
|
||||||
|
let url = format!("{}{}", self.base_url, path);
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.request(method, &url)
|
||||||
|
.headers(self.default_headers())
|
||||||
|
.query(query)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !status.is_success() {
|
let status = response.status();
|
||||||
let message = response
|
|
||||||
.json::<serde_json::Value>()
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Err(OnspringError::Api {
|
|
||||||
status_code: status.as_u16(),
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let headers = response.headers().clone();
|
if !status.is_success() {
|
||||||
let bytes = response.bytes().await?;
|
let message = response
|
||||||
Ok((status, headers, bytes))
|
.json::<serde_json::Value>()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
||||||
|
.unwrap_or_default();
|
||||||
|
return Err(OnspringError::Api {
|
||||||
|
status_code: status.as_u16(),
|
||||||
|
message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn request_multipart<T: DeserializeOwned>(
|
let headers = response.headers().clone();
|
||||||
&self,
|
let bytes = response.bytes().await?;
|
||||||
path: &str,
|
Ok((status, headers, bytes))
|
||||||
form: reqwest::multipart::Form,
|
}
|
||||||
) -> Result<T> {
|
|
||||||
let url = format!("{}{}", self.base_url, path);
|
|
||||||
let response = self
|
|
||||||
.http_client
|
|
||||||
.post(&url)
|
|
||||||
.headers(self.default_headers())
|
|
||||||
.multipart(form)
|
|
||||||
.send()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let status = response.status();
|
pub(crate) async fn request_multipart<T: DeserializeOwned>(
|
||||||
|
&self,
|
||||||
|
path: &str,
|
||||||
|
form: reqwest::multipart::Form,
|
||||||
|
) -> Result<T> {
|
||||||
|
let url = format!("{}{}", self.base_url, path);
|
||||||
|
let response = self
|
||||||
|
.http_client
|
||||||
|
.post(&url)
|
||||||
|
.headers(self.default_headers())
|
||||||
|
.multipart(form)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !status.is_success() {
|
let status = response.status();
|
||||||
let message = response
|
|
||||||
.json::<serde_json::Value>()
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
|
||||||
.unwrap_or_default();
|
|
||||||
return Err(OnspringError::Api {
|
|
||||||
status_code: status.as_u16(),
|
|
||||||
message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let body = response.text().await?;
|
if !status.is_success() {
|
||||||
serde_json::from_str(&body).map_err(OnspringError::Serialization)
|
let message = response
|
||||||
|
.json::<serde_json::Value>()
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.and_then(|v| v.get("message")?.as_str().map(String::from))
|
||||||
|
.unwrap_or_default();
|
||||||
|
return Err(OnspringError::Api {
|
||||||
|
status_code: status.as_u16(),
|
||||||
|
message,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let body = response.text().await?;
|
||||||
|
serde_json::from_str(&body).map_err(OnspringError::Serialization)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-21
@@ -5,28 +5,31 @@ use crate::error::Result;
|
|||||||
use crate::models::{App, CollectionResponse, PagedResponse, PagingRequest};
|
use crate::models::{App, CollectionResponse, PagedResponse, PagingRequest};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Gets all apps for the current client, with optional pagination.
|
/// Gets all apps for the current client, with optional pagination.
|
||||||
pub async fn list_apps(&self, paging: Option<PagingRequest>) -> Result<PagedResponse<App>> {
|
pub async fn list_apps(&self, paging: Option<PagingRequest>) -> Result<PagedResponse<App>> {
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(p) = paging {
|
if let Some(p) = paging {
|
||||||
query.push(("PageNumber", p.page_number.to_string()));
|
query.push(("PageNumber", p.page_number.to_string()));
|
||||||
query.push(("PageSize", p.page_size.to_string()));
|
query.push(("PageSize", p.page_size.to_string()));
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, "/Apps", &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, "/Apps", &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets an app by its identifier.
|
/// Gets an app by its identifier.
|
||||||
pub async fn get_app(&self, app_id: i32) -> Result<App> {
|
pub async fn get_app(&self, app_id: i32) -> Result<App> {
|
||||||
let path = format!("/Apps/id/{}", app_id);
|
let path = format!("/Apps/id/{}", app_id);
|
||||||
self.request(Method::GET, &path, &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request(Method::GET, &path, &[], Option::<&()>::None)
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets up to 100 apps by their identifiers.
|
/// Gets up to 100 apps by their identifiers.
|
||||||
pub async fn batch_get_apps(&self, ids: &[i32]) -> Result<CollectionResponse<App>> {
|
pub async fn batch_get_apps(&self, ids: &[i32]) -> Result<CollectionResponse<App>> {
|
||||||
self.request(Method::POST, "/Apps/batch-get", &[], Some(&ids))
|
self
|
||||||
.await
|
.request(Method::POST, "/Apps/batch-get", &[], Some(&ids))
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-26
@@ -5,33 +5,36 @@ use crate::error::Result;
|
|||||||
use crate::models::{CollectionResponse, Field, PagedResponse, PagingRequest};
|
use crate::models::{CollectionResponse, Field, PagedResponse, PagingRequest};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Gets a field by its identifier.
|
/// Gets a field by its identifier.
|
||||||
pub async fn get_field(&self, field_id: i32) -> Result<Field> {
|
pub async fn get_field(&self, field_id: i32) -> Result<Field> {
|
||||||
let path = format!("/Fields/id/{}", field_id);
|
let path = format!("/Fields/id/{}", field_id);
|
||||||
self.request(Method::GET, &path, &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request(Method::GET, &path, &[], Option::<&()>::None)
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets up to 100 fields by their identifiers.
|
/// Gets up to 100 fields by their identifiers.
|
||||||
pub async fn batch_get_fields(&self, ids: &[i32]) -> Result<CollectionResponse<Field>> {
|
pub async fn batch_get_fields(&self, ids: &[i32]) -> Result<CollectionResponse<Field>> {
|
||||||
self.request(Method::POST, "/Fields/batch-get", &[], Some(&ids))
|
self
|
||||||
.await
|
.request(Method::POST, "/Fields/batch-get", &[], Some(&ids))
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a paginated list of fields for a given application.
|
/// Gets a paginated list of fields for a given application.
|
||||||
pub async fn list_fields(
|
pub async fn list_fields(
|
||||||
&self,
|
&self,
|
||||||
app_id: i32,
|
app_id: i32,
|
||||||
paging: Option<PagingRequest>,
|
paging: Option<PagingRequest>,
|
||||||
) -> Result<PagedResponse<Field>> {
|
) -> Result<PagedResponse<Field>> {
|
||||||
let path = format!("/Fields/appId/{}", app_id);
|
let path = format!("/Fields/appId/{}", app_id);
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(p) = paging {
|
if let Some(p) = paging {
|
||||||
query.push(("PageNumber", p.page_number.to_string()));
|
query.push(("PageNumber", p.page_number.to_string()));
|
||||||
query.push(("PageSize", p.page_size.to_string()));
|
query.push(("PageSize", p.page_size.to_string()));
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-77
@@ -5,84 +5,86 @@ use crate::error::Result;
|
|||||||
use crate::models::{CreatedWithIdResponse, FileInfo, FileResponse, SaveFileRequest};
|
use crate::models::{CreatedWithIdResponse, FileInfo, FileResponse, SaveFileRequest};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Gets a file's metadata information.
|
/// Gets a file's metadata information.
|
||||||
pub async fn get_file_info(
|
pub async fn get_file_info(
|
||||||
&self,
|
&self,
|
||||||
record_id: i32,
|
record_id: i32,
|
||||||
field_id: i32,
|
field_id: i32,
|
||||||
file_id: i32,
|
file_id: i32,
|
||||||
) -> Result<FileInfo> {
|
) -> Result<FileInfo> {
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"/Files/recordId/{}/fieldId/{}/fileId/{}",
|
"/Files/recordId/{}/fieldId/{}/fileId/{}",
|
||||||
record_id, field_id, file_id
|
record_id, field_id, file_id
|
||||||
);
|
);
|
||||||
self.request(Method::GET, &path, &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request(Method::GET, &path, &[], Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Downloads a file's content.
|
||||||
|
pub async fn get_file(
|
||||||
|
&self,
|
||||||
|
record_id: i32,
|
||||||
|
field_id: i32,
|
||||||
|
file_id: i32,
|
||||||
|
) -> Result<FileResponse> {
|
||||||
|
let path = format!(
|
||||||
|
"/Files/recordId/{}/fieldId/{}/fileId/{}/file",
|
||||||
|
record_id, field_id, file_id
|
||||||
|
);
|
||||||
|
let (_status, headers, data) = self.request_bytes(Method::GET, &path, &[]).await?;
|
||||||
|
|
||||||
|
let content_type = headers
|
||||||
|
.get("content-type")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.map(String::from);
|
||||||
|
|
||||||
|
let file_name = headers
|
||||||
|
.get("content-disposition")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|v| {
|
||||||
|
v.split("filename=")
|
||||||
|
.nth(1)
|
||||||
|
.map(|s| s.trim_matches('"').to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(FileResponse {
|
||||||
|
content_type,
|
||||||
|
file_name,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uploads a file attachment.
|
||||||
|
pub async fn upload_file(&self, request: SaveFileRequest) -> Result<CreatedWithIdResponse> {
|
||||||
|
let file_part = reqwest::multipart::Part::bytes(request.file_data)
|
||||||
|
.file_name(request.file_name)
|
||||||
|
.mime_str(&request.content_type)
|
||||||
|
.map_err(|e| crate::error::OnspringError::InvalidArgument(e.to_string()))?;
|
||||||
|
|
||||||
|
let mut form = reqwest::multipart::Form::new()
|
||||||
|
.text("RecordId", request.record_id.to_string())
|
||||||
|
.text("FieldId", request.field_id.to_string())
|
||||||
|
.part("File", file_part);
|
||||||
|
|
||||||
|
if let Some(notes) = request.notes {
|
||||||
|
form = form.text("Notes", notes);
|
||||||
|
}
|
||||||
|
if let Some(date) = request.modified_date {
|
||||||
|
form = form.text("ModifiedDate", date.to_rfc3339());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Downloads a file's content.
|
self.request_multipart("/Files", form).await
|
||||||
pub async fn get_file(
|
}
|
||||||
&self,
|
|
||||||
record_id: i32,
|
|
||||||
field_id: i32,
|
|
||||||
file_id: i32,
|
|
||||||
) -> Result<FileResponse> {
|
|
||||||
let path = format!(
|
|
||||||
"/Files/recordId/{}/fieldId/{}/fileId/{}/file",
|
|
||||||
record_id, field_id, file_id
|
|
||||||
);
|
|
||||||
let (_status, headers, data) = self.request_bytes(Method::GET, &path, &[]).await?;
|
|
||||||
|
|
||||||
let content_type = headers
|
/// Deletes a file attachment.
|
||||||
.get("content-type")
|
pub async fn delete_file(&self, record_id: i32, field_id: i32, file_id: i32) -> Result<()> {
|
||||||
.and_then(|v| v.to_str().ok())
|
let path = format!(
|
||||||
.map(String::from);
|
"/Files/recordId/{}/fieldId/{}/fileId/{}",
|
||||||
|
record_id, field_id, file_id
|
||||||
let file_name = headers
|
);
|
||||||
.get("content-disposition")
|
self
|
||||||
.and_then(|v| v.to_str().ok())
|
.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
||||||
.and_then(|v| {
|
.await
|
||||||
v.split("filename=")
|
}
|
||||||
.nth(1)
|
|
||||||
.map(|s| s.trim_matches('"').to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(FileResponse {
|
|
||||||
content_type,
|
|
||||||
file_name,
|
|
||||||
data,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Uploads a file attachment.
|
|
||||||
pub async fn upload_file(&self, request: SaveFileRequest) -> Result<CreatedWithIdResponse> {
|
|
||||||
let file_part = reqwest::multipart::Part::bytes(request.file_data)
|
|
||||||
.file_name(request.file_name)
|
|
||||||
.mime_str(&request.content_type)
|
|
||||||
.map_err(|e| crate::error::OnspringError::InvalidArgument(e.to_string()))?;
|
|
||||||
|
|
||||||
let mut form = reqwest::multipart::Form::new()
|
|
||||||
.text("RecordId", request.record_id.to_string())
|
|
||||||
.text("FieldId", request.field_id.to_string())
|
|
||||||
.part("File", file_part);
|
|
||||||
|
|
||||||
if let Some(notes) = request.notes {
|
|
||||||
form = form.text("Notes", notes);
|
|
||||||
}
|
|
||||||
if let Some(date) = request.modified_date {
|
|
||||||
form = form.text("ModifiedDate", date.to_rfc3339());
|
|
||||||
}
|
|
||||||
|
|
||||||
self.request_multipart("/Files", form).await
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes a file attachment.
|
|
||||||
pub async fn delete_file(&self, record_id: i32, field_id: i32, file_id: i32) -> Result<()> {
|
|
||||||
let path = format!(
|
|
||||||
"/Files/recordId/{}/fieldId/{}/fileId/{}",
|
|
||||||
record_id, field_id, file_id
|
|
||||||
);
|
|
||||||
self.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-15
@@ -6,20 +6,21 @@ use crate::error::Result;
|
|||||||
use crate::models::{SaveListItemRequest, SaveListItemResponse};
|
use crate::models::{SaveListItemRequest, SaveListItemResponse};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Creates or updates a list item in the specified list.
|
/// Creates or updates a list item in the specified list.
|
||||||
pub async fn save_list_item(
|
pub async fn save_list_item(
|
||||||
&self,
|
&self,
|
||||||
list_id: i32,
|
list_id: i32,
|
||||||
request: SaveListItemRequest,
|
request: SaveListItemRequest,
|
||||||
) -> Result<SaveListItemResponse> {
|
) -> Result<SaveListItemResponse> {
|
||||||
let path = format!("/Lists/id/{}/items", list_id);
|
let path = format!("/Lists/id/{}/items", list_id);
|
||||||
self.request(Method::PUT, &path, &[], Some(&request)).await
|
self.request(Method::PUT, &path, &[], Some(&request)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes a list item from the specified list.
|
/// Deletes a list item from the specified list.
|
||||||
pub async fn delete_list_item(&self, list_id: i32, item_id: Uuid) -> Result<()> {
|
pub async fn delete_list_item(&self, list_id: i32, item_id: Uuid) -> Result<()> {
|
||||||
let path = format!("/Lists/id/{}/itemId/{}", list_id, item_id);
|
let path = format!("/Lists/id/{}/itemId/{}", list_id, item_id);
|
||||||
self.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ use crate::client::OnspringClient;
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Checks if the Onspring API is reachable.
|
/// Checks if the Onspring API is reachable.
|
||||||
pub async fn ping(&self) -> Result<()> {
|
pub async fn ping(&self) -> Result<()> {
|
||||||
self.request_no_content(Method::GET, "/Ping", &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request_no_content(Method::GET, "/Ping", &[], Option::<&()>::None)
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+99
-93
@@ -3,109 +3,115 @@ use reqwest::Method;
|
|||||||
use crate::client::OnspringClient;
|
use crate::client::OnspringClient;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
BatchDeleteRecordsRequest, BatchGetRecordsRequest, CollectionResponse, DataFormat,
|
BatchDeleteRecordsRequest, BatchGetRecordsRequest, CollectionResponse, DataFormat, PagedResponse,
|
||||||
PagedResponse, PagingRequest, QueryRecordsRequest, Record, SaveRecordRequest,
|
PagingRequest, QueryRecordsRequest, Record, SaveRecordRequest, SaveRecordResponse,
|
||||||
SaveRecordResponse,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Gets a paginated collection of records for a given app.
|
/// Gets a paginated collection of records for a given app.
|
||||||
pub async fn list_records(
|
pub async fn list_records(
|
||||||
&self,
|
&self,
|
||||||
app_id: i32,
|
app_id: i32,
|
||||||
paging: Option<PagingRequest>,
|
paging: Option<PagingRequest>,
|
||||||
field_ids: Option<&[i32]>,
|
field_ids: Option<&[i32]>,
|
||||||
data_format: Option<DataFormat>,
|
data_format: Option<DataFormat>,
|
||||||
) -> Result<PagedResponse<Record>> {
|
) -> Result<PagedResponse<Record>> {
|
||||||
let path = format!("/Records/appId/{}", app_id);
|
let path = format!("/Records/appId/{}", app_id);
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(p) = paging {
|
if let Some(p) = paging {
|
||||||
query.push(("PageNumber", p.page_number.to_string()));
|
query.push(("PageNumber", p.page_number.to_string()));
|
||||||
query.push(("PageSize", p.page_size.to_string()));
|
query.push(("PageSize", p.page_size.to_string()));
|
||||||
}
|
|
||||||
if let Some(ids) = field_ids {
|
|
||||||
let ids_str = ids
|
|
||||||
.iter()
|
|
||||||
.map(|id| id.to_string())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(",");
|
|
||||||
query.push(("fieldIds", ids_str));
|
|
||||||
}
|
|
||||||
if let Some(fmt) = data_format {
|
|
||||||
query.push(("dataFormat", format!("{:?}", fmt)));
|
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
if let Some(ids) = field_ids {
|
||||||
|
let ids_str = ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| id.to_string())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",");
|
||||||
|
query.push(("fieldIds", ids_str));
|
||||||
|
}
|
||||||
|
if let Some(fmt) = data_format {
|
||||||
|
query.push(("dataFormat", format!("{:?}", fmt)));
|
||||||
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a record by its identifier.
|
/// Gets a record by its identifier.
|
||||||
pub async fn get_record(
|
pub async fn get_record(
|
||||||
&self,
|
&self,
|
||||||
app_id: i32,
|
app_id: i32,
|
||||||
record_id: i32,
|
record_id: i32,
|
||||||
field_ids: Option<&[i32]>,
|
field_ids: Option<&[i32]>,
|
||||||
data_format: Option<DataFormat>,
|
data_format: Option<DataFormat>,
|
||||||
) -> Result<Record> {
|
) -> Result<Record> {
|
||||||
let path = format!("/Records/appId/{}/recordId/{}", app_id, record_id);
|
let path = format!("/Records/appId/{}/recordId/{}", app_id, record_id);
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(ids) = field_ids {
|
if let Some(ids) = field_ids {
|
||||||
let ids_str = ids
|
let ids_str = ids
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| id.to_string())
|
.map(|id| id.to_string())
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join(",");
|
.join(",");
|
||||||
query.push(("fieldIds", ids_str));
|
query.push(("fieldIds", ids_str));
|
||||||
}
|
|
||||||
if let Some(fmt) = data_format {
|
|
||||||
query.push(("dataFormat", format!("{:?}", fmt)));
|
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
if let Some(fmt) = data_format {
|
||||||
|
query.push(("dataFormat", format!("{:?}", fmt)));
|
||||||
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates or updates a record.
|
/// Creates or updates a record.
|
||||||
pub async fn save_record(&self, request: SaveRecordRequest) -> Result<SaveRecordResponse> {
|
pub async fn save_record(&self, request: SaveRecordRequest) -> Result<SaveRecordResponse> {
|
||||||
self.request(Method::PUT, "/Records", &[], Some(&request))
|
self
|
||||||
.await
|
.request(Method::PUT, "/Records", &[], Some(&request))
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes a record by its identifier.
|
/// Deletes a record by its identifier.
|
||||||
pub async fn delete_record(&self, app_id: i32, record_id: i32) -> Result<()> {
|
pub async fn delete_record(&self, app_id: i32, record_id: i32) -> Result<()> {
|
||||||
let path = format!("/Records/appId/{}/recordId/{}", app_id, record_id);
|
let path = format!("/Records/appId/{}/recordId/{}", app_id, record_id);
|
||||||
self.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
self
|
||||||
.await
|
.request_no_content(Method::DELETE, &path, &[], Option::<&()>::None)
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a batch of records.
|
/// Gets a batch of records.
|
||||||
pub async fn batch_get_records(
|
pub async fn batch_get_records(
|
||||||
&self,
|
&self,
|
||||||
request: BatchGetRecordsRequest,
|
request: BatchGetRecordsRequest,
|
||||||
) -> Result<CollectionResponse<Record>> {
|
) -> Result<CollectionResponse<Record>> {
|
||||||
self.request(Method::POST, "/Records/batch-get", &[], Some(&request))
|
self
|
||||||
.await
|
.request(Method::POST, "/Records/batch-get", &[], Some(&request))
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries records using a filter expression.
|
/// Queries records using a filter expression.
|
||||||
pub async fn query_records(
|
pub async fn query_records(
|
||||||
&self,
|
&self,
|
||||||
request: QueryRecordsRequest,
|
request: QueryRecordsRequest,
|
||||||
paging: Option<PagingRequest>,
|
paging: Option<PagingRequest>,
|
||||||
) -> Result<PagedResponse<Record>> {
|
) -> Result<PagedResponse<Record>> {
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(p) = paging {
|
if let Some(p) = paging {
|
||||||
query.push(("PageNumber", p.page_number.to_string()));
|
query.push(("PageNumber", p.page_number.to_string()));
|
||||||
query.push(("PageSize", p.page_size.to_string()));
|
query.push(("PageSize", p.page_size.to_string()));
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::POST, "/Records/Query", &query_refs, Some(&request))
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::POST, "/Records/Query", &query_refs, Some(&request))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes a batch of records.
|
/// Deletes a batch of records.
|
||||||
pub async fn batch_delete_records(&self, request: BatchDeleteRecordsRequest) -> Result<()> {
|
pub async fn batch_delete_records(&self, request: BatchDeleteRecordsRequest) -> Result<()> {
|
||||||
self.request_no_content(Method::POST, "/Records/batch-delete", &[], Some(&request))
|
self
|
||||||
.await
|
.request_no_content(Method::POST, "/Records/batch-delete", &[], Some(&request))
|
||||||
}
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-34
@@ -3,44 +3,46 @@ use reqwest::Method;
|
|||||||
use crate::client::OnspringClient;
|
use crate::client::OnspringClient;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
DataFormat, PagedResponse, PagingRequest, ReportData, ReportDataType, ReportInfo,
|
DataFormat, PagedResponse, PagingRequest, ReportData, ReportDataType, ReportInfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl OnspringClient {
|
impl OnspringClient {
|
||||||
/// Gets report data by report ID.
|
/// Gets report data by report ID.
|
||||||
pub async fn get_report(
|
pub async fn get_report(
|
||||||
&self,
|
&self,
|
||||||
report_id: i32,
|
report_id: i32,
|
||||||
data_format: Option<DataFormat>,
|
data_format: Option<DataFormat>,
|
||||||
data_type: Option<ReportDataType>,
|
data_type: Option<ReportDataType>,
|
||||||
) -> Result<ReportData> {
|
) -> Result<ReportData> {
|
||||||
let path = format!("/Reports/id/{}", report_id);
|
let path = format!("/Reports/id/{}", report_id);
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(fmt) = data_format {
|
if let Some(fmt) = data_format {
|
||||||
query.push(("apiDataFormat", format!("{:?}", fmt)));
|
query.push(("apiDataFormat", format!("{:?}", fmt)));
|
||||||
}
|
|
||||||
if let Some(dt) = data_type {
|
|
||||||
query.push(("dataType", format!("{:?}", dt)));
|
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
if let Some(dt) = data_type {
|
||||||
|
query.push(("dataType", format!("{:?}", dt)));
|
||||||
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets a paginated list of reports for a given application.
|
/// Gets a paginated list of reports for a given application.
|
||||||
pub async fn list_reports(
|
pub async fn list_reports(
|
||||||
&self,
|
&self,
|
||||||
app_id: i32,
|
app_id: i32,
|
||||||
paging: Option<PagingRequest>,
|
paging: Option<PagingRequest>,
|
||||||
) -> Result<PagedResponse<ReportInfo>> {
|
) -> Result<PagedResponse<ReportInfo>> {
|
||||||
let path = format!("/Reports/appId/{}", app_id);
|
let path = format!("/Reports/appId/{}", app_id);
|
||||||
let mut query = Vec::new();
|
let mut query = Vec::new();
|
||||||
if let Some(p) = paging {
|
if let Some(p) = paging {
|
||||||
query.push(("PageNumber", p.page_number.to_string()));
|
query.push(("PageNumber", p.page_number.to_string()));
|
||||||
query.push(("PageSize", p.page_size.to_string()));
|
query.push(("PageSize", p.page_size.to_string()));
|
||||||
}
|
|
||||||
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
|
||||||
self.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
let query_refs: Vec<(&str, String)> = query.iter().map(|(k, v)| (*k, v.clone())).collect();
|
||||||
|
self
|
||||||
|
.request(Method::GET, &path, &query_refs, Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-12
@@ -1,21 +1,21 @@
|
|||||||
/// Errors that can occur when using the Onspring API client.
|
/// Errors that can occur when using the Onspring API client.
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum OnspringError {
|
pub enum OnspringError {
|
||||||
/// An HTTP transport error occurred.
|
/// An HTTP transport error occurred.
|
||||||
#[error("HTTP request failed: {0}")]
|
#[error("HTTP request failed: {0}")]
|
||||||
Http(#[from] reqwest::Error),
|
Http(#[from] reqwest::Error),
|
||||||
|
|
||||||
/// The API returned a non-success status code.
|
/// The API returned a non-success status code.
|
||||||
#[error("API error (status {status_code}): {message}")]
|
#[error("API error (status {status_code}): {message}")]
|
||||||
Api { status_code: u16, message: String },
|
Api { status_code: u16, message: String },
|
||||||
|
|
||||||
/// A serialization or deserialization error occurred.
|
/// A serialization or deserialization error occurred.
|
||||||
#[error("Serialization error: {0}")]
|
#[error("Serialization error: {0}")]
|
||||||
Serialization(#[from] serde_json::Error),
|
Serialization(#[from] serde_json::Error),
|
||||||
|
|
||||||
/// An invalid argument was provided to an SDK method.
|
/// An invalid argument was provided to an SDK method.
|
||||||
#[error("Invalid argument: {0}")]
|
#[error("Invalid argument: {0}")]
|
||||||
InvalidArgument(String),
|
InvalidArgument(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type alias for `Result<T, OnspringError>`.
|
/// A type alias for `Result<T, OnspringError>`.
|
||||||
|
|||||||
+3
-3
@@ -4,7 +4,7 @@ use serde::Deserialize;
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub href: Option<String>,
|
pub href: Option<String>,
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-22
@@ -3,46 +3,46 @@ use serde::{Deserialize, Serialize};
|
|||||||
/// The format of data returned by the API.
|
/// The format of data returned by the API.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum DataFormat {
|
pub enum DataFormat {
|
||||||
Raw,
|
Raw,
|
||||||
Formatted,
|
Formatted,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of report data to retrieve.
|
/// The type of report data to retrieve.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum ReportDataType {
|
pub enum ReportDataType {
|
||||||
ReportData,
|
ReportData,
|
||||||
ChartData,
|
ChartData,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of a field's value in a record.
|
/// The type of a field's value in a record.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum ValueType {
|
pub enum ValueType {
|
||||||
String,
|
String,
|
||||||
Integer,
|
Integer,
|
||||||
Decimal,
|
Decimal,
|
||||||
Date,
|
Date,
|
||||||
TimeSpan,
|
TimeSpan,
|
||||||
Guid,
|
Guid,
|
||||||
StringList,
|
StringList,
|
||||||
IntegerList,
|
IntegerList,
|
||||||
GuidList,
|
GuidList,
|
||||||
AttachmentList,
|
AttachmentList,
|
||||||
ScoringGroupList,
|
ScoringGroupList,
|
||||||
FileList,
|
FileList,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The output type of a formula field.
|
/// The output type of a formula field.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum FormulaOutputType {
|
pub enum FormulaOutputType {
|
||||||
Text,
|
Text,
|
||||||
Numeric,
|
Numeric,
|
||||||
DateAndTime,
|
DateAndTime,
|
||||||
ListValue,
|
ListValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether a field allows single or multiple selections.
|
/// Whether a field allows single or multiple selections.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub enum Multiplicity {
|
pub enum Multiplicity {
|
||||||
SingleSelect,
|
SingleSelect,
|
||||||
MultiSelect,
|
MultiSelect,
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -7,29 +7,29 @@ use super::enums::{FormulaOutputType, Multiplicity};
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Field {
|
pub struct Field {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub field_type: Option<String>,
|
pub field_type: Option<String>,
|
||||||
pub status: Option<String>,
|
pub status: Option<String>,
|
||||||
pub is_required: bool,
|
pub is_required: bool,
|
||||||
pub is_unique: bool,
|
pub is_unique: bool,
|
||||||
pub multiplicity: Option<Multiplicity>,
|
pub multiplicity: Option<Multiplicity>,
|
||||||
pub list_id: Option<i32>,
|
pub list_id: Option<i32>,
|
||||||
pub values: Option<Vec<ListFieldValue>>,
|
pub values: Option<Vec<ListFieldValue>>,
|
||||||
#[serde(rename = "outputType")]
|
#[serde(rename = "outputType")]
|
||||||
pub output_type: Option<FormulaOutputType>,
|
pub output_type: Option<FormulaOutputType>,
|
||||||
pub referenced_app_id: Option<i32>,
|
pub referenced_app_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a value in a list field.
|
/// Represents a value in a list field.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ListFieldValue {
|
pub struct ListFieldValue {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sort_order: i32,
|
pub sort_order: i32,
|
||||||
pub numeric_value: Option<f64>,
|
pub numeric_value: Option<f64>,
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -6,40 +6,40 @@ use serde::Deserialize;
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct FileInfo {
|
pub struct FileInfo {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub file_type: Option<String>,
|
pub file_type: Option<String>,
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub created_date: Option<DateTime<Utc>>,
|
pub created_date: Option<DateTime<Utc>>,
|
||||||
pub modified_date: Option<DateTime<Utc>>,
|
pub modified_date: Option<DateTime<Utc>>,
|
||||||
pub owner: Option<String>,
|
pub owner: Option<String>,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub file_href: Option<String>,
|
pub file_href: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The content of a downloaded file.
|
/// The content of a downloaded file.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FileResponse {
|
pub struct FileResponse {
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
pub file_name: Option<String>,
|
pub file_name: Option<String>,
|
||||||
pub data: Bytes,
|
pub data: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to upload a file.
|
/// Request to upload a file.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SaveFileRequest {
|
pub struct SaveFileRequest {
|
||||||
pub record_id: i32,
|
pub record_id: i32,
|
||||||
pub field_id: i32,
|
pub field_id: i32,
|
||||||
pub notes: Option<String>,
|
pub notes: Option<String>,
|
||||||
pub modified_date: Option<DateTime<Utc>>,
|
pub modified_date: Option<DateTime<Utc>>,
|
||||||
pub file_name: String,
|
pub file_name: String,
|
||||||
pub file_data: Vec<u8>,
|
pub file_data: Vec<u8>,
|
||||||
pub content_type: String,
|
pub content_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response from creating a file, containing the new file ID.
|
/// Response from creating a file, containing the new file ID.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CreatedWithIdResponse {
|
pub struct CreatedWithIdResponse {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -5,18 +5,18 @@ use uuid::Uuid;
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SaveListItemRequest {
|
pub struct SaveListItemRequest {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub id: Option<Uuid>,
|
pub id: Option<Uuid>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub numeric_value: Option<f64>,
|
pub numeric_value: Option<f64>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response from saving a list item.
|
/// Response from saving a list item.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SaveListItemResponse {
|
pub struct SaveListItemResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-14
@@ -3,34 +3,34 @@ use serde::Deserialize;
|
|||||||
/// Parameters for paginated API requests.
|
/// Parameters for paginated API requests.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PagingRequest {
|
pub struct PagingRequest {
|
||||||
pub page_number: i32,
|
pub page_number: i32,
|
||||||
pub page_size: i32,
|
pub page_size: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PagingRequest {
|
impl Default for PagingRequest {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
page_number: 1,
|
page_number: 1,
|
||||||
page_size: 50,
|
page_size: 50,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A paginated response from the API.
|
/// A paginated response from the API.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PagedResponse<T> {
|
pub struct PagedResponse<T> {
|
||||||
pub page_number: Option<i32>,
|
pub page_number: Option<i32>,
|
||||||
pub page_size: Option<i32>,
|
pub page_size: Option<i32>,
|
||||||
pub total_pages: Option<i32>,
|
pub total_pages: Option<i32>,
|
||||||
pub total_records: Option<i32>,
|
pub total_records: Option<i32>,
|
||||||
pub items: Option<Vec<T>>,
|
pub items: Option<Vec<T>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A collection response from the API (non-paginated).
|
/// A collection response from the API (non-paginated).
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CollectionResponse<T> {
|
pub struct CollectionResponse<T> {
|
||||||
pub count: Option<i32>,
|
pub count: Option<i32>,
|
||||||
pub items: Option<Vec<T>>,
|
pub items: Option<Vec<T>>,
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-27
@@ -8,67 +8,67 @@ use super::enums::{DataFormat, ValueType};
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Record {
|
pub struct Record {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub record_id: i32,
|
pub record_id: i32,
|
||||||
pub field_data: Option<Vec<RecordFieldValue>>,
|
pub field_data: Option<Vec<RecordFieldValue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a single field value within a record.
|
/// Represents a single field value within a record.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct RecordFieldValue {
|
pub struct RecordFieldValue {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub value_type: ValueType,
|
pub value_type: ValueType,
|
||||||
pub field_id: i32,
|
pub field_id: i32,
|
||||||
pub value: serde_json::Value,
|
pub value: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to create or update a record.
|
/// Request to create or update a record.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SaveRecordRequest {
|
pub struct SaveRecordRequest {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub record_id: Option<i32>,
|
pub record_id: Option<i32>,
|
||||||
pub fields: HashMap<String, serde_json::Value>,
|
pub fields: HashMap<String, serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response from saving a record.
|
/// Response from saving a record.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SaveRecordResponse {
|
pub struct SaveRecordResponse {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub warnings: Option<Vec<String>>,
|
pub warnings: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to query records with a filter.
|
/// Request to query records with a filter.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QueryRecordsRequest {
|
pub struct QueryRecordsRequest {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub filter: String,
|
pub filter: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub field_ids: Option<Vec<i32>>,
|
pub field_ids: Option<Vec<i32>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data_format: Option<DataFormat>,
|
pub data_format: Option<DataFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to get a batch of records.
|
/// Request to get a batch of records.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BatchGetRecordsRequest {
|
pub struct BatchGetRecordsRequest {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub record_ids: Vec<i32>,
|
pub record_ids: Vec<i32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub field_ids: Option<Vec<i32>>,
|
pub field_ids: Option<Vec<i32>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data_format: Option<DataFormat>,
|
pub data_format: Option<DataFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to delete a batch of records.
|
/// Request to delete a batch of records.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct BatchDeleteRecordsRequest {
|
pub struct BatchDeleteRecordsRequest {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub record_ids: Vec<i32>,
|
pub record_ids: Vec<i32>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,24 +4,24 @@ use serde::Deserialize;
|
|||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ReportInfo {
|
pub struct ReportInfo {
|
||||||
pub app_id: i32,
|
pub app_id: i32,
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the data returned by a report.
|
/// Represents the data returned by a report.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ReportData {
|
pub struct ReportData {
|
||||||
pub columns: Option<Vec<String>>,
|
pub columns: Option<Vec<String>>,
|
||||||
pub rows: Option<Vec<ReportRow>>,
|
pub rows: Option<Vec<ReportRow>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a single row in a report.
|
/// Represents a single row in a report.
|
||||||
#[derive(Debug, Clone, Deserialize)]
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ReportRow {
|
pub struct ReportRow {
|
||||||
pub record_id: Option<i32>,
|
pub record_id: Option<i32>,
|
||||||
pub cells: Option<Vec<serde_json::Value>>,
|
pub cells: Option<Vec<serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -2,9 +2,9 @@ use onspring::OnspringClient;
|
|||||||
use wiremock::MockServer;
|
use wiremock::MockServer;
|
||||||
|
|
||||||
pub async fn setup() -> (MockServer, OnspringClient) {
|
pub async fn setup() -> (MockServer, OnspringClient) {
|
||||||
let mock_server = MockServer::start().await;
|
let mock_server = MockServer::start().await;
|
||||||
let client = OnspringClient::builder("test-api-key")
|
let client = OnspringClient::builder("test-api-key")
|
||||||
.base_url(mock_server.uri())
|
.base_url(mock_server.uri())
|
||||||
.build();
|
.build();
|
||||||
(mock_server, client)
|
(mock_server, client)
|
||||||
}
|
}
|
||||||
|
|||||||
+86
-86
@@ -5,116 +5,116 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_apps_success() {
|
async fn test_list_apps_success() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Apps"))
|
.and(path("/Apps"))
|
||||||
.and(header("x-apikey", "test-api-key"))
|
.and(header("x-apikey", "test-api-key"))
|
||||||
.and(header("x-api-version", "2"))
|
.and(header("x-api-version", "2"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 1,
|
"pageNumber": 1,
|
||||||
"pageSize": 50,
|
"pageSize": 50,
|
||||||
"totalPages": 1,
|
"totalPages": 1,
|
||||||
"totalRecords": 2,
|
"totalRecords": 2,
|
||||||
"items": [
|
"items": [
|
||||||
{"href": "/Apps/id/1", "id": 1, "name": "App One"},
|
{"href": "/Apps/id/1", "id": 1, "name": "App One"},
|
||||||
{"href": "/Apps/id/2", "id": 2, "name": "App Two"}
|
{"href": "/Apps/id/2", "id": 2, "name": "App Two"}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.list_apps(None).await.unwrap();
|
let result = client.list_apps(None).await.unwrap();
|
||||||
assert_eq!(result.total_records, Some(2));
|
assert_eq!(result.total_records, Some(2));
|
||||||
let items = result.items.unwrap();
|
let items = result.items.unwrap();
|
||||||
assert_eq!(items.len(), 2);
|
assert_eq!(items.len(), 2);
|
||||||
assert_eq!(items[0].id, 1);
|
assert_eq!(items[0].id, 1);
|
||||||
assert_eq!(items[0].name.as_deref(), Some("App One"));
|
assert_eq!(items[0].name.as_deref(), Some("App One"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_apps_with_paging() {
|
async fn test_list_apps_with_paging() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Apps"))
|
.and(path("/Apps"))
|
||||||
.and(query_param("PageNumber", "2"))
|
.and(query_param("PageNumber", "2"))
|
||||||
.and(query_param("PageSize", "10"))
|
.and(query_param("PageSize", "10"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 2,
|
"pageNumber": 2,
|
||||||
"pageSize": 10,
|
"pageSize": 10,
|
||||||
"totalPages": 3,
|
"totalPages": 3,
|
||||||
"totalRecords": 25,
|
"totalRecords": 25,
|
||||||
"items": []
|
"items": []
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let paging = onspring::PagingRequest {
|
let paging = onspring::PagingRequest {
|
||||||
page_number: 2,
|
page_number: 2,
|
||||||
page_size: 10,
|
page_size: 10,
|
||||||
};
|
};
|
||||||
let result = client.list_apps(Some(paging)).await.unwrap();
|
let result = client.list_apps(Some(paging)).await.unwrap();
|
||||||
assert_eq!(result.page_number, Some(2));
|
assert_eq!(result.page_number, Some(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_app_success() {
|
async fn test_get_app_success() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Apps/id/42"))
|
.and(path("/Apps/id/42"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"href": "/Apps/id/42",
|
"href": "/Apps/id/42",
|
||||||
"id": 42,
|
"id": 42,
|
||||||
"name": "Test App"
|
"name": "Test App"
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let app = client.get_app(42).await.unwrap();
|
let app = client.get_app(42).await.unwrap();
|
||||||
assert_eq!(app.id, 42);
|
assert_eq!(app.id, 42);
|
||||||
assert_eq!(app.name.as_deref(), Some("Test App"));
|
assert_eq!(app.name.as_deref(), Some("Test App"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_app_not_found() {
|
async fn test_get_app_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Apps/id/999"))
|
.and(path("/Apps/id/999"))
|
||||||
.respond_with(ResponseTemplate::new(404))
|
.respond_with(ResponseTemplate::new(404))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.get_app(999).await;
|
let result = client.get_app(999).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_batch_get_apps() {
|
async fn test_batch_get_apps() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Apps/batch-get"))
|
.and(path("/Apps/batch-get"))
|
||||||
.and(body_json(serde_json::json!([1, 2, 3])))
|
.and(body_json(serde_json::json!([1, 2, 3])))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"count": 3,
|
"count": 3,
|
||||||
"items": [
|
"items": [
|
||||||
{"href": "/Apps/id/1", "id": 1, "name": "App 1"},
|
{"href": "/Apps/id/1", "id": 1, "name": "App 1"},
|
||||||
{"href": "/Apps/id/2", "id": 2, "name": "App 2"},
|
{"href": "/Apps/id/2", "id": 2, "name": "App 2"},
|
||||||
{"href": "/Apps/id/3", "id": 3, "name": "App 3"}
|
{"href": "/Apps/id/3", "id": 3, "name": "App 3"}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.batch_get_apps(&[1, 2, 3]).await.unwrap();
|
let result = client.batch_get_apps(&[1, 2, 3]).await.unwrap();
|
||||||
assert_eq!(result.count, Some(3));
|
assert_eq!(result.count, Some(3));
|
||||||
assert_eq!(result.items.unwrap().len(), 3);
|
assert_eq!(result.items.unwrap().len(), 3);
|
||||||
}
|
}
|
||||||
|
|||||||
+81
-81
@@ -5,75 +5,75 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_field_success() {
|
async fn test_get_field_success() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Fields/id/100"))
|
.and(path("/Fields/id/100"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"id": 100,
|
"id": 100,
|
||||||
"appId": 1,
|
"appId": 1,
|
||||||
"name": "Text Field",
|
"name": "Text Field",
|
||||||
"type": "Text",
|
"type": "Text",
|
||||||
"status": "Enabled",
|
"status": "Enabled",
|
||||||
"isRequired": true,
|
"isRequired": true,
|
||||||
"isUnique": false
|
"isUnique": false
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let field = client.get_field(100).await.unwrap();
|
let field = client.get_field(100).await.unwrap();
|
||||||
assert_eq!(field.id, 100);
|
assert_eq!(field.id, 100);
|
||||||
assert_eq!(field.app_id, 1);
|
assert_eq!(field.app_id, 1);
|
||||||
assert_eq!(field.name.as_deref(), Some("Text Field"));
|
assert_eq!(field.name.as_deref(), Some("Text Field"));
|
||||||
assert!(field.is_required);
|
assert!(field.is_required);
|
||||||
assert!(!field.is_unique);
|
assert!(!field.is_unique);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_field_list_type() {
|
async fn test_get_field_list_type() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Fields/id/200"))
|
.and(path("/Fields/id/200"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"id": 200,
|
"id": 200,
|
||||||
"appId": 1,
|
"appId": 1,
|
||||||
"name": "Status",
|
"name": "Status",
|
||||||
"type": "List",
|
"type": "List",
|
||||||
"status": "Enabled",
|
"status": "Enabled",
|
||||||
"isRequired": false,
|
"isRequired": false,
|
||||||
"isUnique": false,
|
"isUnique": false,
|
||||||
"multiplicity": "SingleSelect",
|
"multiplicity": "SingleSelect",
|
||||||
"listId": 50,
|
"listId": 50,
|
||||||
"values": [
|
"values": [
|
||||||
{
|
{
|
||||||
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||||
"name": "Active",
|
"name": "Active",
|
||||||
"sortOrder": 1,
|
"sortOrder": 1,
|
||||||
"numericValue": 1.0,
|
"numericValue": 1.0,
|
||||||
"color": "#00ff00"
|
"color": "#00ff00"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let field = client.get_field(200).await.unwrap();
|
let field = client.get_field(200).await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
field.multiplicity,
|
field.multiplicity,
|
||||||
Some(onspring::Multiplicity::SingleSelect)
|
Some(onspring::Multiplicity::SingleSelect)
|
||||||
);
|
);
|
||||||
assert_eq!(field.list_id, Some(50));
|
assert_eq!(field.list_id, Some(50));
|
||||||
let values = field.values.unwrap();
|
let values = field.values.unwrap();
|
||||||
assert_eq!(values.len(), 1);
|
assert_eq!(values.len(), 1);
|
||||||
assert_eq!(values[0].name, "Active");
|
assert_eq!(values[0].name, "Active");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_batch_get_fields() {
|
async fn test_batch_get_fields() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Fields/batch-get"))
|
.and(path("/Fields/batch-get"))
|
||||||
.and(body_json(serde_json::json!([1, 2])))
|
.and(body_json(serde_json::json!([1, 2])))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
@@ -86,16 +86,16 @@ async fn test_batch_get_fields() {
|
|||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.batch_get_fields(&[1, 2]).await.unwrap();
|
let result = client.batch_get_fields(&[1, 2]).await.unwrap();
|
||||||
assert_eq!(result.count, Some(2));
|
assert_eq!(result.count, Some(2));
|
||||||
assert_eq!(result.items.unwrap().len(), 2);
|
assert_eq!(result.items.unwrap().len(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_fields_for_app() {
|
async fn test_list_fields_for_app() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Fields/appId/5"))
|
.and(path("/Fields/appId/5"))
|
||||||
.and(query_param("PageNumber", "1"))
|
.and(query_param("PageNumber", "1"))
|
||||||
.and(query_param("PageSize", "25"))
|
.and(query_param("PageSize", "25"))
|
||||||
@@ -113,30 +113,30 @@ async fn test_list_fields_for_app() {
|
|||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let paging = onspring::PagingRequest {
|
let paging = onspring::PagingRequest {
|
||||||
page_number: 1,
|
page_number: 1,
|
||||||
page_size: 25,
|
page_size: 25,
|
||||||
};
|
};
|
||||||
let result = client.list_fields(5, Some(paging)).await.unwrap();
|
let result = client.list_fields(5, Some(paging)).await.unwrap();
|
||||||
assert_eq!(result.total_records, Some(3));
|
assert_eq!(result.total_records, Some(3));
|
||||||
assert_eq!(result.items.unwrap().len(), 3);
|
assert_eq!(result.items.unwrap().len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_field_not_found() {
|
async fn test_get_field_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Fields/id/999"))
|
.and(path("/Fields/id/999"))
|
||||||
.respond_with(ResponseTemplate::new(404))
|
.respond_with(ResponseTemplate::new(404))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.get_field(999).await;
|
let result = client.get_field(999).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-82
@@ -5,112 +5,112 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_file_info() {
|
async fn test_get_file_info() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Files/recordId/1/fieldId/2/fileId/3"))
|
.and(path("/Files/recordId/1/fieldId/2/fileId/3"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"type": "Attachment",
|
"type": "Attachment",
|
||||||
"contentType": "application/pdf",
|
"contentType": "application/pdf",
|
||||||
"name": "document.pdf",
|
"name": "document.pdf",
|
||||||
"createdDate": "2024-01-15T10:30:00Z",
|
"createdDate": "2024-01-15T10:30:00Z",
|
||||||
"modifiedDate": "2024-01-15T10:30:00Z",
|
"modifiedDate": "2024-01-15T10:30:00Z",
|
||||||
"owner": "admin",
|
"owner": "admin",
|
||||||
"notes": "Important doc",
|
"notes": "Important doc",
|
||||||
"fileHref": "/Files/recordId/1/fieldId/2/fileId/3/file"
|
"fileHref": "/Files/recordId/1/fieldId/2/fileId/3/file"
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let info = client.get_file_info(1, 2, 3).await.unwrap();
|
let info = client.get_file_info(1, 2, 3).await.unwrap();
|
||||||
assert_eq!(info.name.as_deref(), Some("document.pdf"));
|
assert_eq!(info.name.as_deref(), Some("document.pdf"));
|
||||||
assert_eq!(info.content_type.as_deref(), Some("application/pdf"));
|
assert_eq!(info.content_type.as_deref(), Some("application/pdf"));
|
||||||
assert_eq!(info.owner.as_deref(), Some("admin"));
|
assert_eq!(info.owner.as_deref(), Some("admin"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_file_content() {
|
async fn test_get_file_content() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
let file_bytes = b"hello file content";
|
let file_bytes = b"hello file content";
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Files/recordId/1/fieldId/2/fileId/3/file"))
|
.and(path("/Files/recordId/1/fieldId/2/fileId/3/file"))
|
||||||
.respond_with(
|
.respond_with(
|
||||||
ResponseTemplate::new(200)
|
ResponseTemplate::new(200)
|
||||||
.set_body_bytes(file_bytes.to_vec())
|
.set_body_bytes(file_bytes.to_vec())
|
||||||
.insert_header("content-type", "application/pdf")
|
.insert_header("content-type", "application/pdf")
|
||||||
.insert_header("content-disposition", "attachment; filename=\"test.pdf\""),
|
.insert_header("content-disposition", "attachment; filename=\"test.pdf\""),
|
||||||
)
|
)
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let response = client.get_file(1, 2, 3).await.unwrap();
|
let response = client.get_file(1, 2, 3).await.unwrap();
|
||||||
assert_eq!(response.data.as_ref(), file_bytes);
|
assert_eq!(response.data.as_ref(), file_bytes);
|
||||||
assert_eq!(response.content_type.as_deref(), Some("application/pdf"));
|
assert_eq!(response.content_type.as_deref(), Some("application/pdf"));
|
||||||
assert_eq!(response.file_name.as_deref(), Some("test.pdf"));
|
assert_eq!(response.file_name.as_deref(), Some("test.pdf"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_upload_file() {
|
async fn test_upload_file() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Files"))
|
.and(path("/Files"))
|
||||||
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
||||||
"id": 42
|
"id": 42
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::file::SaveFileRequest {
|
let request = onspring::models::file::SaveFileRequest {
|
||||||
record_id: 1,
|
record_id: 1,
|
||||||
field_id: 2,
|
field_id: 2,
|
||||||
notes: Some("test notes".to_string()),
|
notes: Some("test notes".to_string()),
|
||||||
modified_date: None,
|
modified_date: None,
|
||||||
file_name: "test.txt".to_string(),
|
file_name: "test.txt".to_string(),
|
||||||
file_data: b"file content".to_vec(),
|
file_data: b"file content".to_vec(),
|
||||||
content_type: "text/plain".to_string(),
|
content_type: "text/plain".to_string(),
|
||||||
};
|
};
|
||||||
let result = client.upload_file(request).await.unwrap();
|
let result = client.upload_file(request).await.unwrap();
|
||||||
assert_eq!(result.id, 42);
|
assert_eq!(result.id, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_file() {
|
async fn test_delete_file() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("DELETE"))
|
Mock::given(method("DELETE"))
|
||||||
.and(path("/Files/recordId/1/fieldId/2/fileId/3"))
|
.and(path("/Files/recordId/1/fieldId/2/fileId/3"))
|
||||||
.respond_with(ResponseTemplate::new(204))
|
.respond_with(ResponseTemplate::new(204))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.delete_file(1, 2, 3).await;
|
let result = client.delete_file(1, 2, 3).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_file_info_not_found() {
|
async fn test_get_file_info_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Files/recordId/1/fieldId/2/fileId/999"))
|
.and(path("/Files/recordId/1/fieldId/2/fileId/999"))
|
||||||
.respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
|
||||||
"message": "File not found"
|
"message": "File not found"
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.get_file_info(1, 2, 999).await;
|
let result = client.get_file_info(1, 2, 999).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api {
|
if let Err(onspring::OnspringError::Api {
|
||||||
status_code,
|
status_code,
|
||||||
message,
|
message,
|
||||||
}) = result
|
}) = result
|
||||||
{
|
{
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
assert_eq!(message, "File not found");
|
assert_eq!(message, "File not found");
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-57
@@ -6,81 +6,81 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_save_list_item_create() {
|
async fn test_save_list_item_create() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
let new_id = Uuid::new_v4();
|
let new_id = Uuid::new_v4();
|
||||||
|
|
||||||
Mock::given(method("PUT"))
|
Mock::given(method("PUT"))
|
||||||
.and(path("/Lists/id/10/items"))
|
.and(path("/Lists/id/10/items"))
|
||||||
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
||||||
"id": new_id.to_string()
|
"id": new_id.to_string()
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::list::SaveListItemRequest {
|
let request = onspring::models::list::SaveListItemRequest {
|
||||||
id: None,
|
id: None,
|
||||||
name: "New Item".to_string(),
|
name: "New Item".to_string(),
|
||||||
numeric_value: Some(1.0),
|
numeric_value: Some(1.0),
|
||||||
color: Some("#ff0000".to_string()),
|
color: Some("#ff0000".to_string()),
|
||||||
};
|
};
|
||||||
let result = client.save_list_item(10, request).await.unwrap();
|
let result = client.save_list_item(10, request).await.unwrap();
|
||||||
assert_eq!(result.id, new_id);
|
assert_eq!(result.id, new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_save_list_item_update() {
|
async fn test_save_list_item_update() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
let existing_id = Uuid::new_v4();
|
let existing_id = Uuid::new_v4();
|
||||||
|
|
||||||
Mock::given(method("PUT"))
|
Mock::given(method("PUT"))
|
||||||
.and(path("/Lists/id/10/items"))
|
.and(path("/Lists/id/10/items"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"id": existing_id.to_string()
|
"id": existing_id.to_string()
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::list::SaveListItemRequest {
|
let request = onspring::models::list::SaveListItemRequest {
|
||||||
id: Some(existing_id),
|
id: Some(existing_id),
|
||||||
name: "Updated Item".to_string(),
|
name: "Updated Item".to_string(),
|
||||||
numeric_value: None,
|
numeric_value: None,
|
||||||
color: None,
|
color: None,
|
||||||
};
|
};
|
||||||
let result = client.save_list_item(10, request).await.unwrap();
|
let result = client.save_list_item(10, request).await.unwrap();
|
||||||
assert_eq!(result.id, existing_id);
|
assert_eq!(result.id, existing_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_list_item() {
|
async fn test_delete_list_item() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
let item_id = Uuid::new_v4();
|
let item_id = Uuid::new_v4();
|
||||||
|
|
||||||
Mock::given(method("DELETE"))
|
Mock::given(method("DELETE"))
|
||||||
.and(path(format!("/Lists/id/10/itemId/{}", item_id)))
|
.and(path(format!("/Lists/id/10/itemId/{}", item_id)))
|
||||||
.respond_with(ResponseTemplate::new(204))
|
.respond_with(ResponseTemplate::new(204))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.delete_list_item(10, item_id).await;
|
let result = client.delete_list_item(10, item_id).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_list_item_not_found() {
|
async fn test_delete_list_item_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
let item_id = Uuid::new_v4();
|
let item_id = Uuid::new_v4();
|
||||||
|
|
||||||
Mock::given(method("DELETE"))
|
Mock::given(method("DELETE"))
|
||||||
.and(path(format!("/Lists/id/10/itemId/{}", item_id)))
|
.and(path(format!("/Lists/id/10/itemId/{}", item_id)))
|
||||||
.respond_with(ResponseTemplate::new(404))
|
.respond_with(ResponseTemplate::new(404))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.delete_list_item(10, item_id).await;
|
let result = client.delete_list_item(10, item_id).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+23
-23
@@ -5,36 +5,36 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_ping_success() {
|
async fn test_ping_success() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Ping"))
|
.and(path("/Ping"))
|
||||||
.and(header("x-apikey", "test-api-key"))
|
.and(header("x-apikey", "test-api-key"))
|
||||||
.and(header("x-api-version", "2"))
|
.and(header("x-api-version", "2"))
|
||||||
.respond_with(ResponseTemplate::new(200))
|
.respond_with(ResponseTemplate::new(200))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.ping().await;
|
let result = client.ping().await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_ping_unauthorized() {
|
async fn test_ping_unauthorized() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Ping"))
|
.and(path("/Ping"))
|
||||||
.respond_with(ResponseTemplate::new(401))
|
.respond_with(ResponseTemplate::new(401))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.ping().await;
|
let result = client.ping().await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 401);
|
assert_eq!(status_code, 401);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+165
-165
@@ -7,220 +7,220 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_records() {
|
async fn test_list_records() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Records/appId/1"))
|
.and(path("/Records/appId/1"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 1,
|
"pageNumber": 1,
|
||||||
"pageSize": 50,
|
"pageSize": 50,
|
||||||
"totalPages": 1,
|
"totalPages": 1,
|
||||||
"totalRecords": 1,
|
"totalRecords": 1,
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"appId": 1,
|
"appId": 1,
|
||||||
"recordId": 100,
|
"recordId": 100,
|
||||||
"fieldData": [
|
"fieldData": [
|
||||||
{"type": "String", "fieldId": 10, "value": "hello"}
|
{"type": "String", "fieldId": 10, "value": "hello"}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.list_records(1, None, None, None).await.unwrap();
|
let result = client.list_records(1, None, None, None).await.unwrap();
|
||||||
assert_eq!(result.total_records, Some(1));
|
assert_eq!(result.total_records, Some(1));
|
||||||
let items = result.items.unwrap();
|
let items = result.items.unwrap();
|
||||||
assert_eq!(items[0].record_id, 100);
|
assert_eq!(items[0].record_id, 100);
|
||||||
let field_data = items[0].field_data.as_ref().unwrap();
|
let field_data = items[0].field_data.as_ref().unwrap();
|
||||||
assert_eq!(field_data[0].field_id, 10);
|
assert_eq!(field_data[0].field_id, 10);
|
||||||
assert_eq!(field_data[0].value, serde_json::json!("hello"));
|
assert_eq!(field_data[0].value, serde_json::json!("hello"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_records_with_params() {
|
async fn test_list_records_with_params() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Records/appId/1"))
|
.and(path("/Records/appId/1"))
|
||||||
.and(query_param("fieldIds", "10,20"))
|
.and(query_param("fieldIds", "10,20"))
|
||||||
.and(query_param("dataFormat", "Formatted"))
|
.and(query_param("dataFormat", "Formatted"))
|
||||||
.and(query_param("PageNumber", "2"))
|
.and(query_param("PageNumber", "2"))
|
||||||
.and(query_param("PageSize", "10"))
|
.and(query_param("PageSize", "10"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 2,
|
"pageNumber": 2,
|
||||||
"pageSize": 10,
|
"pageSize": 10,
|
||||||
"totalPages": 5,
|
"totalPages": 5,
|
||||||
"totalRecords": 50,
|
"totalRecords": 50,
|
||||||
"items": []
|
"items": []
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let paging = onspring::PagingRequest {
|
let paging = onspring::PagingRequest {
|
||||||
page_number: 2,
|
page_number: 2,
|
||||||
page_size: 10,
|
page_size: 10,
|
||||||
};
|
};
|
||||||
let result = client
|
let result = client
|
||||||
.list_records(
|
.list_records(
|
||||||
1,
|
1,
|
||||||
Some(paging),
|
Some(paging),
|
||||||
Some(&[10, 20]),
|
Some(&[10, 20]),
|
||||||
Some(onspring::DataFormat::Formatted),
|
Some(onspring::DataFormat::Formatted),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(result.page_number, Some(2));
|
assert_eq!(result.page_number, Some(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_record() {
|
async fn test_get_record() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Records/appId/1/recordId/42"))
|
.and(path("/Records/appId/1/recordId/42"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"appId": 1,
|
"appId": 1,
|
||||||
"recordId": 42,
|
"recordId": 42,
|
||||||
"fieldData": [
|
"fieldData": [
|
||||||
{"type": "Integer", "fieldId": 5, "value": 123}
|
{"type": "Integer", "fieldId": 5, "value": 123}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let record = client.get_record(1, 42, None, None).await.unwrap();
|
let record = client.get_record(1, 42, None, None).await.unwrap();
|
||||||
assert_eq!(record.record_id, 42);
|
assert_eq!(record.record_id, 42);
|
||||||
assert_eq!(record.app_id, 1);
|
assert_eq!(record.app_id, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_save_record_create() {
|
async fn test_save_record_create() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("PUT"))
|
Mock::given(method("PUT"))
|
||||||
.and(path("/Records"))
|
.and(path("/Records"))
|
||||||
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(201).set_body_json(serde_json::json!({
|
||||||
"id": 99,
|
"id": 99,
|
||||||
"warnings": []
|
"warnings": []
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let mut fields = HashMap::new();
|
let mut fields = HashMap::new();
|
||||||
fields.insert("10".to_string(), serde_json::json!("test value"));
|
fields.insert("10".to_string(), serde_json::json!("test value"));
|
||||||
|
|
||||||
let request = onspring::models::record::SaveRecordRequest {
|
let request = onspring::models::record::SaveRecordRequest {
|
||||||
app_id: 1,
|
app_id: 1,
|
||||||
record_id: None,
|
record_id: None,
|
||||||
fields,
|
fields,
|
||||||
};
|
};
|
||||||
let result = client.save_record(request).await.unwrap();
|
let result = client.save_record(request).await.unwrap();
|
||||||
assert_eq!(result.id, 99);
|
assert_eq!(result.id, 99);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_record() {
|
async fn test_delete_record() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("DELETE"))
|
Mock::given(method("DELETE"))
|
||||||
.and(path("/Records/appId/1/recordId/42"))
|
.and(path("/Records/appId/1/recordId/42"))
|
||||||
.respond_with(ResponseTemplate::new(204))
|
.respond_with(ResponseTemplate::new(204))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.delete_record(1, 42).await;
|
let result = client.delete_record(1, 42).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_batch_get_records() {
|
async fn test_batch_get_records() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Records/batch-get"))
|
.and(path("/Records/batch-get"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"count": 2,
|
"count": 2,
|
||||||
"items": [
|
"items": [
|
||||||
{"appId": 1, "recordId": 1, "fieldData": []},
|
{"appId": 1, "recordId": 1, "fieldData": []},
|
||||||
{"appId": 1, "recordId": 2, "fieldData": []}
|
{"appId": 1, "recordId": 2, "fieldData": []}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::record::BatchGetRecordsRequest {
|
let request = onspring::models::record::BatchGetRecordsRequest {
|
||||||
app_id: 1,
|
app_id: 1,
|
||||||
record_ids: vec![1, 2],
|
record_ids: vec![1, 2],
|
||||||
field_ids: None,
|
field_ids: None,
|
||||||
data_format: None,
|
data_format: None,
|
||||||
};
|
};
|
||||||
let result = client.batch_get_records(request).await.unwrap();
|
let result = client.batch_get_records(request).await.unwrap();
|
||||||
assert_eq!(result.count, Some(2));
|
assert_eq!(result.count, Some(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_query_records() {
|
async fn test_query_records() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Records/Query"))
|
.and(path("/Records/Query"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 1,
|
"pageNumber": 1,
|
||||||
"pageSize": 50,
|
"pageSize": 50,
|
||||||
"totalPages": 1,
|
"totalPages": 1,
|
||||||
"totalRecords": 1,
|
"totalRecords": 1,
|
||||||
"items": [
|
"items": [
|
||||||
{"appId": 1, "recordId": 5, "fieldData": []}
|
{"appId": 1, "recordId": 5, "fieldData": []}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::record::QueryRecordsRequest {
|
let request = onspring::models::record::QueryRecordsRequest {
|
||||||
app_id: 1,
|
app_id: 1,
|
||||||
filter: "fieldId eq 'test'".to_string(),
|
filter: "fieldId eq 'test'".to_string(),
|
||||||
field_ids: None,
|
field_ids: None,
|
||||||
data_format: None,
|
data_format: None,
|
||||||
};
|
};
|
||||||
let result = client.query_records(request, None).await.unwrap();
|
let result = client.query_records(request, None).await.unwrap();
|
||||||
assert_eq!(result.total_records, Some(1));
|
assert_eq!(result.total_records, Some(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_batch_delete_records() {
|
async fn test_batch_delete_records() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("POST"))
|
Mock::given(method("POST"))
|
||||||
.and(path("/Records/batch-delete"))
|
.and(path("/Records/batch-delete"))
|
||||||
.respond_with(ResponseTemplate::new(204))
|
.respond_with(ResponseTemplate::new(204))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let request = onspring::models::record::BatchDeleteRecordsRequest {
|
let request = onspring::models::record::BatchDeleteRecordsRequest {
|
||||||
app_id: 1,
|
app_id: 1,
|
||||||
record_ids: vec![1, 2, 3],
|
record_ids: vec![1, 2, 3],
|
||||||
};
|
};
|
||||||
let result = client.batch_delete_records(request).await;
|
let result = client.batch_delete_records(request).await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_record_not_found() {
|
async fn test_get_record_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Records/appId/1/recordId/999"))
|
.and(path("/Records/appId/1/recordId/999"))
|
||||||
.respond_with(ResponseTemplate::new(404))
|
.respond_with(ResponseTemplate::new(404))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.get_record(1, 999, None, None).await;
|
let result = client.get_record(1, 999, None, None).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-73
@@ -5,97 +5,97 @@ use wiremock::{Mock, ResponseTemplate};
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_report() {
|
async fn test_get_report() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Reports/id/1"))
|
.and(path("/Reports/id/1"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"columns": ["Record Id", "Name", "Status"],
|
"columns": ["Record Id", "Name", "Status"],
|
||||||
"rows": [
|
"rows": [
|
||||||
{"recordId": 100, "cells": [100, "Test", "Active"]},
|
{"recordId": 100, "cells": [100, "Test", "Active"]},
|
||||||
{"recordId": 101, "cells": [101, "Other", "Inactive"]}
|
{"recordId": 101, "cells": [101, "Other", "Inactive"]}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let report = client.get_report(1, None, None).await.unwrap();
|
let report = client.get_report(1, None, None).await.unwrap();
|
||||||
let columns = report.columns.unwrap();
|
let columns = report.columns.unwrap();
|
||||||
assert_eq!(columns.len(), 3);
|
assert_eq!(columns.len(), 3);
|
||||||
assert_eq!(columns[0], "Record Id");
|
assert_eq!(columns[0], "Record Id");
|
||||||
let rows = report.rows.unwrap();
|
let rows = report.rows.unwrap();
|
||||||
assert_eq!(rows.len(), 2);
|
assert_eq!(rows.len(), 2);
|
||||||
assert_eq!(rows[0].record_id, Some(100));
|
assert_eq!(rows[0].record_id, Some(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_report_with_format() {
|
async fn test_get_report_with_format() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Reports/id/1"))
|
.and(path("/Reports/id/1"))
|
||||||
.and(query_param("apiDataFormat", "Formatted"))
|
.and(query_param("apiDataFormat", "Formatted"))
|
||||||
.and(query_param("dataType", "ChartData"))
|
.and(query_param("dataType", "ChartData"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"columns": ["Name"],
|
"columns": ["Name"],
|
||||||
"rows": []
|
"rows": []
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client
|
let result = client
|
||||||
.get_report(
|
.get_report(
|
||||||
1,
|
1,
|
||||||
Some(onspring::DataFormat::Formatted),
|
Some(onspring::DataFormat::Formatted),
|
||||||
Some(onspring::ReportDataType::ChartData),
|
Some(onspring::ReportDataType::ChartData),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(result.columns.unwrap().len() == 1);
|
assert!(result.columns.unwrap().len() == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_list_reports() {
|
async fn test_list_reports() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Reports/appId/5"))
|
.and(path("/Reports/appId/5"))
|
||||||
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
"pageNumber": 1,
|
"pageNumber": 1,
|
||||||
"pageSize": 50,
|
"pageSize": 50,
|
||||||
"totalPages": 1,
|
"totalPages": 1,
|
||||||
"totalRecords": 2,
|
"totalRecords": 2,
|
||||||
"items": [
|
"items": [
|
||||||
{"appId": 5, "id": 10, "name": "Report A", "description": "First report"},
|
{"appId": 5, "id": 10, "name": "Report A", "description": "First report"},
|
||||||
{"appId": 5, "id": 11, "name": "Report B", "description": null}
|
{"appId": 5, "id": 11, "name": "Report B", "description": null}
|
||||||
]
|
]
|
||||||
})))
|
})))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.list_reports(5, None).await.unwrap();
|
let result = client.list_reports(5, None).await.unwrap();
|
||||||
assert_eq!(result.total_records, Some(2));
|
assert_eq!(result.total_records, Some(2));
|
||||||
let items = result.items.unwrap();
|
let items = result.items.unwrap();
|
||||||
assert_eq!(items[0].id, 10);
|
assert_eq!(items[0].id, 10);
|
||||||
assert_eq!(items[0].name.as_deref(), Some("Report A"));
|
assert_eq!(items[0].name.as_deref(), Some("Report A"));
|
||||||
assert_eq!(items[1].description, None);
|
assert_eq!(items[1].description, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_get_report_not_found() {
|
async fn test_get_report_not_found() {
|
||||||
let (mock_server, client) = common::setup().await;
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
Mock::given(method("GET"))
|
Mock::given(method("GET"))
|
||||||
.and(path("/Reports/id/999"))
|
.and(path("/Reports/id/999"))
|
||||||
.respond_with(ResponseTemplate::new(404))
|
.respond_with(ResponseTemplate::new(404))
|
||||||
.mount(&mock_server)
|
.mount(&mock_server)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let result = client.get_report(999, None, None).await;
|
let result = client.get_report(999, None, None).await;
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
assert_eq!(status_code, 404);
|
assert_eq!(status_code, 404);
|
||||||
} else {
|
} else {
|
||||||
panic!("Expected Api error");
|
panic!("Expected Api error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user