1use serde::{Deserialize, Serialize};
4use std::collections::BTreeSet;
5use std::sync::OnceLock;
6
7use crate::CascadeLevel;
8
9#[derive(
10 Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize,
11)]
12#[serde(rename_all = "camelCase")]
13pub enum CascadeOriginV0 {
14 UserAgent,
15 User,
16 #[default]
17 Author,
18 Inline,
19}
20
21impl CascadeOriginV0 {
22 pub const fn is_author(&self) -> bool {
23 matches!(self, Self::Author)
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
28#[serde(rename_all = "camelCase")]
29pub struct CascadeOriginDriverV0 {
30 pub origin: CascadeOriginV0,
31 pub important: bool,
32 pub level: CascadeLevel,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub enum CascadeWinnerAxisV0 {
38 CascadeLevel,
39 LayerRank,
40 ScopeProximity,
41 Specificity,
42 SourceOrder,
43}
44
45#[derive(Debug, Deserialize)]
46#[serde(rename_all = "camelCase")]
47struct CascadeDriverCensusV0 {
48 schema_version: String,
49 product: String,
50 levels: Vec<CascadeDriverLevelV0>,
51 winner_axes: Vec<CascadeDriverAxisV0>,
52}
53
54#[derive(Debug, Deserialize)]
55#[serde(rename_all = "camelCase")]
56struct CascadeDriverLevelV0 {
57 level: String,
58 status: String,
59 driver_inputs: Vec<String>,
60 #[serde(default)]
61 follow_up: Option<String>,
62}
63
64#[derive(Debug, Deserialize)]
65#[serde(rename_all = "camelCase")]
66struct CascadeDriverAxisV0 {
67 axis: CascadeWinnerAxisV0,
68 status: String,
69}
70
71const CASCADE_DRIVER_CENSUS_JSON: &str = include_str!("../data/cascade-driver-census.json");
72static CASCADE_DRIVER_CENSUS: OnceLock<Result<CascadeDriverCensusV0, String>> = OnceLock::new();
73
74fn cascade_driver_census_v0() -> Option<&'static CascadeDriverCensusV0> {
75 CASCADE_DRIVER_CENSUS
76 .get_or_init(|| {
77 serde_json::from_str(CASCADE_DRIVER_CENSUS_JSON).map_err(|error| error.to_string())
78 })
79 .as_ref()
80 .ok()
81}
82
83pub fn cascade_driven_levels_v0() -> Vec<CascadeLevel> {
84 cascade_driver_census_v0()
85 .into_iter()
86 .flat_map(|census| census.levels.iter())
87 .filter(|entry| entry.status == "driven")
88 .filter_map(|entry| cascade_level_from_name_v0(entry.level.as_str()))
89 .collect()
90}
91
92pub fn cascade_driven_winner_axes_v0() -> Vec<CascadeWinnerAxisV0> {
93 cascade_driver_census_v0()
94 .into_iter()
95 .flat_map(|census| census.winner_axes.iter())
96 .filter(|entry| entry.status == "driven")
97 .map(|entry| entry.axis)
98 .collect()
99}
100
101pub fn cascade_driver_census_is_consistent_v0() -> bool {
102 let Some(census) = cascade_driver_census_v0() else {
103 return false;
104 };
105 let catalog = cascade_level_catalog_v0();
106 let levels = census
107 .levels
108 .iter()
109 .filter_map(|entry| cascade_level_from_name_v0(entry.level.as_str()))
110 .collect::<Vec<_>>();
111 let driven = census
112 .levels
113 .iter()
114 .filter(|entry| entry.status == "driven")
115 .filter_map(|entry| cascade_level_from_name_v0(entry.level.as_str()))
116 .collect::<Vec<_>>();
117 let expected_driven = cascade_origin_driver_catalog_v0()
118 .into_iter()
119 .map(|driver| driver.level)
120 .collect::<BTreeSet<_>>();
121 let all_levels_have_evidence = census.levels.iter().all(|entry| {
122 (entry.status == "driven" && !entry.driver_inputs.is_empty() && entry.follow_up.is_none())
123 || (entry.status == "deferred"
124 && entry.driver_inputs.is_empty()
125 && entry
126 .follow_up
127 .as_deref()
128 .is_some_and(|value| !value.is_empty()))
129 });
130 let expected_axes = [
131 CascadeWinnerAxisV0::CascadeLevel,
132 CascadeWinnerAxisV0::LayerRank,
133 CascadeWinnerAxisV0::ScopeProximity,
134 CascadeWinnerAxisV0::Specificity,
135 CascadeWinnerAxisV0::SourceOrder,
136 ];
137 census.schema_version == "0"
138 && census.product == "omena-cascade.driver-census"
139 && levels == catalog
140 && driven.into_iter().collect::<BTreeSet<_>>() == expected_driven
141 && all_levels_have_evidence
142 && census.winner_axes.len() == expected_axes.len()
143 && census
144 .winner_axes
145 .iter()
146 .all(|entry| entry.status == "driven")
147 && census
148 .winner_axes
149 .iter()
150 .map(|entry| entry.axis)
151 .eq(expected_axes)
152}
153
154fn cascade_level_from_name_v0(name: &str) -> Option<CascadeLevel> {
155 cascade_level_catalog_v0()
156 .into_iter()
157 .find(|level| cascade_level_name_v0(*level) == name)
158}
159
160pub const fn cascade_level_for_origin(origin: CascadeOriginV0, important: bool) -> CascadeLevel {
161 match (origin, important) {
162 (CascadeOriginV0::UserAgent, false) => CascadeLevel::UserAgentNormal,
163 (CascadeOriginV0::User, false) => CascadeLevel::UserNormal,
164 (CascadeOriginV0::Author, false) => CascadeLevel::AuthorNormal,
165 (CascadeOriginV0::Inline, false) => CascadeLevel::InlineNormal,
166 (CascadeOriginV0::UserAgent, true) => CascadeLevel::UserAgentImportant,
167 (CascadeOriginV0::User, true) => CascadeLevel::UserImportant,
168 (CascadeOriginV0::Author | CascadeOriginV0::Inline, true) => CascadeLevel::AuthorImportant,
169 }
170}
171
172pub const fn cascade_level_catalog_v0() -> [CascadeLevel; 9] {
173 [
174 CascadeLevel::UserAgentNormal,
175 CascadeLevel::UserNormal,
176 CascadeLevel::AuthorNormal,
177 CascadeLevel::InlineNormal,
178 CascadeLevel::Animation,
179 CascadeLevel::AuthorImportant,
180 CascadeLevel::UserImportant,
181 CascadeLevel::UserAgentImportant,
182 CascadeLevel::Transition,
183 ]
184}
185
186pub const fn cascade_level_name_v0(level: CascadeLevel) -> &'static str {
187 match level {
188 CascadeLevel::UserAgentNormal => "userAgentNormal",
189 CascadeLevel::UserNormal => "userNormal",
190 CascadeLevel::AuthorNormal => "authorNormal",
191 CascadeLevel::InlineNormal => "inlineNormal",
192 CascadeLevel::Animation => "animation",
193 CascadeLevel::AuthorImportant => "authorImportant",
194 CascadeLevel::UserImportant => "userImportant",
195 CascadeLevel::UserAgentImportant => "userAgentImportant",
196 CascadeLevel::Transition => "transition",
197 }
198}
199
200pub const fn cascade_origin_driver_catalog_v0() -> [CascadeOriginDriverV0; 8] {
201 [
202 origin_driver(CascadeOriginV0::UserAgent, false),
203 origin_driver(CascadeOriginV0::User, false),
204 origin_driver(CascadeOriginV0::Author, false),
205 origin_driver(CascadeOriginV0::Inline, false),
206 origin_driver(CascadeOriginV0::Author, true),
207 origin_driver(CascadeOriginV0::Inline, true),
208 origin_driver(CascadeOriginV0::User, true),
209 origin_driver(CascadeOriginV0::UserAgent, true),
210 ]
211}
212
213const fn origin_driver(origin: CascadeOriginV0, important: bool) -> CascadeOriginDriverV0 {
214 CascadeOriginDriverV0 {
215 origin,
216 important,
217 level: cascade_level_for_origin(origin, important),
218 }
219}