fm_script_client/
odata_api.rs1use crate::{Connection, Error, FileMakerError, ScriptClient};
2use async_trait::async_trait;
3use reqwest::Client;
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use std::sync::Arc;
8use url::Url;
9
10pub struct ODataApiScriptClient {
16 connection: Arc<Connection>,
17 client: Client,
18}
19
20impl ODataApiScriptClient {
21 pub fn new(connection: Connection) -> Self {
34 Self {
35 connection: Arc::new(connection),
36 client: Client::new(),
37 }
38 }
39}
40
41#[derive(Debug, Serialize)]
42#[serde(rename_all = "camelCase")]
43struct RequestBody<T> {
44 #[serde(skip_serializing_if = "Option::is_none")]
45 script_parameter_value: Option<T>,
46}
47
48#[derive(Debug, Deserialize)]
49#[serde(rename_all = "camelCase")]
50struct ScriptResult {
51 code: i64,
52 result_parameter: Value,
53}
54
55#[derive(Debug, Deserialize)]
56#[serde(rename_all = "camelCase")]
57struct ResponseBody {
58 script_result: ScriptResult,
59}
60
61#[derive(Debug, Deserialize)]
62struct ErrorResponseBody {
63 error: FileMakerError,
64}
65
66#[async_trait]
67impl ScriptClient for ODataApiScriptClient {
68 async fn execute<T: DeserializeOwned, P: Serialize + Send + Sync>(
69 &self,
70 script_name: impl Into<String> + Send,
71 parameter: Option<P>,
72 ) -> Result<T, Error> {
73 let mut url = Url::parse(&format!(
74 "{}://{}/fmi/odata/v4/{}/Script.{}",
75 if self.connection.disable_tls {
76 "http"
77 } else {
78 "https"
79 },
80 self.connection.hostname,
81 self.connection.database,
82 script_name.into(),
83 ))?;
84
85 if let Some(port) = self.connection.port {
86 let _ = url.set_port(Some(port));
87 }
88
89 let body = RequestBody {
90 script_parameter_value: parameter,
91 };
92
93 let response = self
94 .client
95 .post(url)
96 .basic_auth(&self.connection.username, Some(&self.connection.password))
97 .header("Content-Type", "application/json")
98 .header("Accept", "application/json")
99 .json(&body)
100 .send()
101 .await?;
102
103 let status = response.status();
104
105 if status.is_success() {
106 let result: ResponseBody = response.json().await?;
107
108 if result.script_result.code != 0 {
109 return Err(Error::ScriptFailure {
110 code: result.script_result.code,
111 data: result.script_result.result_parameter.to_string(),
112 });
113 }
114
115 let result: T = serde_json::from_value(result.script_result.result_parameter)?;
116 return Ok(result);
117 }
118
119 match response.json::<ErrorResponseBody>().await {
120 Ok(result) => Err(Error::FileMaker(result.error)),
121 Err(_) => Err(Error::UnknownResponse(status)),
122 }
123 }
124}