use super::api_model_config::ApiModelConfig;
use crate::{
id::Id,
prelude::{schema::common_model::CommonModel, shared::record_metadata::RecordMetadata},
};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use strum::{Display, EnumIter};
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
pub enum ParameterLocation {
QueryParameter,
RequestBody,
Header,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct ConnectionModelDefinition {
#[serde(rename = "_id")]
pub id: Id,
pub connection_platform: String,
pub connection_definition_id: Id,
pub platform_version: String,
#[serde(default)]
pub key: String,
pub title: String,
pub name: String,
pub model_name: String,
#[serde(with = "http_serde_ext::method")]
#[cfg_attr(feature = "dummy", dummy(expr = "http::Method::GET"))]
pub action: http::Method,
pub action_name: CrudAction,
#[serde(flatten)]
pub platform_info: PlatformInfo,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "dummy", dummy(default))]
pub extractor_config: Option<ExtractorConfig>,
pub test_connection_status: TestConnection,
pub test_connection_payload: Option<Value>,
pub is_default_crud_mapping: Option<bool>,
pub mapping: Option<CrudMapping>,
#[serde(flatten, default)]
pub record_metadata: RecordMetadata,
#[serde(default)]
pub supported: bool,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct TestConnection {
pub last_tested_at: i64,
pub state: TestConnectionState,
}
impl Default for TestConnection {
fn default() -> Self {
Self {
last_tested_at: 0,
state: TestConnectionState::Untested,
}
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Default)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub enum TestConnectionState {
Success {
#[serde(rename = "requestPayload")]
request_payload: String,
response: String,
},
Failure {
message: String,
#[serde(rename = "requestPayload")]
request_payload: String,
},
#[default]
Untested,
}
pub enum ConnectionModelDefinitionWithState {
Populated(ConnectionModelDefinition),
Unpopulated(ConnectionModelDefinition),
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(untagged)]
pub enum PlatformInfo {
Api(ApiModelConfig),
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct ExtractorConfig {
pub pull_frequency: i64,
pub batch_size: i64,
pub cursor: CursorConfig,
pub limit: Option<LimitConfig>,
pub sleep_after_finish: i64,
pub update_config: Option<UpdateConfig>,
pub enabled: bool,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct CursorConfig {
pub param_name: Option<String>,
pub location: Option<ParameterLocation>,
pub format: Option<String>,
pub cursor_path: String,
pub data_path: String,
pub js_extractor_function: Option<String>,
pub reset_on_end: bool,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct LimitConfig {
pub param_name: String,
pub location: ParameterLocation,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct UpdateConfig {
pub param_name: String,
pub location: ParameterLocation,
pub format: String,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
pub struct CrudMapping {
pub action: CrudAction,
pub common_model_name: String,
pub from_common_model: Option<String>,
pub to_common_model: Option<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Display, EnumIter)]
#[cfg_attr(feature = "dummy", derive(fake::Dummy))]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum CrudAction {
GetOne,
GetMany,
GetCount,
Upsert,
Update,
Create,
Delete,
Custom,
}
impl CrudAction {
pub fn description(&self) -> &'static str {
match self {
CrudAction::GetOne => "Get one record",
CrudAction::GetMany => "List records",
CrudAction::GetCount => "Get count of records",
CrudAction::Upsert => "Upsert a record",
CrudAction::Update => "Update a record",
CrudAction::Create => "Create a record",
CrudAction::Delete => "Delete a record",
CrudAction::Custom => "Custom action",
}
}
pub fn example(&self, common_model: &CommonModel) -> Value {
let meta = json!({
"timestamp": chrono::offset::Local::now().timestamp_millis(),
"latency": 36,
"platformRateLimitRemaining": 0,
"rateLimitRemaining": 0,
"cache": {
"hit": false,
"ttl": 0,
"key": ""
},
"transactionKey": "tx_ky::7fb4a1ee61454e61adf79f37251affc4",
"txn": "727cc0ecc6144f6782ca0d72486d3dea",
"platform": "PlatformName",
"platformVersion": "v1",
"connectionDefinitionKey": "conn_def::7923c71ff5ae4119870bc42182c50cb7",
"action": self.to_string(),
"commonModel": common_model.name,
"commonModelVersion": common_model.record_metadata.version,
"connectionKey": "platform::8e09f436-601a-40c4-97cd-f9347e924418",
"hash": "3eafdfa35a39411ca4992e7139c69d854f2135ad956c45449a7b6a98288d59a61177f386801d460ca3ee3884635d575b",
"heartbeats": [],
"totalTransactions": 1
});
match self {
CrudAction::Create => {
json!({
"status": "success",
"statusCode": 200,
"unified": common_model.sample,
"passthrough": {},
"meta": meta
})
}
CrudAction::GetMany => {
json!({
"status": "success",
"statusCode": 200,
"unified": vec![common_model.clone().sample],
"passthrough": {},
"pagination": {
"cursor": "23e6534fa96e810b3",
"limit": 100
},
"meta": meta
})
}
CrudAction::GetOne => {
json!({
"status": "success",
"statusCode": 200,
"unified": common_model.sample,
"passthrough": {},
"meta": meta
})
}
CrudAction::GetCount => {
json!({
"status": "success",
"statusCode": 200,
"unified": {
"count": 1
},
"passthrough": {},
"meta": meta
})
}
CrudAction::Upsert => {
json!({
"status": "success",
"statusCode": 200,
"unified": common_model.sample,
"passthrough": {},
"meta": meta
})
}
CrudAction::Update => {
json!({
"status": "success",
"statusCode": 200,
"unified": {},
"passthrough": {},
"meta": meta
})
}
CrudAction::Delete => {
json!({
"status": "success",
"statusCode": 200,
"unified": {},
"passthrough": {},
"meta": meta
})
}
CrudAction::Custom => {
unimplemented!()
}
}
}
}
#[cfg(test)]
mod tests {
use crate::prelude::connection::api_model_config::AuthMethod;
use super::*;
use serde_json::json;
#[test]
fn test_deserialize_auth_method() {
let bearer_token = json!({
"type": "BearerToken",
"value": "some_token"
});
let api_key = json!({
"type": "ApiKey",
"key": "X-Api-Key",
"value": "some_key"
});
let basic_auth = json!({
"type": "BasicAuth",
"username": "username",
"password": "password"
});
let deserialized_bearer_token: AuthMethod = serde_json::from_value(bearer_token).unwrap();
let deserialized_api_key: AuthMethod = serde_json::from_value(api_key).unwrap();
let deserialized_basic_auth: AuthMethod = serde_json::from_value(basic_auth).unwrap();
assert_eq!(
deserialized_bearer_token,
AuthMethod::BearerToken {
value: "some_token".to_string()
}
);
assert_eq!(
deserialized_api_key,
AuthMethod::ApiKey {
key: "X-Api-Key".to_string(),
value: "some_key".to_string()
}
);
assert_eq!(
deserialized_basic_auth,
AuthMethod::BasicAuth {
username: "username".to_string(),
password: "password".to_string()
}
);
}
#[test]
fn test_deserialize_parameter_location() {
let query_parameter = json!("QueryParameter");
let request_body = json!("RequestBody");
let header = json!("Header");
let deserialized_query_parameter: ParameterLocation =
serde_json::from_value(query_parameter).unwrap();
let deserialized_request_body: ParameterLocation =
serde_json::from_value(request_body).unwrap();
let deserialized_header: ParameterLocation = serde_json::from_value(header).unwrap();
assert_eq!(
deserialized_query_parameter,
ParameterLocation::QueryParameter
);
assert_eq!(deserialized_request_body, ParameterLocation::RequestBody);
assert_eq!(deserialized_header, ParameterLocation::Header);
}
#[test]
fn test_model_config_deserializing() {
let sample_config = json!({
"_id" : "conn_mod_def::AAAAAAAAAAA::AAAAAAAAAAAAAAAAAAAAAA",
"connectionPlatform" : "stripe",
"connectionDefinitionId" : "conn_def::AAAAAAAAAAA::AAAAAAAAAAAAAAAAAAAAAA",
"platformVersion" : "v1",
"title" : "Get Webhook Endpoints",
"name" : "webhook_endpoints",
"key" : "api::stripe::v1::Webhook::getOne::webhook_endpoints",
"modelName" : "Webhook",
"action" : "GET",
"actionName": "getOne",
"baseUrl" : "https://api.stripe.com/v1",
"path" : "webhook_endpoints",
"authMethod" : {
"type" : "BearerToken",
"value" : "stripe_secret_key"
},
"samples" : {
"queryParams": null,
"pathParams": null,
"body": null
},
"schemas" : {
"headers": null,
"queryParams": null,
"pathParams": null,
"body": null
},
"paths": null,
"responses": [],
"headers" : null,
"queryParams" : null,
"pullFrequency" : 5,
"batchSize" : 100,
"cursor" : {
"paramName" : "starting_after",
"location" : "QueryParameter",
"format" : "{cursor}",
"cursorPath" : "_.body.id",
"dataPath" : "_.body.data",
"jsExtractorFunction" : null,
"resetOnEnd" : true
},
"limit" : {
"paramName" : "limit",
"location" : "QueryParameter"
},
"sleepAfterFinish" : 86400,
"updateConfig" : null,
"enabled" : true,
"_version" : "1.0.0",
"testConnectionStatus": {
"lastTestedAt": 1697833149,
"state" : {
"success" : {
"response" : "{}",
"requestPayload" : "{}"
}
}
},
"createdAt": 1697833149,
"updatedAt": 1697833149,
"updated": false,
"version": "1.0.0",
"lastModifiedBy": "system",
"deleted": false,
"changeLog": {},
"tags": [],
"active": true,
"deprecated": false,
"isDefaultCrudMapping": false,
});
let model_config: ConnectionModelDefinition =
serde_json::from_value(sample_config).expect("Failed to deserialize ModelConfig");
assert_eq!(model_config.name, "webhook_endpoints");
assert_eq!(model_config.action, http::Method::GET);
assert_eq!(model_config.action_name, CrudAction::GetOne);
let PlatformInfo::Api(platform_info) = model_config.platform_info;
assert_eq!(platform_info.base_url, "https://api.stripe.com/v1");
assert_eq!(platform_info.path, "webhook_endpoints");
assert_eq!(
platform_info.auth_method,
AuthMethod::BearerToken {
value: "stripe_secret_key".to_string()
}
);
assert_eq!(platform_info.query_params, None);
assert_eq!(platform_info.headers, None);
if let Some(ExtractorConfig {
pull_frequency,
batch_size,
cursor,
limit,
sleep_after_finish,
update_config,
enabled,
}) = model_config.extractor_config
{
assert_eq!(pull_frequency, 5);
assert_eq!(batch_size, 100);
assert_eq!(sleep_after_finish, 86400);
assert_eq!(
limit,
Some(LimitConfig {
param_name: "limit".to_string(),
location: ParameterLocation::QueryParameter
})
);
assert_eq!(
cursor,
CursorConfig {
param_name: Some("starting_after".to_string()),
location: Some(ParameterLocation::QueryParameter),
format: Some("{cursor}".to_string()),
cursor_path: "_.body.id".to_string(),
data_path: "_.body.data".to_string(),
reset_on_end: true,
js_extractor_function: None
}
);
assert_eq!(update_config, None);
assert!(enabled);
} else {
panic!("Wrong api config type");
}
}
}