Skip to main content

ephemeris_forge_github/api/model/
rulesets.rs

1mod head;
2
3use ephemeris_forge::Limitation;
4use facet::Facet;
5use facet_value::Value;
6
7use self::head::RuleHead;
8use crate::api::ForgeError;
9use crate::{
10    ActorType, BypassActor, BypassMode, Enforcement, Rules, Ruleset as Definition, Target,
11};
12
13/// A repository ruleset as GitHub reports it.
14#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct Ruleset {
16    /// Stable API identifier used by the update endpoint.
17    pub id: u64,
18    /// The ruleset's name.
19    pub name: String,
20    /// The part of the ruleset that the configuration declares.
21    pub definition: Definition,
22    /// The rules of types the configuration cannot declare, exactly as GitHub
23    /// returned them. An update sends them back unchanged.
24    pub unmanaged_rules: Vec<Value>,
25    /// What the decoder found that the configuration cannot declare, such as
26    /// an unmanaged rule type.
27    pub limitations: Vec<Limitation>,
28}
29
30/// A ruleset summary, as returned by the listing endpoint before the detail
31/// endpoint is read by id.
32#[derive(Facet)]
33pub(crate) struct RulesetSummary {
34    pub(crate) id: u64,
35}
36
37#[derive(Default, Facet)]
38pub(crate) struct RulesetConditions {
39    #[facet(default)]
40    ref_name: RefNameCondition,
41}
42
43#[derive(Default, Facet)]
44struct RefNameCondition {
45    #[facet(default)]
46    include: Vec<String>,
47    #[facet(default)]
48    exclude: Vec<String>,
49}
50
51#[derive(Facet)]
52pub(crate) struct BypassActorWire {
53    #[facet(default)]
54    actor_id: Option<i64>,
55    actor_type: ActorType,
56    #[facet(default)]
57    bypass_mode: Option<BypassMode>,
58}
59
60/// The wire shape of GitHub's ruleset detail endpoint.
61#[derive(Facet)]
62pub(crate) struct RulesetResponse {
63    pub(crate) id: u64,
64    pub(crate) name: String,
65    pub(crate) target: String,
66    pub(crate) enforcement: Enforcement,
67    #[facet(default)]
68    pub(crate) bypass_actors: Vec<BypassActorWire>,
69    #[facet(default)]
70    pub(crate) conditions: RulesetConditions,
71    #[facet(default)]
72    pub(crate) rules: Vec<Value>,
73}
74
75impl TryFrom<RulesetResponse> for Ruleset {
76    type Error = ForgeError;
77
78    /// Decode leniently: a rule type the configuration cannot declare is
79    /// reported as a limitation and kept as an opaque value.
80    fn try_from(value: RulesetResponse) -> Result<Self, ForgeError> {
81        let mut limitations = Vec::new();
82        let mut unmanaged_rules = Vec::new();
83        let mut rules = Rules::none();
84        let name = value.name.as_str();
85        for rule in value.rules {
86            let head = RuleHead::decode(name, &rule)?;
87            if head.kind == "pull_request" && head.has_entries("required_reviewers") {
88                limitations.push(Limitation::RequiredReviewersDropped {
89                    ruleset: name.to_owned(),
90                });
91            }
92            if !rules.absorb(&head.kind, &head.decoder(name))? {
93                limitations.push(Limitation::UnmanagedRuleType {
94                    ruleset: name.to_owned(),
95                    kind: head.kind.clone(),
96                });
97                unmanaged_rules.push(rule);
98            }
99        }
100        Ok(Self {
101            id: value.id,
102            definition: Definition {
103                target: Target::observed(&value.target),
104                enforcement: value.enforcement,
105                include: value.conditions.ref_name.include,
106                exclude: value.conditions.ref_name.exclude,
107                bypass_actors: value
108                    .bypass_actors
109                    .into_iter()
110                    .map(|actor| BypassActor {
111                        actor_type: actor.actor_type,
112                        actor_id: actor.actor_id,
113                        bypass_mode: actor.bypass_mode.unwrap_or(BypassMode::Always),
114                    })
115                    .collect(),
116                rules,
117            },
118            name: value.name,
119            unmanaged_rules,
120            limitations,
121        })
122    }
123}