use crate::result::ScriptResultDeserialize;
use async_trait::async_trait;
use percent_encoding::percent_decode_str;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use url::Url;
pub mod data_api;
pub mod odata_api;
pub mod result;
#[async_trait]
pub trait ScriptClient {
async fn execute<T: ScriptResultDeserialize, P: Serialize + Send + Sync>(
&self,
script_name: impl Into<String> + Send,
parameter: Option<P>,
) -> Result<T, Error>;
async fn execute_without_parameter<T: ScriptResultDeserialize>(
&self,
script_name: &str,
) -> Result<T, Error> {
self.execute::<T, ()>(script_name, None).await
}
}
#[derive(Debug, Error)]
pub enum Error {
#[error("Failed to parse URL")]
Url(#[from] url::ParseError),
#[error("Failed to perform request")]
Request(#[from] reqwest::Error),
#[error("Failed to (de)serialize JSON")]
SerdeJson(#[from] serde_json::Error),
#[error("FileMaker returned an error")]
FileMaker(FileMakerError),
#[error("FileMaker script returned an error")]
ScriptFailure { code: i64, data: Option<String> },
#[error("FileMaker did not respond with an access token")]
MissingAccessToken,
#[error("Received an unknown response")]
UnknownResponse(StatusCode),
#[error("Invalid connection URL")]
InvalidConnectionUrl,
}
#[derive(Debug, Deserialize)]
pub struct FileMakerError {
pub code: String,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct Connection {
hostname: String,
database: String,
username: String,
password: String,
port: Option<u16>,
disable_tls: bool,
}
impl Connection {
pub fn new(
hostname: impl Into<String>,
database: impl Into<String>,
username: impl Into<String>,
password: impl Into<String>,
) -> Connection {
Self {
hostname: hostname.into(),
database: database.into(),
username: username.into(),
password: password.into(),
port: None,
disable_tls: false,
}
}
pub fn with_port(mut self, port: Option<u16>) -> Self {
self.port = port;
self
}
pub fn without_tls(mut self, disable_tls: bool) -> Self {
self.disable_tls = disable_tls;
self
}
}
impl TryFrom<Url> for Connection {
type Error = Error;
fn try_from(url: Url) -> Result<Self, Self::Error> {
let decode = |value: &str| -> Result<String, Error> {
Ok(percent_decode_str(value)
.decode_utf8()
.map_err(|_| Error::InvalidConnectionUrl)?
.to_string())
};
Ok(Connection {
hostname: decode(url.host_str().ok_or_else(|| Error::InvalidConnectionUrl)?)?,
database: decode(&url.path()[1..])?,
username: decode(url.username())?,
password: decode(url.password().ok_or_else(|| Error::InvalidConnectionUrl)?)?,
port: url.port(),
disable_tls: url.scheme() == "http",
})
}
}
impl TryFrom<&str> for Connection {
type Error = Error;
fn try_from(url: &str) -> Result<Self, Self::Error> {
Url::parse(url)?.try_into()
}
}
impl TryFrom<String> for Connection {
type Error = Error;
fn try_from(url: String) -> Result<Self, Self::Error> {
Url::parse(&url)?.try_into()
}
}