speccoder_core/
context.rs1use serde::{Deserialize, Serialize};
8
9#[derive(
11 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, schemars::JsonSchema,
12)]
13#[serde(rename_all = "snake_case")]
14pub enum ContextKind {
15 Constitution,
17 Plan,
19 Story,
21 Task,
23 Rejection,
25 RetryDigest,
27 Issue,
29 Scenario,
31 Note,
33 Comment,
35 TestLayout,
37 Chip,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
43pub struct ContextEntry {
44 pub kind: ContextKind,
46 pub source: String,
49 pub reference: String,
51 pub sha256: String,
53 pub bytes: u64,
55 pub cut: bool,
57}
58
59#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
61pub struct ContextManifest {
62 pub entries: Vec<ContextEntry>,
64 pub digest: String,
66}
67
68impl ContextManifest {
69 #[must_use]
71 pub fn canonical(entries: &[ContextEntry]) -> String {
72 let mut sorted = entries.to_vec();
73 sorted.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
74 serde_json::to_string(&sorted).unwrap_or_default()
75 }
76
77 #[must_use]
79 pub fn of(mut entries: Vec<ContextEntry>, hash: impl Fn(&[u8]) -> String) -> Self {
80 let digest = hash(Self::canonical(&entries).as_bytes());
81 entries.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
82 Self { entries, digest }
83 }
84
85 pub fn of_kind(&self, kind: ContextKind) -> impl Iterator<Item = &ContextEntry> {
87 self.entries.iter().filter(move |e| e.kind == kind)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn entry(kind: ContextKind, reference: &str) -> ContextEntry {
96 ContextEntry {
97 kind,
98 source: "person".to_owned(),
99 reference: reference.to_owned(),
100 sha256: "00".to_owned(),
101 bytes: 2,
102 cut: false,
103 }
104 }
105
106 #[test]
107 fn the_digest_does_not_depend_on_the_order_parts_were_added() {
108 let h = |b: &[u8]| format!("{}", b.len());
109 let a = ContextManifest::of(
110 vec![
111 entry(ContextKind::Issue, "#42"),
112 entry(ContextKind::Story, "/triad"),
113 ],
114 h,
115 );
116 let b = ContextManifest::of(
117 vec![
118 entry(ContextKind::Story, "/triad"),
119 entry(ContextKind::Issue, "#42"),
120 ],
121 h,
122 );
123 assert_eq!(a, b);
124 assert_eq!(a.entries[0].kind, ContextKind::Story);
125 assert_eq!(a.of_kind(ContextKind::Issue).count(), 1);
126 }
127}