feat: add fields endpoint with get, batch-get, and list methods
Implement Field model supporting base fields and polymorphic variants (list, formula, reference fields via optional properties). Add get_field, batch_get_fields, and list_fields endpoint methods with tests covering basic fields, list-type fields, batch retrieval, pagination, and not-found scenarios.
This commit is contained in:
@@ -0,0 +1,37 @@
|
|||||||
|
use reqwest::Method;
|
||||||
|
|
||||||
|
use crate::client::OnspringClient;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::models::{CollectionResponse, Field, PagedResponse, PagingRequest};
|
||||||
|
|
||||||
|
impl OnspringClient {
|
||||||
|
/// Gets a field by its identifier.
|
||||||
|
pub async fn get_field(&self, field_id: i32) -> Result<Field> {
|
||||||
|
let path = format!("/Fields/id/{}", field_id);
|
||||||
|
self.request(Method::GET, &path, &[], Option::<&()>::None)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets up to 100 fields by their identifiers.
|
||||||
|
pub async fn batch_get_fields(&self, ids: &[i32]) -> Result<CollectionResponse<Field>> {
|
||||||
|
self.request(Method::POST, "/Fields/batch-get", &[], Some(&ids))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a paginated list of fields for a given application.
|
||||||
|
pub async fn list_fields(
|
||||||
|
&self,
|
||||||
|
app_id: i32,
|
||||||
|
paging: Option<PagingRequest>,
|
||||||
|
) -> Result<PagedResponse<Field>> {
|
||||||
|
let path = format!("/Fields/appId/{}", app_id);
|
||||||
|
let mut query = Vec::new();
|
||||||
|
if let Some(p) = paging {
|
||||||
|
query.push(("PageNumber", p.page_number.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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
mod apps;
|
mod apps;
|
||||||
|
mod fields;
|
||||||
mod ping;
|
mod ping;
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use serde::Deserialize;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::enums::{FormulaOutputType, Multiplicity};
|
||||||
|
|
||||||
|
/// Represents a field in an Onspring application.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Field {
|
||||||
|
pub id: i32,
|
||||||
|
pub app_id: i32,
|
||||||
|
pub name: Option<String>,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub field_type: Option<String>,
|
||||||
|
pub status: Option<String>,
|
||||||
|
pub is_required: bool,
|
||||||
|
pub is_unique: bool,
|
||||||
|
pub multiplicity: Option<Multiplicity>,
|
||||||
|
pub list_id: Option<i32>,
|
||||||
|
pub values: Option<Vec<ListFieldValue>>,
|
||||||
|
#[serde(rename = "outputType")]
|
||||||
|
pub output_type: Option<FormulaOutputType>,
|
||||||
|
pub referenced_app_id: Option<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a value in a list field.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ListFieldValue {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub name: String,
|
||||||
|
pub sort_order: i32,
|
||||||
|
pub numeric_value: Option<f64>,
|
||||||
|
pub color: Option<String>,
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
mod app;
|
mod app;
|
||||||
mod enums;
|
mod enums;
|
||||||
|
mod field;
|
||||||
mod paging;
|
mod paging;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use enums::*;
|
pub use enums::*;
|
||||||
|
pub use field::*;
|
||||||
pub use paging::*;
|
pub use paging::*;
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
use wiremock::matchers::{body_json, method, path, query_param};
|
||||||
|
use wiremock::{Mock, ResponseTemplate};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_field_success() {
|
||||||
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/Fields/id/100"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
|
"id": 100,
|
||||||
|
"appId": 1,
|
||||||
|
"name": "Text Field",
|
||||||
|
"type": "Text",
|
||||||
|
"status": "Enabled",
|
||||||
|
"isRequired": true,
|
||||||
|
"isUnique": false
|
||||||
|
})))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let field = client.get_field(100).await.unwrap();
|
||||||
|
assert_eq!(field.id, 100);
|
||||||
|
assert_eq!(field.app_id, 1);
|
||||||
|
assert_eq!(field.name.as_deref(), Some("Text Field"));
|
||||||
|
assert!(field.is_required);
|
||||||
|
assert!(!field.is_unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_field_list_type() {
|
||||||
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/Fields/id/200"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
|
"id": 200,
|
||||||
|
"appId": 1,
|
||||||
|
"name": "Status",
|
||||||
|
"type": "List",
|
||||||
|
"status": "Enabled",
|
||||||
|
"isRequired": false,
|
||||||
|
"isUnique": false,
|
||||||
|
"multiplicity": "SingleSelect",
|
||||||
|
"listId": 50,
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
|
||||||
|
"name": "Active",
|
||||||
|
"sortOrder": 1,
|
||||||
|
"numericValue": 1.0,
|
||||||
|
"color": "#00ff00"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let field = client.get_field(200).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
field.multiplicity,
|
||||||
|
Some(onspring::Multiplicity::SingleSelect)
|
||||||
|
);
|
||||||
|
assert_eq!(field.list_id, Some(50));
|
||||||
|
let values = field.values.unwrap();
|
||||||
|
assert_eq!(values.len(), 1);
|
||||||
|
assert_eq!(values[0].name, "Active");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_batch_get_fields() {
|
||||||
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
|
Mock::given(method("POST"))
|
||||||
|
.and(path("/Fields/batch-get"))
|
||||||
|
.and(body_json(serde_json::json!([1, 2])))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
|
"count": 2,
|
||||||
|
"items": [
|
||||||
|
{"id": 1, "appId": 1, "name": "Field 1", "type": "Text", "status": "Enabled", "isRequired": false, "isUnique": false},
|
||||||
|
{"id": 2, "appId": 1, "name": "Field 2", "type": "Number", "status": "Enabled", "isRequired": true, "isUnique": false}
|
||||||
|
]
|
||||||
|
})))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let result = client.batch_get_fields(&[1, 2]).await.unwrap();
|
||||||
|
assert_eq!(result.count, Some(2));
|
||||||
|
assert_eq!(result.items.unwrap().len(), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_list_fields_for_app() {
|
||||||
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/Fields/appId/5"))
|
||||||
|
.and(query_param("PageNumber", "1"))
|
||||||
|
.and(query_param("PageSize", "25"))
|
||||||
|
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
||||||
|
"pageNumber": 1,
|
||||||
|
"pageSize": 25,
|
||||||
|
"totalPages": 1,
|
||||||
|
"totalRecords": 3,
|
||||||
|
"items": [
|
||||||
|
{"id": 10, "appId": 5, "name": "Name", "type": "Text", "status": "Enabled", "isRequired": true, "isUnique": false},
|
||||||
|
{"id": 11, "appId": 5, "name": "Email", "type": "Text", "status": "Enabled", "isRequired": false, "isUnique": true},
|
||||||
|
{"id": 12, "appId": 5, "name": "Notes", "type": "Text", "status": "Enabled", "isRequired": false, "isUnique": false}
|
||||||
|
]
|
||||||
|
})))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let paging = onspring::PagingRequest {
|
||||||
|
page_number: 1,
|
||||||
|
page_size: 25,
|
||||||
|
};
|
||||||
|
let result = client.list_fields(5, Some(paging)).await.unwrap();
|
||||||
|
assert_eq!(result.total_records, Some(3));
|
||||||
|
assert_eq!(result.items.unwrap().len(), 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_get_field_not_found() {
|
||||||
|
let (mock_server, client) = common::setup().await;
|
||||||
|
|
||||||
|
Mock::given(method("GET"))
|
||||||
|
.and(path("/Fields/id/999"))
|
||||||
|
.respond_with(ResponseTemplate::new(404))
|
||||||
|
.mount(&mock_server)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let result = client.get_field(999).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
if let Err(onspring::OnspringError::Api { status_code, .. }) = result {
|
||||||
|
assert_eq!(status_code, 404);
|
||||||
|
} else {
|
||||||
|
panic!("Expected Api error");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user