mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-19 17:17:50 +01:00
303 lines
10 KiB
Rust
303 lines
10 KiB
Rust
use crate::error::Error::UnknownModel;
|
|
use crate::error::Result;
|
|
use chrono::NaiveDateTime;
|
|
use log::{debug, warn};
|
|
use serde::{Deserialize, Deserializer, Serialize};
|
|
use serde_yaml::{Mapping, Value};
|
|
use sha1::{Digest, Sha1};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use ts_rs::TS;
|
|
use yaak_models::models::{
|
|
AnyModel, Environment, Folder, GrpcRequest, HttpRequest, WebsocketRequest, Workspace,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, TS)]
|
|
#[serde(rename_all = "snake_case", tag = "type")]
|
|
#[ts(export, export_to = "gen_models.ts")]
|
|
pub enum SyncModel {
|
|
Workspace(Workspace),
|
|
Environment(Environment),
|
|
Folder(Folder),
|
|
HttpRequest(HttpRequest),
|
|
GrpcRequest(GrpcRequest),
|
|
WebsocketRequest(WebsocketRequest),
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for SyncModel {
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
where
|
|
D: Deserializer<'de>,
|
|
{
|
|
use serde_path_to_error as spte;
|
|
let mut v = Value::deserialize(deserializer)?;
|
|
let model = match v.get("model") {
|
|
Some(Value::String(model)) => model.clone(),
|
|
_ => "".to_string(),
|
|
};
|
|
let model = model.as_str();
|
|
|
|
let obj = v
|
|
.as_mapping_mut()
|
|
.ok_or_else(|| serde::de::Error::custom("expected object for SyncModel"))?;
|
|
|
|
// Dispatch to CHILD types (no recursion)
|
|
match model {
|
|
"workspace" => {
|
|
let x: Workspace = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::Workspace(x))
|
|
}
|
|
"environment" => {
|
|
migrate_environment(obj);
|
|
let x: Environment = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::Environment(x))
|
|
}
|
|
"folder" => {
|
|
let x: Folder = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::Folder(x))
|
|
}
|
|
"http_request" => {
|
|
let x: HttpRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::HttpRequest(x))
|
|
}
|
|
"grpc_request" => {
|
|
let x: GrpcRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::GrpcRequest(x))
|
|
}
|
|
"websocket_request" => {
|
|
let x: WebsocketRequest = spte::deserialize(v).map_err(serde::de::Error::custom)?;
|
|
Ok(SyncModel::WebsocketRequest(x))
|
|
}
|
|
other => Err(serde::de::Error::unknown_variant(
|
|
other,
|
|
&[
|
|
"workspace",
|
|
"environment",
|
|
"folder",
|
|
"http_request",
|
|
"grpc_request",
|
|
"websocket_request",
|
|
],
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn migrate_environment(obj: &mut Mapping) {
|
|
match (obj.get("base"), obj.get("parentModel")) {
|
|
(Some(Value::Bool(base)), None) => {
|
|
debug!("Migrating legacy environment {:?}", obj.get("id"));
|
|
if *base {
|
|
obj.insert("parentModel".into(), "workspace".into());
|
|
} else {
|
|
obj.insert("parentModel".into(), "environment".into());
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
impl SyncModel {
|
|
pub fn from_bytes(content: Vec<u8>, file_path: &Path) -> Result<Option<(SyncModel, String)>> {
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(&content);
|
|
let checksum = hex::encode(hasher.finalize());
|
|
let content_str = String::from_utf8(content.clone()).unwrap_or_default();
|
|
|
|
// Check for some strings that will be in a model file for sure. If these strings
|
|
// don't exist, then it's probably not a Yaak file.
|
|
if !content_str.contains("model") || !content_str.contains("id") {
|
|
return Ok(None);
|
|
}
|
|
|
|
let ext = file_path.extension().unwrap_or_default();
|
|
if ext == "yml" || ext == "yaml" {
|
|
Ok(match serde_yaml::from_str::<SyncModel>(&content_str) {
|
|
Ok(m) => Some((m, checksum)),
|
|
Err(e) => {
|
|
warn!("Error parsing {:?} {:?}", file_path.file_name(), e);
|
|
None
|
|
}
|
|
})
|
|
} else if ext == "json" {
|
|
Ok(match serde_json::from_str::<SyncModel>(&content_str) {
|
|
Ok(m) => Some((m, checksum)),
|
|
Err(e) => {
|
|
warn!("Error parsing {:?} {:?}", file_path.file_name(), e);
|
|
None
|
|
}
|
|
})
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub fn from_file(file_path: &Path) -> Result<Option<(SyncModel, String)>> {
|
|
let content = match fs::read(file_path) {
|
|
Ok(c) => c,
|
|
Err(_) => return Ok(None),
|
|
};
|
|
|
|
Self::from_bytes(content, file_path)
|
|
}
|
|
|
|
pub fn to_file_contents(&self, rel_path: &Path) -> Result<(Vec<u8>, String)> {
|
|
let ext = rel_path.extension().unwrap_or_default();
|
|
let content = if ext == "yaml" || ext == "yml" {
|
|
serde_yaml::to_string(self)?
|
|
} else {
|
|
serde_json::to_string(self)?
|
|
};
|
|
|
|
let mut hasher = Sha1::new();
|
|
hasher.update(&content);
|
|
let checksum = hex::encode(hasher.finalize());
|
|
|
|
Ok((content.into_bytes(), checksum))
|
|
}
|
|
|
|
pub fn id(&self) -> String {
|
|
match self.clone() {
|
|
SyncModel::Workspace(m) => m.id,
|
|
SyncModel::Environment(m) => m.id,
|
|
SyncModel::Folder(m) => m.id,
|
|
SyncModel::HttpRequest(m) => m.id,
|
|
SyncModel::GrpcRequest(m) => m.id,
|
|
SyncModel::WebsocketRequest(m) => m.id,
|
|
}
|
|
}
|
|
|
|
pub fn workspace_id(&self) -> String {
|
|
match self.clone() {
|
|
SyncModel::Workspace(m) => m.id,
|
|
SyncModel::Environment(m) => m.workspace_id,
|
|
SyncModel::Folder(m) => m.workspace_id,
|
|
SyncModel::HttpRequest(m) => m.workspace_id,
|
|
SyncModel::GrpcRequest(m) => m.workspace_id,
|
|
SyncModel::WebsocketRequest(m) => m.workspace_id,
|
|
}
|
|
}
|
|
|
|
pub fn updated_at(&self) -> NaiveDateTime {
|
|
match self.clone() {
|
|
SyncModel::Workspace(m) => m.updated_at,
|
|
SyncModel::Environment(m) => m.updated_at,
|
|
SyncModel::Folder(m) => m.updated_at,
|
|
SyncModel::HttpRequest(m) => m.updated_at,
|
|
SyncModel::GrpcRequest(m) => m.updated_at,
|
|
SyncModel::WebsocketRequest(m) => m.updated_at,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TryFrom<AnyModel> for SyncModel {
|
|
type Error = crate::error::Error;
|
|
|
|
fn try_from(value: AnyModel) -> Result<Self> {
|
|
let m = match value {
|
|
AnyModel::Environment(m) => SyncModel::Environment(m),
|
|
AnyModel::Folder(m) => SyncModel::Folder(m),
|
|
AnyModel::GrpcRequest(m) => SyncModel::GrpcRequest(m),
|
|
AnyModel::HttpRequest(m) => SyncModel::HttpRequest(m),
|
|
AnyModel::WebsocketRequest(m) => SyncModel::WebsocketRequest(m),
|
|
AnyModel::Workspace(m) => SyncModel::Workspace(m),
|
|
|
|
// Non-sync models
|
|
AnyModel::CookieJar(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::GraphQlIntrospection(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::GrpcConnection(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::GrpcEvent(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::HttpResponse(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::HttpResponseEvent(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::KeyValue(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::Plugin(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::Settings(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::WebsocketConnection(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::WebsocketEvent(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::WorkspaceMeta(m) => return Err(UnknownModel(m.model)),
|
|
AnyModel::SyncState(m) => return Err(UnknownModel(m.model)),
|
|
};
|
|
Ok(m)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod migration_tests {
|
|
use crate::error::Result;
|
|
use crate::models::SyncModel;
|
|
|
|
#[test]
|
|
fn deserializes_environment_via_syncmodel_with_fixups() -> Result<()> {
|
|
let raw = r#"
|
|
type: environment
|
|
model: environment
|
|
id: ev_fAUS49FUN2
|
|
workspaceId: wk_kfSI3JDHd7
|
|
createdAt: 2025-01-11T17:02:58.012792
|
|
updatedAt: 2025-07-23T20:00:46.049649
|
|
name: Global Variables
|
|
public: true
|
|
base: true
|
|
variables: []
|
|
color: null
|
|
"#;
|
|
|
|
let m: SyncModel = serde_yaml::from_str(raw)?;
|
|
match m {
|
|
SyncModel::Environment(env) => {
|
|
assert_eq!(env.parent_model, "workspace".to_string());
|
|
assert_eq!(env.parent_id, None);
|
|
}
|
|
_ => panic!("expected base environment"),
|
|
}
|
|
|
|
let raw = r#"
|
|
type: environment
|
|
model: environment
|
|
id: ev_fAUS49FUN2
|
|
workspaceId: wk_kfSI3JDHd7
|
|
createdAt: 2025-01-11T17:02:58.012792
|
|
updatedAt: 2025-07-23T20:00:46.049649
|
|
name: Global Variables
|
|
public: true
|
|
base: false
|
|
variables: []
|
|
color: null
|
|
"#;
|
|
let m: SyncModel = serde_yaml::from_str(raw)?;
|
|
match m {
|
|
SyncModel::Environment(env) => {
|
|
assert_eq!(env.parent_model, "environment".to_string());
|
|
assert_eq!(env.parent_id, None);
|
|
}
|
|
_ => panic!("expected sub environment"),
|
|
}
|
|
|
|
let raw = r#"
|
|
type: environment
|
|
model: environment
|
|
id: ev_fAUS49FUN2
|
|
parentId: fld_123
|
|
parentModel: folder
|
|
workspaceId: wk_kfSI3JDHd7
|
|
createdAt: 2025-01-11T17:02:58.012792
|
|
updatedAt: 2025-07-23T20:00:46.049649
|
|
name: Folder Environment
|
|
public: true
|
|
base: false
|
|
variables: []
|
|
color: null
|
|
"#;
|
|
let m: SyncModel = serde_yaml::from_str(raw)?;
|
|
match m {
|
|
SyncModel::Environment(env) => {
|
|
assert_eq!(env.parent_model, "folder".to_string());
|
|
assert_eq!(env.parent_id, Some("fld_123".to_string()));
|
|
}
|
|
_ => panic!("expected folder environment"),
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|