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}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
41pub struct ContextEntry {
42 pub kind: ContextKind,
44 pub source: String,
47 pub reference: String,
49 pub sha256: String,
51 pub bytes: u64,
53 pub cut: bool,
55}
56
57#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
59pub struct ContextManifest {
60 pub entries: Vec<ContextEntry>,
62 pub digest: String,
64}
65
66impl ContextManifest {
67 #[must_use]
69 pub fn canonical(entries: &[ContextEntry]) -> String {
70 let mut sorted = entries.to_vec();
71 sorted.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
72 serde_json::to_string(&sorted).unwrap_or_default()
73 }
74
75 #[must_use]
77 pub fn of(mut entries: Vec<ContextEntry>, hash: impl Fn(&[u8]) -> String) -> Self {
78 let digest = hash(Self::canonical(&entries).as_bytes());
79 entries.sort_by(|a, b| (a.kind, &a.reference).cmp(&(b.kind, &b.reference)));
80 Self { entries, digest }
81 }
82
83 pub fn of_kind(&self, kind: ContextKind) -> impl Iterator<Item = &ContextEntry> {
85 self.entries.iter().filter(move |e| e.kind == kind)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 fn entry(kind: ContextKind, reference: &str) -> ContextEntry {
94 ContextEntry {
95 kind,
96 source: "person".to_owned(),
97 reference: reference.to_owned(),
98 sha256: "00".to_owned(),
99 bytes: 2,
100 cut: false,
101 }
102 }
103
104 #[test]
105 fn the_digest_does_not_depend_on_the_order_parts_were_added() {
106 let h = |b: &[u8]| format!("{}", b.len());
107 let a = ContextManifest::of(
108 vec![
109 entry(ContextKind::Issue, "#42"),
110 entry(ContextKind::Story, "/triad"),
111 ],
112 h,
113 );
114 let b = ContextManifest::of(
115 vec![
116 entry(ContextKind::Story, "/triad"),
117 entry(ContextKind::Issue, "#42"),
118 ],
119 h,
120 );
121 assert_eq!(a, b);
122 assert_eq!(a.entries[0].kind, ContextKind::Story);
123 assert_eq!(a.of_kind(ContextKind::Issue).count(), 1);
124 }
125}