來源格式與預期記錄
這是原創假設轉換例子。輸入限下列欄位的普通、無循環 JSON 形記錄,統一記錄格式及拒絕代碼由本例訂明。若換來源,先修改格式約定。
每次改動要有非空 field,from、to 只用文字、布林、有限數字或 null。同一欄下一次 from 須與上次 to 同類型且相等,數字正零、負零視為相等。按欄合成 before、after 及 trace,逐步仍記原位置。不能銜接便整筆拒絕,值未變的事件也保留。
| 個案 | 輸入記錄(JSON) | 預期輸出(JSON) |
|---|---|---|
| 整理連續欄位歷史 | {"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"B","to":"C"}]} | {"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"C","trace":[{"position":1,"from":"A","to":"B"},{"position":2,"from":"B","to":"C"}]}]}} |
| 拒絕歷史接不上 | {"events":[{"field":"title","from":"A","to":"B"},{"field":"title","from":"X","to":"C"}]} | {"ok":false,"error":"AUDIT_CONTINUITY"} |
| 保留沒有改值的事件 | {"events":[{"field":"title","from":"A","to":"A"}]} | {"ok":true,"value":{"changes":[{"field":"title","before":"A","after":"A","trace":[{"position":1,"from":"A","to":"A"}]}]}} |
在本機執行轉換
把完整例子以 JavaScript 模組在 Node.js 執行,內含純轉換函數、三個輸入及寫明的預期輸出,對不上便由斷言停止。拒絕個案回傳 ok=false 及代碼,不回傳半份統一記錄。
import assert from 'node:assert/strict';
function auditChain(i){
const fail=error=>({ok:false,error});if(!i||!Array.isArray(i.events))return fail('AUDIT_EVENTS');
const scalar=v=>v===null||typeof v==='string'||typeof v==='boolean'||(typeof v==='number'&&Number.isFinite(v));
const fields=new Map();
for(let n=0;n<i.events.length;n++){
const e=i.events[n];if(!e||typeof e.field!=='string'||!e.field||!scalar(e.from)||!scalar(e.to))return fail('AUDIT_VALUE');
let f=fields.get(e.field);
if(f&&f.after!==e.from)return fail('AUDIT_CONTINUITY');
if(!f){f={field:e.field,before:e.from,after:e.from,trace:[]};fields.set(e.field,f);}
f.after=e.to;f.trace.push({position:n+1,from:e.from,to:e.to});
}
return {ok:true,value:{changes:[...fields.values()]}};
}
// Original synthetic cases; expected outputs are literal records.
const cases = [
{
"label": "Fold a continuous field history",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "B"
},
{
"field": "title",
"from": "B",
"to": "C"
}
]
},
"expected": {
"ok": true,
"value": {
"changes": [
{
"field": "title",
"before": "A",
"after": "C",
"trace": [
{
"position": 1,
"from": "A",
"to": "B"
},
{
"position": 2,
"from": "B",
"to": "C"
}
]
}
]
}
}
},
{
"label": "Reject broken continuity",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "B"
},
{
"field": "title",
"from": "X",
"to": "C"
}
]
},
"expected": {
"ok": false,
"error": "AUDIT_CONTINUITY"
}
},
{
"label": "Preserve unchanged event",
"input": {
"events": [
{
"field": "title",
"from": "A",
"to": "A"
}
]
},
"expected": {
"ok": true,
"value": {
"changes": [
{
"field": "title",
"before": "A",
"after": "A",
"trace": [
{
"position": 1,
"from": "A",
"to": "A"
}
]
}
]
}
}
}
];
for (const sample of cases) {
const actual = auditChain(sample.input);
assert.deepEqual(actual, sample.expected);
console.log(sample.label, JSON.stringify(actual));
}
判清邊界個案
title 由 A→B→C,除了最後 C 還保留甚麼?
查看參考答案
before=A、after=C,位置 1、2 的兩次事件都保存。若 A→B 後是 X→C,則 AUDIT_CONTINUITY 拒絕。輸出描述改動,未執行更新。