use crate::face_record::{
FaceMatch, FaceRecord, Orientation, OrientationPlane, PERMUTATION_MATRICES,
};
use serde_json::{json, Value};
pub fn face_record_to_json(rec: &FaceRecord) -> Value {
let lb = [rec.il, rec.jl, rec.kl];
let ub = [rec.ih, rec.jh, rec.kh];
let mut obj = json!({
"block_index": rec.block_index,
"lb": lb,
"ub": ub,
});
if let Some(id) = rec.id {
obj["id"] = json!(id);
}
obj
}
pub fn face_match_to_json(fm: &FaceMatch) -> Value {
let perm_idx: i8 = fm
.orientation
.as_ref()
.map(|o| o.permutation_index as i8)
.unwrap_or(0);
let mut obj = json!({
"block1": face_record_to_json(&fm.block1),
"block2": face_record_to_json(&fm.block2),
"permutation_index": perm_idx,
});
if let Some(m) = fm.orientation.as_ref().and_then(|o| o.permutation_matrix) {
obj["permutation_matrix"] = json!([
[m[0][0], m[0][1]],
[m[1][0], m[1][1]],
]);
}
obj
}
pub fn permutation_matrices_json() -> Vec<Value> {
PERMUTATION_MATRICES
.iter()
.map(|m| json!([[m[0][0], m[0][1]], [m[1][0], m[1][1]]]))
.collect()
}
fn array_usize(arr: &[Value], idx: usize, ctx: &str) -> Result<usize, String> {
arr.get(idx)
.ok_or_else(|| format!("{}: missing index {}", ctx, idx))?
.as_u64()
.ok_or_else(|| format!("{}: element {} is not a non-negative integer", ctx, idx))
.map(|v| v as usize)
}
pub fn face_record_from_json(val: &Value) -> Result<FaceRecord, String> {
let block_index = val
.get("block_index")
.ok_or_else(|| "face_record: missing 'block_index'".to_string())?
.as_u64()
.ok_or_else(|| "face_record: 'block_index' is not a non-negative integer".to_string())?
as usize;
let lb = val
.get("lb")
.ok_or_else(|| "face_record: missing 'lb' array".to_string())?
.as_array()
.ok_or_else(|| "face_record: 'lb' is not an array".to_string())?;
let ub = val
.get("ub")
.ok_or_else(|| "face_record: missing 'ub' array".to_string())?
.as_array()
.ok_or_else(|| "face_record: 'ub' is not an array".to_string())?;
if lb.len() != 3 || ub.len() != 3 {
return Err(format!(
"face_record: 'lb' and 'ub' must be 3-element arrays (got lb={}, ub={})",
lb.len(),
ub.len()
));
}
let il = array_usize(lb, 0, "face_record.lb")?;
let jl = array_usize(lb, 1, "face_record.lb")?;
let kl = array_usize(lb, 2, "face_record.lb")?;
let ih = array_usize(ub, 0, "face_record.ub")?;
let jh = array_usize(ub, 1, "face_record.ub")?;
let kh = array_usize(ub, 2, "face_record.ub")?;
let id = val.get("id").and_then(|v| v.as_u64()).map(|v| v as usize);
Ok(FaceRecord {
block_index,
il,
jl,
kl,
ih,
jh,
kh,
id,
u_physical: None,
v_physical: None,
})
}
pub fn face_match_from_json(val: &Value) -> Result<FaceMatch, String> {
let block1_val = val
.get("block1")
.ok_or_else(|| "face_match: missing 'block1'".to_string())?;
let block2_val = val
.get("block2")
.ok_or_else(|| "face_match: missing 'block2'".to_string())?;
let block1 = face_record_from_json(block1_val)
.map_err(|e| format!("face_match.block1: {}", e))?;
let block2 = face_record_from_json(block2_val)
.map_err(|e| format!("face_match.block2: {}", e))?;
let permutation_matrix: Option<[[i8; 2]; 2]> = val
.get("permutation_matrix")
.and_then(|v| v.as_array())
.and_then(|outer| {
if outer.len() != 2 {
return None;
}
let mut m = [[0i8; 2]; 2];
for (r, row) in outer.iter().enumerate() {
let row = row.as_array()?;
if row.len() != 2 {
return None;
}
for (c, cell) in row.iter().enumerate() {
let v = cell.as_i64()?;
if !(-1..=1).contains(&v) {
return None;
}
m[r][c] = v as i8;
}
}
Some(m)
});
let orientation = if let Some(m) = permutation_matrix {
let idx = Orientation::index_from_permutation_matrix(m).unwrap_or(0);
Some(Orientation {
permutation_index: idx,
plane: OrientationPlane::InPlane,
permutation_matrix: Some(m),
})
} else {
val.get("permutation_index")
.and_then(|v| v.as_i64())
.map(|idx| {
let clamped = idx.clamp(0, 7) as u8;
Orientation {
permutation_index: clamped,
plane: OrientationPlane::InPlane,
permutation_matrix: None,
}
})
};
Ok(FaceMatch {
block1,
block2,
points: Vec::new(),
orientation,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn face_record_round_trip() {
let original = FaceRecord {
block_index: 2,
il: 0,
jl: 0,
kl: 0,
ih: 10,
jh: 20,
kh: 0,
id: Some(7),
u_physical: None,
v_physical: None,
};
let as_json = face_record_to_json(&original);
let parsed = face_record_from_json(&as_json).expect("round trip");
assert_eq!(parsed.block_index, original.block_index);
assert_eq!(parsed.il, original.il);
assert_eq!(parsed.jl, original.jl);
assert_eq!(parsed.kl, original.kl);
assert_eq!(parsed.ih, original.ih);
assert_eq!(parsed.jh, original.jh);
assert_eq!(parsed.kh, original.kh);
assert_eq!(parsed.id, original.id);
}
#[test]
fn face_record_directed_corners_preserved() {
let json = json!({
"block_index": 1,
"lb": [24, 0, 0],
"ub": [0, 408, 0]
});
let parsed = face_record_from_json(&json).unwrap();
assert_eq!(parsed.il, 24);
assert_eq!(parsed.ih, 0);
assert!(parsed.id.is_none());
}
#[test]
fn face_record_missing_block_index() {
let json = json!({ "lb": [0, 0, 0], "ub": [1, 1, 1] });
let err = face_record_from_json(&json).unwrap_err();
assert!(err.contains("block_index"));
}
#[test]
fn face_record_wrong_array_length() {
let json = json!({
"block_index": 0,
"lb": [0, 0],
"ub": [1, 1, 1]
});
let err = face_record_from_json(&json).unwrap_err();
assert!(err.contains("3-element"));
}
#[test]
fn face_match_round_trip() {
let original = FaceMatch {
block1: FaceRecord {
block_index: 0,
il: 10,
jl: 0,
kl: 0,
ih: 10,
jh: 5,
kh: 5,
id: None,
u_physical: None,
v_physical: None,
},
block2: FaceRecord {
block_index: 1,
il: 0,
jl: 0,
kl: 0,
ih: 0,
jh: 5,
kh: 5,
id: None,
u_physical: None,
v_physical: None,
},
points: Vec::new(),
orientation: Some(Orientation {
permutation_index: 5,
plane: OrientationPlane::InPlane,
permutation_matrix: None,
}),
};
let as_json = face_match_to_json(&original);
let parsed = face_match_from_json(&as_json).expect("round trip");
assert_eq!(parsed.block1.block_index, 0);
assert_eq!(parsed.block2.block_index, 1);
assert_eq!(parsed.block1.il, 10);
assert_eq!(parsed.block2.ih, 0);
let orient = parsed.orientation.expect("orientation preserved");
assert_eq!(orient.permutation_index, 5);
}
#[test]
fn face_match_without_permutation_index() {
let json = json!({
"block1": { "block_index": 0, "lb": [0, 0, 0], "ub": [1, 1, 0] },
"block2": { "block_index": 1, "lb": [0, 0, 0], "ub": [1, 1, 0] }
});
let parsed = face_match_from_json(&json).unwrap();
assert!(parsed.orientation.is_none());
assert!(parsed.points.is_empty());
}
#[test]
fn face_match_missing_block1() {
let json = json!({
"block2": { "block_index": 1, "lb": [0, 0, 0], "ub": [1, 1, 0] }
});
let err = face_match_from_json(&json).unwrap_err();
assert!(err.contains("block1"));
}
}