tokmd_types/
packet_siblings.rs1use serde::{Deserialize, Serialize};
7
8pub const MANUAL_CANDIDATES_FILE: &str = "manual-candidates.json";
10
11pub const CARDS_FILE: &str = "cards.json";
13
14pub const MANUAL_CANDIDATES_SCHEMA: &str = "manual-candidates/v1";
16
17#[derive(Debug, Clone, PartialEq, Eq, Default)]
19pub struct PacketSiblingInputs {
20 pub manual_candidates: Option<ManualCandidatesFile>,
21 pub cards: Option<CardsFile>,
22 pub load_notes: Vec<String>,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct PacketRenderBundle {
28 pub manifest: super::TokmdPacketsManifest,
29 pub siblings: PacketSiblingInputs,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
34pub struct ManualCandidatesFile {
35 pub schema_version: String,
36 #[serde(default)]
37 pub candidates: Vec<ManualCandidateRecord>,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
42pub struct ManualCandidateRecord {
43 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub id: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
46 pub title: Option<String>,
47 #[serde(default, skip_serializing_if = "Option::is_none")]
48 pub invariant: Option<String>,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub safe_caller: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub unsafe_operation: Option<String>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub operation_family: Option<String>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub location_text: Option<String>,
57 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub proof_mode: Option<String>,
59 #[serde(default, skip_serializing_if = "Option::is_none")]
60 pub fix_boundary: Option<String>,
61 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub pr_aperture: Option<String>,
63 #[serde(default, skip_serializing_if = "Vec::is_empty")]
64 pub test_targets: Vec<String>,
65 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub do_not_touch: Vec<String>,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
71pub struct CardsFile {
72 #[serde(default, skip_serializing_if = "Option::is_none")]
73 pub schema_version: Option<String>,
74 #[serde(default, alias = "review_cards")]
75 pub cards: Vec<ReviewCardRecord>,
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
80pub struct ReviewCardRecord {
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub id: Option<String>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub title: Option<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub location_text: Option<String>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub unsafe_operation: Option<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub operation_family: Option<String>,
91}
92
93impl ManualCandidatesFile {
94 pub fn schema_matches(&self) -> bool {
96 self.schema_version == MANUAL_CANDIDATES_SCHEMA
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn manual_candidates_roundtrip() {
106 let file = ManualCandidatesFile {
107 schema_version: MANUAL_CANDIDATES_SCHEMA.into(),
108 candidates: vec![ManualCandidateRecord {
109 id: Some("seed-42".into()),
110 title: Some("UTF-8 boundary".into()),
111 invariant: Some("buffer bounds".into()),
112 ..ManualCandidateRecord::default()
113 }],
114 };
115 let json = serde_json::to_string(&file).unwrap();
116 let back: ManualCandidatesFile = serde_json::from_str(&json).unwrap();
117 assert_eq!(back, file);
118 assert!(back.schema_matches());
119 }
120
121 #[test]
122 fn cards_roundtrip_with_review_cards_alias() {
123 let raw =
124 r#"{"schema_version":"0.2","review_cards":[{"id":"rc-17","title":"slice read"}]}"#;
125 let file: CardsFile = serde_json::from_str(raw).unwrap();
126 assert_eq!(file.cards.len(), 1);
127 assert_eq!(file.cards[0].id.as_deref(), Some("rc-17"));
128 }
129}