mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-09-10 03:41:52 +02:00
refactor(models): share the content-stripping primitive with import
Import already had this: `comparable()` in import.rs, added last week, strips identity fields off a model so a re-import can tell a change from a conflict. Request versioning arrived with its own copy of the same idea. Now both call `yaak_models::content`, which owns the mechanism and — more usefully — the reasoning. Two things had no single home before: why identity fields can never count as content, and why hashing has to sort object keys itself rather than trusting `serde_json::Map` to be a `BTreeMap` (`preserve_order` is on in some builds of this workspace and off in others). The key lists stay separate on purpose, as IDENTITY_KEYS and PLACEMENT_KEYS. Import counts a move as a change, because equality there means "same content in the same place"; versioning must not, or dragging a request around the sidebar would mint versions nobody asked for. A test on each side pins that difference, since the obvious next refactor is to collapse the two lists into one and neither behaviour would fail loudly if you did. Directory sync deliberately keeps its own Sha1 and is not folded in. It checksums the bytes of a file to notice someone edited it on disk, so it has to reflect formatting and key order — the exact things this module discards.
This commit is contained in:
@@ -4,6 +4,7 @@ use log::info;
|
||||
use serde_json::Value;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use yaak_models::client_db::ClientDb;
|
||||
use yaak_models::content::{IDENTITY_KEYS, without_keys};
|
||||
use yaak_models::models::{
|
||||
AnyModel, DEFAULT_REQUEST_MESSAGE_SIZE, Environment, Folder, GrpcRequest, HttpRequest,
|
||||
ImportSource, ImportSourceResource, UpsertModelInfo, WebsocketRequest, Workspace,
|
||||
@@ -841,15 +842,18 @@ fn create_only_items(plan: &ImportPlan) -> Vec<ImportPlanItem> {
|
||||
items
|
||||
}
|
||||
|
||||
/// Strip identity and bookkeeping fields so equality means "same content in the same place".
|
||||
/// The deprecated environment `base` flag mirrors `parentModel`, which is compared already.
|
||||
fn comparable(mut value: Value) -> Value {
|
||||
if let Some(object) = value.as_object_mut() {
|
||||
for field in ["id", "model", "workspaceId", "createdAt", "updatedAt", "base"] {
|
||||
object.remove(field);
|
||||
}
|
||||
}
|
||||
value
|
||||
/// Strip identity fields so equality means "same content in the same place".
|
||||
///
|
||||
/// Placement (`folderId`, `sortPriority`) is deliberately *kept*, which is
|
||||
/// where this parts company with request versioning: a re-import that moved a
|
||||
/// resource somewhere else is a change worth showing, while dragging a request
|
||||
/// around the sidebar is not an edit. See [`yaak_models::content`].
|
||||
///
|
||||
/// The deprecated environment `base` flag mirrors `parentModel`, which is
|
||||
/// compared already.
|
||||
fn comparable(value: Value) -> Value {
|
||||
let stripped = [IDENTITY_KEYS, &["base"]].concat();
|
||||
without_keys(value, &stripped)
|
||||
}
|
||||
|
||||
fn existing_model_json(
|
||||
@@ -1204,6 +1208,42 @@ mod tests {
|
||||
use serde_json::json;
|
||||
use yaak_models::models::{EnvironmentVariable, HttpRequestHeader};
|
||||
|
||||
/// Import and request versioning share the stripping mechanism but not the
|
||||
/// key list, and this is the difference. Versioning drops placement so
|
||||
/// dragging a request around the sidebar is not an edit; import keeps it so
|
||||
/// a source that moved a resource reads as a change. Unifying the two lists
|
||||
/// would silently make a re-import stop noticing moves.
|
||||
#[test]
|
||||
fn comparable_treats_a_move_as_a_change_but_ignores_identity() {
|
||||
let base = json!({
|
||||
"model": "http_request",
|
||||
"id": "rq_1",
|
||||
"workspaceId": "wk_1",
|
||||
"createdAt": "2026-01-01T00:00:00",
|
||||
"updatedAt": "2026-01-01T00:00:00",
|
||||
"folderId": "fl_1",
|
||||
"sortPriority": 1.0,
|
||||
"url": "https://example.com",
|
||||
});
|
||||
|
||||
let mut renamed_identity = base.clone();
|
||||
renamed_identity["id"] = json!("rq_2");
|
||||
renamed_identity["updatedAt"] = json!("2026-09-06T00:00:00");
|
||||
assert_eq!(
|
||||
comparable(renamed_identity),
|
||||
comparable(base.clone()),
|
||||
"identity and timestamps are never content",
|
||||
);
|
||||
|
||||
let mut moved = base.clone();
|
||||
moved["folderId"] = json!("fl_2");
|
||||
assert_ne!(comparable(moved), comparable(base.clone()), "a move is a change");
|
||||
|
||||
let mut resorted = base.clone();
|
||||
resorted["sortPriority"] = json!(99.0);
|
||||
assert_ne!(comparable(resorted), comparable(base), "a reorder is a change");
|
||||
}
|
||||
|
||||
fn destination_workspace() -> Workspace {
|
||||
Workspace {
|
||||
id: "wk_destination".to_string(),
|
||||
|
||||
Reference in New Issue
Block a user