git-ents.gitmain
⌘K
foforge
issue.rs87 lines · 3.0 KB · rusthistorycomment on this file
1//! The Issue entity: title, body, state, assignees, and labels.
2//!
3//! Spec coverage: `model.issue`.
4
5use facet::Facet;
6
7use crate::member::MemberId;
8
9/// One issue, living at its own `refs/meta/issues/<id>` ref
10/// (`namespace::issue_ref`, `meta-ref.granularity`).
11///
12/// `model.issue` requires `state` to accept custom values and `assignees`
13/// to accept more than one member — "multiple assignees and custom states
14/// are schema, not platform features" — so `state` is a plain `String`
15/// rather than a fixed enum (contrast [`crate::result::Status`], which
16/// *is* a fixed taxonomy because `model.result-taxonomy` says so
17/// explicitly). Extending this struct with further fields later is a
18/// storage migration (`model.extensibility`, `meta-ref.migration`), not a
19/// platform request.
20///
21/// # Examples
22///
23/// ```
24/// use ents_model::{Issue, MemberId};
25///
26/// let issue = Issue {
27/// title: "gate rejects a valid signature".to_owned(),
28/// body: "steps to reproduce...".to_owned(),
29/// state: "triaged".to_owned(),
30/// assignees: vec![MemberId::new("jdc")],
31/// labels: vec!["bug".to_owned(), "gate".to_owned()],
32/// };
33/// let (id, store) = facet_git_tree::serialize(&issue).expect("serialize");
34/// let back: Issue = facet_git_tree::deserialize(&id, &store).expect("deserialize");
35/// assert_eq!(back, issue);
36/// ```
37// @relation(model.issue, meta-ref.typed-tree, model.extensibility, scope=file)
38#[derive(Debug, Clone, PartialEq, Eq, Facet)]
39pub struct Issue {
40 /// The issue's title.
41 pub title: String,
42 /// The issue's body.
43 pub body: String,
44 /// The issue's current state. Not a fixed enum: custom states are
45 /// schema, not a platform feature (`model.issue`).
46 pub state: String,
47 /// The members assigned to the issue. More than one is ordinary.
48 pub assignees: Vec<MemberId>,
49 /// Free-form labels.
50 pub labels: Vec<String>,
51}
52
53#[cfg(test)]
54mod tests {
55 #![allow(clippy::expect_used, reason = "unit test")]
56
57 use facet_git_tree::{deserialize, serialize};
58 use rstest::rstest;
59
60 use super::*;
61
62 #[rstest]
63 #[case::default_state_no_assignees("open", vec![], vec![])]
64 #[case::custom_state_one_assignee("triaged", vec![MemberId::new("jdc")], vec!["bug".to_owned()])]
65 #[case::custom_state_many_assignees(
66 "needs-review",
67 vec![MemberId::new("jdc"), MemberId::new("ci-worker")],
68 vec!["bug".to_owned(), "gate".to_owned()]
69 )]
70 // @relation(model.issue, meta-ref.typed-tree, scope=function, role=Verifies)
71 fn issue_round_trips_with_custom_state_and_any_assignee_count(
72 #[case] state: &str,
73 #[case] assignees: Vec<MemberId>,
74 #[case] labels: Vec<String>,
75 ) {
76 let issue = Issue {
77 title: "title".to_owned(),
78 body: "body".to_owned(),
79 state: state.to_owned(),
80 assignees,
81 labels,
82 };
83 let (id, store) = serialize(&issue).expect("serialize");
84 let back: Issue = deserialize(&id, &store).expect("deserialize");
85 assert_eq!(back, issue);
86 }
87}