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