fastsim_schema/v1/
search.rs1use super::IndexEntryV1;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
14pub struct QueryV1 {
15 pub fastsim_version: Option<u32>,
16 pub powertrain: Option<String>,
17 pub make: Option<String>,
18 pub model: Option<String>,
19 pub year: Option<String>,
20 pub variant: Option<String>,
21}
22
23pub fn search_v1<'a>(entries: &'a [IndexEntryV1], query: &QueryV1) -> Vec<&'a IndexEntryV1> {
30 entries.iter().filter(|e| matches(e, query)).collect()
31}
32
33fn matches(entry: &IndexEntryV1, query: &QueryV1) -> bool {
34 let model_query = query.model.as_deref().map(|v| v.to_ascii_lowercase());
35
36 query
37 .fastsim_version
38 .is_none_or(|v| entry.fastsim_version == v)
39 && query
40 .powertrain
41 .as_deref()
42 .is_none_or(|v| entry.powertrain.eq_ignore_ascii_case(v))
43 && query
44 .make
45 .as_deref()
46 .is_none_or(|v| entry.make.eq_ignore_ascii_case(v))
47 && model_query
48 .as_deref()
49 .is_none_or(|v| entry.model.to_ascii_lowercase().contains(v))
50 && query.year.as_deref().is_none_or(|v| entry.year == v)
51 && query
52 .variant
53 .as_deref()
54 .is_none_or(|v| entry.variant.eq_ignore_ascii_case(v))
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 fn entry(id: &str) -> IndexEntryV1 {
62 format!("{id}.yaml").parse::<IndexEntryV1>().unwrap()
63 }
64
65 fn sample_entries() -> Vec<IndexEntryV1> {
66 vec![
67 entry("v1/fastsim-3/conv/ford/fusion/2012/base/r1"),
68 entry("v1/fastsim-3/bev/ford/f-150-lightning/2024/base/r1"),
69 entry("v1/fastsim-3/bev/tesla/model-3/2020/base/r1"),
70 entry("v1/fastsim-3/conv/ford/f-150/2018/base/r1"),
71 ]
72 }
73
74 #[test]
75 fn empty_query_matches_everything() {
76 let entries = sample_entries();
77 let results = search_v1(&entries, &QueryV1::default());
78 assert_eq!(results.len(), 4);
79 }
80
81 #[test]
82 fn filters_by_make_case_insensitively() {
83 let entries = sample_entries();
84 let query = QueryV1 {
85 make: Some("FORD".to_string()),
86 ..Default::default()
87 };
88 let results = search_v1(&entries, &query);
89 assert_eq!(results.len(), 3);
90 assert!(results.iter().all(|e| e.make == "ford"));
91 }
92
93 #[test]
94 fn filters_by_powertrain_and_make_together() {
95 let entries = sample_entries();
96 let query = QueryV1 {
97 powertrain: Some("bev".to_string()),
98 make: Some("ford".to_string()),
99 ..Default::default()
100 };
101 let results = search_v1(&entries, &query);
102 assert_eq!(results.len(), 1);
103 assert_eq!(results[0].model, "f-150-lightning");
104 }
105
106 #[test]
107 fn model_substring_match_is_case_insensitive() {
108 let entries = sample_entries();
109 let query = QueryV1 {
110 model: Some("F-150".to_string()),
111 ..Default::default()
112 };
113 let results = search_v1(&entries, &query);
114 assert_eq!(results.len(), 2); }
116
117 #[test]
118 fn no_matches_returns_empty() {
119 let entries = sample_entries();
120 let query = QueryV1 {
121 make: Some("toyota".to_string()),
122 ..Default::default()
123 };
124 assert!(search_v1(&entries, &query).is_empty());
125 }
126
127 #[test]
128 fn fastsim_version_filters_correctly() {
129 let entries = sample_entries();
130 let query = QueryV1 {
131 fastsim_version: Some(3),
132 ..Default::default()
133 };
134 assert_eq!(search_v1(&entries, &query).len(), 4);
135
136 let query = QueryV1 {
137 fastsim_version: Some(4),
138 ..Default::default()
139 };
140 assert!(search_v1(&entries, &query).is_empty());
141 }
142}