Skip to main content

ephemeris_forge_github/api/model/rulesets/
head.rs

1use facet::{Def, Facet, PrimitiveType, Shape, StructKind, Type, UserType};
2use facet_value::{VArray, Value, from_value, to_value};
3
4use crate::api::ForgeError;
5use crate::config::{MergeMethod, RuleDecoder};
6
7#[derive(Facet)]
8pub(super) struct RuleHead {
9    #[facet(rename = "type")]
10    pub(super) kind: String,
11    #[facet(default)]
12    parameters: Option<Value>,
13}
14
15impl RuleHead {
16    /// A decoder for the parameters of this rule.
17    pub(super) const fn decoder<'a>(&'a self, ruleset: &'a str) -> HeadDecoder<'a> {
18        HeadDecoder {
19            head: self,
20            ruleset,
21        }
22    }
23
24    pub(super) fn decode(ruleset: &str, rule: &Value) -> Result<Self, ForgeError> {
25        from_value(rule.clone()).map_err(|source| ForgeError::RuleDecode {
26            ruleset: ruleset.to_owned(),
27            source: Box::new(source),
28        })
29    }
30
31    /// Whether the parameter `key` is a list with at least one entry.
32    pub(super) fn has_entries(&self, key: &str) -> bool {
33        self.parameters
34            .as_ref()
35            .and_then(Value::as_object)
36            .and_then(|parameters| parameters.get(key))
37            .and_then(Value::as_array)
38            .is_some_and(|entries| !entries.is_empty())
39    }
40
41    /// Decode the rule's parameters, which a rule without any decodes as empty.
42    ///
43    /// Keys that `T` does not declare and keys that are null are dropped
44    /// first, so GitHub can add parameters. Declared keys it omits are then
45    /// filled with GitHub's defaults.
46    pub(super) fn parameters<T: for<'a> Facet<'a>>(&self, ruleset: &str) -> Result<T, ForgeError> {
47        let mut parameters = self
48            .parameters
49            .clone()
50            .unwrap_or_else(|| Vec::<(String, Value)>::new().into_iter().collect());
51        prune(&mut parameters, T::SHAPE);
52        fill(&mut parameters, T::SHAPE);
53        from_value(parameters).map_err(|source| ForgeError::RuleParametersDecode {
54            ruleset: ruleset.to_owned(),
55            rule: self.kind.clone(),
56            source: Box::new(source),
57        })
58    }
59}
60
61pub(super) struct HeadDecoder<'a> {
62    head: &'a RuleHead,
63    ruleset: &'a str,
64}
65
66impl RuleDecoder for HeadDecoder<'_> {
67    type Error = ForgeError;
68
69    fn parameters<T: for<'a> Facet<'a>>(&self) -> Result<T, ForgeError> {
70        self.head.parameters(self.ruleset)
71    }
72}
73
74fn prune(value: &mut Value, shape: &'static Shape) {
75    let mut shape = shape;
76    while let Def::Option(option) = shape.def {
77        shape = option.t();
78    }
79    if let Def::List(list) = shape.def {
80        if let Some(entries) = value.as_array_mut() {
81            for entry in entries.iter_mut() {
82                prune(entry, list.t());
83            }
84        }
85    } else if let Type::User(UserType::Struct(declared)) = shape.ty
86        && declared.kind == StructKind::Struct
87        && let Some(object) = value.as_object_mut()
88    {
89        let keys: Vec<String> = object.keys().map(|key| key.as_str().to_owned()).collect();
90        for key in keys {
91            let field = declared
92                .fields
93                .iter()
94                .find(|field| field.effective_name() == key);
95            match (field, object.get_mut(&key)) {
96                (Some(field), Some(entry)) if !entry.is_null() => prune(entry, field.shape()),
97                _ => {
98                    object.remove(&key);
99                }
100            }
101        }
102    }
103}
104
105fn fill(value: &mut Value, shape: &'static Shape) {
106    let mut shape = shape;
107    while let Def::Option(option) = shape.def {
108        shape = option.t();
109    }
110    if let Def::List(list) = shape.def {
111        if let Some(entries) = value.as_array_mut() {
112            for entry in entries.iter_mut() {
113                fill(entry, list.t());
114            }
115        }
116    } else if let Type::User(UserType::Struct(declared)) = shape.ty
117        && declared.kind == StructKind::Struct
118        && let Some(object) = value.as_object_mut()
119    {
120        for field in declared.fields {
121            let key = field.effective_name();
122            if let Some(entry) = object.get_mut(key) {
123                fill(entry, field.shape());
124            } else if let Some(default) = default_for(key, field.shape()) {
125                object.insert(key, default);
126            }
127        }
128    }
129}
130
131fn default_for(key: &str, shape: &'static Shape) -> Option<Value> {
132    // GitHub omits the merge methods when every method is allowed.
133    if key == "allowed_merge_methods" {
134        return to_value(&MergeMethod::ALL).ok();
135    }
136    match (shape.def, shape.ty) {
137        (Def::Option(_), _) => Some(Value::NULL),
138        (Def::List(_), _) => Some(VArray::new().into()),
139        (_, Type::Primitive(PrimitiveType::Boolean)) => Some(Value::FALSE),
140        (_, Type::Primitive(PrimitiveType::Numeric(_))) => Some(Value::from(0u8)),
141        _ => None,
142    }
143}