batuta/oracle/cookbook/
mod.rs1mod recipes;
10mod recipes_more;
11mod recipes_rlhf_alignment;
12mod recipes_rlhf_efficiency;
13mod recipes_rlhf_training;
14
15use serde::{Deserialize, Serialize};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct Recipe {
20 pub id: String,
22 pub title: String,
24 pub problem: String,
26 pub components: Vec<String>,
28 pub tags: Vec<String>,
30 pub code: String,
32 pub test_code: String,
34 pub related: Vec<String>,
36}
37
38impl Recipe {
39 pub fn new(id: impl Into<String>, title: impl Into<String>) -> Self {
40 Self {
41 id: id.into(),
42 title: title.into(),
43 problem: String::new(),
44 components: Vec::new(),
45 tags: Vec::new(),
46 code: String::new(),
47 test_code: String::new(),
48 related: Vec::new(),
49 }
50 }
51
52 pub fn with_problem(mut self, problem: impl Into<String>) -> Self {
53 self.problem = problem.into();
54 self
55 }
56
57 pub fn with_components(mut self, components: Vec<&str>) -> Self {
58 self.components = components.into_iter().map(String::from).collect();
59 self
60 }
61
62 pub fn with_tags(mut self, tags: Vec<&str>) -> Self {
63 self.tags = tags.into_iter().map(String::from).collect();
64 self
65 }
66
67 pub fn with_code(mut self, code: impl Into<String>) -> Self {
68 self.code = code.into();
69 self
70 }
71
72 pub fn with_test_code(mut self, test_code: impl Into<String>) -> Self {
73 self.test_code = test_code.into();
74 self
75 }
76
77 pub fn with_related(mut self, related: Vec<&str>) -> Self {
78 self.related = related.into_iter().map(String::from).collect();
79 self
80 }
81}
82
83#[derive(Debug, Clone, Default)]
85pub struct Cookbook {
86 pub(crate) recipes: Vec<Recipe>,
87}
88
89impl Cookbook {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn standard() -> Self {
97 let mut cookbook = Self::new();
98 recipes::register_all(&mut cookbook);
99 cookbook
100 }
101
102 pub fn recipes(&self) -> &[Recipe] {
104 &self.recipes
105 }
106
107 pub fn find_by_tag(&self, tag: &str) -> Vec<&Recipe> {
109 self.recipes.iter().filter(|r| r.tags.iter().any(|t| t.eq_ignore_ascii_case(tag))).collect()
110 }
111
112 pub fn find_by_component(&self, component: &str) -> Vec<&Recipe> {
114 self.recipes
115 .iter()
116 .filter(|r| r.components.iter().any(|c| c.eq_ignore_ascii_case(component)))
117 .collect()
118 }
119
120 pub fn get(&self, id: &str) -> Option<&Recipe> {
122 self.recipes.iter().find(|r| r.id == id)
123 }
124
125 pub fn search(&self, query: &str) -> Vec<&Recipe> {
127 let query_lower = query.to_lowercase();
128 self.recipes
129 .iter()
130 .filter(|r| {
131 r.title.to_lowercase().contains(&query_lower)
132 || r.problem.to_lowercase().contains(&query_lower)
133 || r.tags.iter().any(|t| t.to_lowercase().contains(&query_lower))
134 })
135 .collect()
136 }
137
138 pub(crate) fn add(&mut self, recipe: Recipe) {
140 self.recipes.push(recipe);
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_cookbook_standard() {
150 let cookbook = Cookbook::standard();
151 assert!(!cookbook.recipes().is_empty());
152 }
153
154 #[test]
155 fn test_find_by_tag() {
156 let cookbook = Cookbook::standard();
157 let wasm_recipes = cookbook.find_by_tag("wasm");
158 assert!(!wasm_recipes.is_empty());
159 }
160
161 #[test]
162 fn test_find_by_component() {
163 let cookbook = Cookbook::standard();
164 let simular_recipes = cookbook.find_by_component("simular");
165 assert!(!simular_recipes.is_empty());
166 }
167
168 #[test]
169 fn test_get_by_id() {
170 let cookbook = Cookbook::standard();
171 let recipe = cookbook.get("wasm-zero-js");
172 assert!(recipe.is_some());
173 assert_eq!(recipe.expect("unexpected failure").title, "Zero-JS WASM Application");
174 }
175
176 #[test]
177 fn test_search() {
178 let cookbook = Cookbook::standard();
179 let results = cookbook.search("random forest");
180 assert!(!results.is_empty());
181 }
182
183 #[test]
184 fn test_all_recipes_have_code() {
185 let cookbook = Cookbook::standard();
186 for recipe in cookbook.recipes() {
187 assert!(!recipe.code.is_empty(), "Recipe '{}' has empty code field", recipe.id);
188 }
189 }
190
191 #[test]
192 fn test_recipe_code_contains_rust() {
193 let cookbook = Cookbook::standard();
194 let recipe = cookbook.get("ml-random-forest").expect("ml-random-forest must exist");
195 assert!(recipe.code.contains("use "), "ml-random-forest code should contain 'use ' import");
196 assert!(
197 recipe.code.contains("aprender"),
198 "ml-random-forest code should reference aprender"
199 );
200 }
201
202 #[test]
203 fn test_all_recipes_have_test_code() {
204 let cookbook = Cookbook::standard();
205 for recipe in cookbook.recipes() {
206 assert!(
207 !recipe.test_code.is_empty(),
208 "Recipe '{}' has empty test_code field",
209 recipe.id
210 );
211 }
212 }
213
214 #[test]
215 fn test_test_code_has_cfg_test() {
216 let cookbook = Cookbook::standard();
217 for recipe in cookbook.recipes() {
218 assert!(
219 recipe.test_code.contains("#[cfg("),
220 "Recipe '{}' test_code should contain #[cfg(test)] or #[cfg(all(test, ...))]",
221 recipe.id
222 );
223 }
224 }
225
226 #[test]
227 fn test_test_code_has_test_attr() {
228 let cookbook = Cookbook::standard();
229 for recipe in cookbook.recipes() {
230 assert!(
231 recipe.test_code.contains("#[test]"),
232 "Recipe '{}' test_code should contain #[test] attribute",
233 recipe.id
234 );
235 }
236 }
237}