chore: cargo fmt

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Gregory Schier
2026-09-10 19:35:33 -07:00
co-authored by Claude Fable 5.1
parent 7f8b7bf567
commit 2f5639a26f
23 changed files with 676 additions and 318 deletions
@@ -20,7 +20,9 @@ impl UpdateSource {
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum ModelChangeEvent {
Upsert { created: bool },
Upsert {
created: bool,
},
/// A delete for a workspace implies deletion of every model in that
/// workspace — children are bulk-deleted without their own change rows or
/// events, and consumers must prune the subtree themselves (the frontend
+4 -5
View File
@@ -29,11 +29,10 @@ use yaak_plugins::api::{PluginNameVersion, PluginSearchResponse, PluginUpdatesRe
use yaak_plugins::events::{
CallFolderActionRequest, CallGrpcRequestActionRequest, CallHttpRequestActionRequest,
CallWebsocketRequestActionRequest, CallWorkspaceActionRequest, FilterResponse,
GetFolderActionsResponse, GetGrpcRequestActionsResponse,
GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse,
GetHttpRequestActionsResponse, GetTemplateFunctionConfigResponse,
GetTemplateFunctionSummaryResponse, GetThemesResponse, GetWebsocketRequestActionsResponse,
GetWorkspaceActionsResponse, JsonPrimitive, RenderPurpose,
GetFolderActionsResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse,
GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse,
GetTemplateFunctionConfigResponse, GetTemplateFunctionSummaryResponse, GetThemesResponse,
GetWebsocketRequestActionsResponse, GetWorkspaceActionsResponse, JsonPrimitive, RenderPurpose,
};
use yaak_plugins::plugin_meta::PluginMetadata;
use yaak_sse::sse::ServerSentEvent;
+5 -5
View File
@@ -13,8 +13,9 @@ pub type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send + 'static>>;
/// `dispatch` call frame, so it cannot borrow, and contexts are cheap clones
/// (handles and `Arc`s). Synchronous handlers wrap into this via `rpc_handler!`
/// with no visible change.
type HandlerFn<Ctx> =
Box<dyn Fn(Ctx, serde_json::Value) -> BoxFuture<Result<serde_json::Value, RpcError>> + Send + Sync>;
type HandlerFn<Ctx> = Box<
dyn Fn(Ctx, serde_json::Value) -> BoxFuture<Result<serde_json::Value, RpcError>> + Send + Sync,
>;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RpcError {
@@ -249,9 +250,8 @@ macro_rules! rpc_handler_async {
Box::new(|ctx, payload| {
Box::pin(async move {
let req = serde_json::from_value(payload).map_err($crate::RpcError::from)?;
let res = $f(ctx, req)
.await
.map_err(|e| $crate::RpcError { message: e.to_string() })?;
let res =
$f(ctx, req).await.map_err(|e| $crate::RpcError { message: e.to_string() })?;
serde_json::to_value(res).map_err($crate::RpcError::from)
})
})
+1 -1
View File
@@ -10,8 +10,8 @@ use yaak_database::SqlitePool;
pub mod blob_manager;
pub mod client_db;
pub mod cookies;
mod connection_or_tx;
pub mod cookies;
pub mod error;
pub mod migrate;
pub mod models;
@@ -96,8 +96,7 @@ impl<'a> ClientDb<'a> {
let Some(response_id) = path.file_name().and_then(|n| n.to_str()) else {
continue;
};
if self.find_optional::<HttpResponse>(HttpResponseIden::Id, response_id).is_some()
{
if self.find_optional::<HttpResponse>(HttpResponseIden::Id, response_id).is_some() {
continue;
}
if fs::remove_file(&path).is_ok() {
+1 -1
View File
@@ -24,8 +24,8 @@ mod websocket_events;
mod websocket_requests;
mod workspace_metas;
pub mod workspaces;
pub use model_changes::PersistedModelChange;
pub(crate) use duplicate_name::conflict_free_name;
pub use model_changes::PersistedModelChange;
const MAX_HISTORY_ITEMS: usize = 20;
+3 -3
View File
@@ -8,9 +8,9 @@ use crate::models::{
GrpcEventIden, GrpcRequest, GrpcRequestIden, HttpRequest, HttpRequestHeader, HttpRequestIden,
HttpResponse, HttpResponseEvent, HttpResponseEventIden, HttpResponseIden, ImportSource,
ImportSourceIden, ResolvedHttpRequestSettings, ResolvedSetting, SyncState, SyncStateIden,
WebsocketConnection,
WebsocketConnectionIden, WebsocketEvent, WebsocketEventIden, WebsocketRequest,
WebsocketRequestIden, Workspace, WorkspaceIden, WorkspaceMeta, WorkspaceMetaIden,
WebsocketConnection, WebsocketConnectionIden, WebsocketEvent, WebsocketEventIden,
WebsocketRequest, WebsocketRequestIden, Workspace, WorkspaceIden, WorkspaceMeta,
WorkspaceMetaIden,
};
use crate::util::UpdateSource;
use log::warn;
+2 -10
View File
@@ -125,19 +125,11 @@ pub enum ImportPlanWarningLevel {
impl ImportPlanWarning {
pub fn info(title: impl Into<String>, detail: impl Into<String>) -> Self {
Self {
title: title.into(),
detail: detail.into(),
level: ImportPlanWarningLevel::Info,
}
Self { title: title.into(), detail: detail.into(), level: ImportPlanWarningLevel::Info }
}
pub fn warning(title: impl Into<String>, detail: impl Into<String>) -> Self {
Self {
title: title.into(),
detail: detail.into(),
level: ImportPlanWarningLevel::Warning,
}
Self { title: title.into(), detail: detail.into(), level: ImportPlanWarningLevel::Warning }
}
}
+1 -1
View File
@@ -1,5 +1,4 @@
use crate::error::Result;
use yaak_models::blob_manager::BlobManager;
use crate::models::SyncModel;
use chrono::Utc;
use log::{info, warn};
@@ -11,6 +10,7 @@ use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use ts_rs::TS;
use yaak_models::blob_manager::BlobManager;
use yaak_models::client_db::ClientDb;
use yaak_models::models::{SyncState, WorkspaceMeta};
use yaak_models::util::{UpdateSource, get_workspace_export_resources};
+141 -114
View File
@@ -521,16 +521,11 @@ fn record_import_source(
};
let incoming_keys: BTreeSet<String> = plan.source_keys.values().cloned().collect();
let existing = match resolve_linked_source(
db,
&workspace_id,
&plan.importer,
origin,
&incoming_keys,
)? {
LinkedSource::Linked(source) => Some(source),
LinkedSource::Ambiguous(_) | LinkedSource::Unlinked => None,
};
let existing =
match resolve_linked_source(db, &workspace_id, &plan.importer, origin, &incoming_keys)? {
LinkedSource::Linked(source) => Some(source),
LinkedSource::Ambiguous(_) | LinkedSource::Unlinked => None,
};
let import_source = db.upsert_import_source(
&ImportSource {
id: existing.map(|s| s.id).unwrap_or_default(),
@@ -659,9 +654,8 @@ fn resolve_linked_source(
incoming_keys: &BTreeSet<String>,
) -> Result<LinkedSource> {
let sources = db.list_import_sources(workspace_id)?;
let same_origin = |source: &ImportSource| {
source.importer == importer && source.origin == origin.origin
};
let same_origin =
|source: &ImportSource| source.importer == importer && source.origin == origin.origin;
let mut overlapping = Vec::new();
for source in &sources {
@@ -743,12 +737,18 @@ fn merge_with_linked_source(
let mut current_models: BTreeMap<String, Value> = BTreeMap::new();
{
let mut consider = |planned_id: &str, resource: ImportResourceType| -> Result<()> {
let Some(key) = plan.source_keys.get(planned_id) else { return Ok(()) };
let Some(row) = rows.get(key) else { return Ok(()) };
let Some(key) = plan.source_keys.get(planned_id) else {
return Ok(());
};
let Some(row) = rows.get(key) else {
return Ok(());
};
if ImportResourceType::from_str(&row.model_type) != Some(resource) {
return Ok(());
}
let Some(model_id) = row.model_id.as_deref() else { return Ok(()) };
let Some(model_id) = row.model_id.as_deref() else {
return Ok(());
};
let Some(current) = existing_model_json(&db, resource, model_id)? else {
return Ok(());
};
@@ -819,11 +819,9 @@ fn merge_with_linked_source(
// environment: it came from this source, so the imported-copy separation does not apply.
let mut restored_base_names = BTreeSet::new();
for (i, v) in plan.resources.environments.iter_mut().enumerate() {
let is_current_base = current_models
.get(&v.id)
.and_then(|m| m.get("parentModel"))
.and_then(|p| p.as_str())
== Some("workspace");
let is_current_base =
current_models.get(&v.id).and_then(|m| m.get("parentModel")).and_then(|p| p.as_str())
== Some("workspace");
if !is_current_base {
continue;
}
@@ -936,16 +934,13 @@ fn merge_with_linked_source(
(false, false) => (ImportPlanAction::Unchanged, false, None),
(true, false) => (ImportPlanAction::Update, true, None),
(false, true) => (ImportPlanAction::KeepLocal, false, None),
(true, true) => (
ImportPlanAction::Conflict,
true,
Some(ImportConflictResolution::KeepMine),
),
(true, true) => {
(ImportPlanAction::Conflict, true, Some(ImportConflictResolution::KeepMine))
}
};
// A resource the source moved into a folder that isn't imported keeps its place
// until that folder is: nothing can be written into a folder that will not exist.
let reason =
(!reachable).then_some(ImportPlanReason::MovedIntoIgnoredFolder);
let reason = (!reachable).then_some(ImportPlanReason::MovedIntoIgnoredFolder);
let mut planned = item(action, selected && reachable, resolution, reason);
planned.changed_fields = changed_fields(&incoming, &current);
items.push(planned);
@@ -956,16 +951,32 @@ fn merge_with_linked_source(
classify(AnyModel::Folder(v.clone()), ImportResourceType::Folder, v.folder_id.clone())?;
}
for v in &plan.resources.http_requests {
classify(AnyModel::HttpRequest(v.clone()), ImportResourceType::HttpRequest, v.folder_id.clone())?;
classify(
AnyModel::HttpRequest(v.clone()),
ImportResourceType::HttpRequest,
v.folder_id.clone(),
)?;
}
for v in &plan.resources.grpc_requests {
classify(AnyModel::GrpcRequest(v.clone()), ImportResourceType::GrpcRequest, v.folder_id.clone())?;
classify(
AnyModel::GrpcRequest(v.clone()),
ImportResourceType::GrpcRequest,
v.folder_id.clone(),
)?;
}
for v in &plan.resources.websocket_requests {
classify(AnyModel::WebsocketRequest(v.clone()), ImportResourceType::WebsocketRequest, v.folder_id.clone())?;
classify(
AnyModel::WebsocketRequest(v.clone()),
ImportResourceType::WebsocketRequest,
v.folder_id.clone(),
)?;
}
for v in &plan.resources.environments {
classify(AnyModel::Environment(v.clone()), ImportResourceType::Environment, v.parent_id.clone())?;
classify(
AnyModel::Environment(v.clone()),
ImportResourceType::Environment,
v.parent_id.clone(),
)?;
}
}
@@ -1030,16 +1041,32 @@ fn create_only_items(plan: &ImportPlan) -> Vec<ImportPlanItem> {
push(AnyModel::Folder(v.clone()), ImportResourceType::Folder, v.folder_id.clone());
}
for v in &plan.resources.http_requests {
push(AnyModel::HttpRequest(v.clone()), ImportResourceType::HttpRequest, v.folder_id.clone());
push(
AnyModel::HttpRequest(v.clone()),
ImportResourceType::HttpRequest,
v.folder_id.clone(),
);
}
for v in &plan.resources.grpc_requests {
push(AnyModel::GrpcRequest(v.clone()), ImportResourceType::GrpcRequest, v.folder_id.clone());
push(
AnyModel::GrpcRequest(v.clone()),
ImportResourceType::GrpcRequest,
v.folder_id.clone(),
);
}
for v in &plan.resources.websocket_requests {
push(AnyModel::WebsocketRequest(v.clone()), ImportResourceType::WebsocketRequest, v.folder_id.clone());
push(
AnyModel::WebsocketRequest(v.clone()),
ImportResourceType::WebsocketRequest,
v.folder_id.clone(),
);
}
for v in &plan.resources.environments {
push(AnyModel::Environment(v.clone()), ImportResourceType::Environment, v.parent_id.clone());
push(
AnyModel::Environment(v.clone()),
ImportResourceType::Environment,
v.parent_id.clone(),
);
}
items
}
@@ -1060,7 +1087,14 @@ enum KeyStatus<'a> {
fn comparable(value: Value) -> Value {
let mut value = strip_ids(value);
if let Some(object) = value.as_object_mut() {
for field in ["model", "workspaceId", "createdAt", "updatedAt", "base", "sortPriority"] {
for field in [
"model",
"workspaceId",
"createdAt",
"updatedAt",
"base",
"sortPriority",
] {
object.remove(field);
}
}
@@ -1208,10 +1242,7 @@ fn validate_plan(plan: &ImportPlan) -> Result<()> {
let updates_own_base = |id: &str| {
plan.items.iter().any(|i| {
i.model_id == id
&& !matches!(
i.action,
ImportPlanAction::Create | ImportPlanAction::Ignored
)
&& !matches!(i.action, ImportPlanAction::Create | ImportPlanAction::Ignored)
})
};
if plan
@@ -1376,9 +1407,8 @@ fn assign_source_keys(
for (i, v) in resources.environments.iter().enumerate() {
let source = original.environments.get(i);
let name = source.map(|s| s.name.as_str()).unwrap_or(v.name.as_str());
let parent_id = source.and_then(|s| {
if s.parent_model == "folder" { s.parent_id.as_deref() } else { None }
});
let parent_id = source
.and_then(|s| if s.parent_model == "folder" { s.parent_id.as_deref() } else { None });
let ancestry = ancestry_path(&folder_tree, parent_id);
let key = fallback_key("environment", &ancestry, name);
candidates.push((&v.id, plugin_key(source.map(|s| &s.id)), key.clone(), key));
@@ -1386,8 +1416,7 @@ fn assign_source_keys(
for (i, v) in resources.folders.iter().enumerate() {
let source = original.folders.get(i);
let name = source.map(|s| s.name.as_str()).unwrap_or(v.name.as_str());
let ancestry =
ancestry_path(&folder_tree, source.and_then(|s| s.folder_id.as_deref()));
let ancestry = ancestry_path(&folder_tree, source.and_then(|s| s.folder_id.as_deref()));
let key = fallback_key("folder", &ancestry, name);
candidates.push((&v.id, plugin_key(source.map(|s| &s.id)), key.clone(), key));
}
@@ -2602,10 +2631,9 @@ mod tests {
// Anything new inside that folder can't be created either, so it waits for the folder.
let mut resources = with_extra_folder(true);
resources.http_requests.push(HttpRequest {
folder_id: Some("fl_extra".to_string()),
..extra_request()
});
resources
.http_requests
.push(HttpRequest { folder_id: Some("fl_extra".to_string()), ..extra_request() });
let plan = replan(&query_manager, &workspace_id, resources);
let extra = item_by_name(&plan, "Extra Request");
assert_eq!(extra.action, ImportPlanAction::Create);
@@ -2930,73 +2958,72 @@ mod tests {
assert_eq!(not_wanted, 2, "the skipped folder and its request are remembered: {rows:?}");
}
#[test]
fn desktop_style_json_roundtrip_records_source() {
let (query_manager, _blob_manager, _rx) =
yaak_models::init_in_memory().expect("initialize database");
let plan = plan_import_resources(
&query_manager,
"OpenAPI".to_string(),
ImportDestination::NewWorkspace,
imported_resources(),
None,
Some(linked_origin()),
)
.expect("plan import");
let json = serde_json::to_string(&plan).expect("serialize plan");
let plan: ImportPlan = serde_json::from_str(&json).expect("deserialize plan");
let committed = commit_import_plan(&query_manager, plan).expect("commit");
let workspace_id = committed.workspaces[0].id.clone();
let source = query_manager
.connect()
.find_import_source(&workspace_id, "OpenAPI", "/tmp/api.yaml")
.expect("query")
.expect("source recorded after JSON round-trip");
assert_eq!(source.origin_label, "api.yaml");
}
#[test]
fn selected_keep_local_reverts_the_local_edit() {
let (query_manager, _blob_manager, _rx) =
yaak_models::init_in_memory().expect("initialize database");
let committed = first_import(&query_manager);
let workspace_id = committed.workspaces[0].id.clone();
let root_id = committed
.http_requests
.iter()
.find(|r| r.name == "Root Request")
.expect("root request")
.id
.clone();
{
let db = query_manager.connect();
let root = db.get_http_request(&root_id).expect("get root");
db.upsert_http_request(
&HttpRequest { url: "https://example.com/root-local".to_string(), ..root },
&UpdateSource::Background,
#[test]
fn desktop_style_json_roundtrip_records_source() {
let (query_manager, _blob_manager, _rx) =
yaak_models::init_in_memory().expect("initialize database");
let plan = plan_import_resources(
&query_manager,
"OpenAPI".to_string(),
ImportDestination::NewWorkspace,
imported_resources(),
None,
Some(linked_origin()),
)
.expect("edit root locally");
.expect("plan import");
let json = serde_json::to_string(&plan).expect("serialize plan");
let plan: ImportPlan = serde_json::from_str(&json).expect("deserialize plan");
let committed = commit_import_plan(&query_manager, plan).expect("commit");
let workspace_id = committed.workspaces[0].id.clone();
let source = query_manager
.connect()
.find_import_source(&workspace_id, "OpenAPI", "/tmp/api.yaml")
.expect("query")
.expect("source recorded after JSON round-trip");
assert_eq!(source.origin_label, "api.yaml");
}
let mut plan = replan(&query_manager, &workspace_id, imported_resources());
let root = item_by_name(&plan, "Root Request");
assert_eq!(root.action, ImportPlanAction::KeepLocal);
assert!(!root.selected, "keep-local defaults to keeping the local edit");
for item in plan.items.iter_mut() {
if item.action == ImportPlanAction::KeepLocal {
item.selected = true;
#[test]
fn selected_keep_local_reverts_the_local_edit() {
let (query_manager, _blob_manager, _rx) =
yaak_models::init_in_memory().expect("initialize database");
let committed = first_import(&query_manager);
let workspace_id = committed.workspaces[0].id.clone();
let root_id = committed
.http_requests
.iter()
.find(|r| r.name == "Root Request")
.expect("root request")
.id
.clone();
{
let db = query_manager.connect();
let root = db.get_http_request(&root_id).expect("get root");
db.upsert_http_request(
&HttpRequest { url: "https://example.com/root-local".to_string(), ..root },
&UpdateSource::Background,
)
.expect("edit root locally");
}
}
commit_import_plan(&query_manager, plan).expect("commit revert");
assert_eq!(
query_manager.connect().get_http_request(&root_id).expect("get root").url,
"https://example.com/root",
"selected keep-local must revert to the source version"
);
let plan = replan(&query_manager, &workspace_id, imported_resources());
assert_eq!(item_by_name(&plan, "Root Request").action, ImportPlanAction::Unchanged);
}
let mut plan = replan(&query_manager, &workspace_id, imported_resources());
let root = item_by_name(&plan, "Root Request");
assert_eq!(root.action, ImportPlanAction::KeepLocal);
assert!(!root.selected, "keep-local defaults to keeping the local edit");
for item in plan.items.iter_mut() {
if item.action == ImportPlanAction::KeepLocal {
item.selected = true;
}
}
commit_import_plan(&query_manager, plan).expect("commit revert");
assert_eq!(
query_manager.connect().get_http_request(&root_id).expect("get root").url,
"https://example.com/root",
"selected keep-local must revert to the source version"
);
let plan = replan(&query_manager, &workspace_id, imported_resources());
assert_eq!(item_by_name(&plan, "Root Request").action, ImportPlanAction::Unchanged);
}
}
+13 -7
View File
@@ -668,9 +668,10 @@ mod tests {
let (query_manager, _temp_dir) = seed_query_manager();
let store = FakeBodyStore { body: b"hello".to_vec(), reads: RefCell::new(Vec::new()) };
let info_payload = InternalEventPayload::GetHttpResponseBodyInfoRequest(
GetHttpResponseBodyInfoRequest { response_id: "rs_test".to_string() },
);
let info_payload =
InternalEventPayload::GetHttpResponseBodyInfoRequest(GetHttpResponseBodyInfoRequest {
response_id: "rs_test".to_string(),
});
let info = handle_shared_plugin_event(
&query_manager,
&store,
@@ -716,9 +717,10 @@ mod tests {
#[test]
fn an_unreadable_response_body_becomes_an_error_reply() {
let (query_manager, _temp_dir) = seed_query_manager();
let payload = InternalEventPayload::GetHttpResponseBodyInfoRequest(
GetHttpResponseBodyInfoRequest { response_id: "rs_never_persisted".to_string() },
);
let payload =
InternalEventPayload::GetHttpResponseBodyInfoRequest(GetHttpResponseBodyInfoRequest {
response_id: "rs_never_persisted".to_string(),
});
let result = dispatch(
&query_manager,
&payload,
@@ -727,7 +729,11 @@ mod tests {
match result {
GroupedPluginEvent::Handled(Some(InternalEventPayload::ErrorResponse(resp))) => {
assert!(resp.error.contains("rs_never_persisted"), "unhelpful error: {}", resp.error)
assert!(
resp.error.contains("rs_never_persisted"),
"unhelpful error: {}",
resp.error
)
}
other => panic!("unexpected missing-response result: {other:?}"),
}