1use std::collections::BTreeSet;
12
13use crate::api::endpoint_prelude::*;
14use crate::api::ParamValue;
15
16pub use groups::{GroupIssues, GroupIssuesBuilder, GroupIssuesBuilderError};
17pub use projects::{ProjectIssues, ProjectIssuesBuilder, ProjectIssuesBuilderError};
18
19mod groups;
20mod projects;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[non_exhaustive]
25pub enum IssueState {
26 Opened,
28 Closed,
30}
31
32impl IssueState {
33 fn as_str(self) -> &'static str {
34 match self {
35 IssueState::Opened => "opened",
36 IssueState::Closed => "closed",
37 }
38 }
39}
40
41impl ParamValue<'static> for IssueState {
42 fn as_value(&self) -> Cow<'static, str> {
43 self.as_str().into()
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49#[non_exhaustive]
50pub enum IssueScope {
51 CreatedByMe,
53 AssignedToMe,
55 All,
57}
58
59impl IssueScope {
60 fn as_str(self) -> &'static str {
61 match self {
62 IssueScope::CreatedByMe => "created_by_me",
63 IssueScope::AssignedToMe => "assigned_to_me",
64 IssueScope::All => "all",
65 }
66 }
67}
68
69impl ParamValue<'static> for IssueScope {
70 fn as_value(&self) -> Cow<'static, str> {
71 self.as_str().into()
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77#[non_exhaustive]
78pub enum IssueType {
79 Issue,
81 Incident,
83 TestCase,
85 Task,
87}
88
89impl IssueType {
90 fn as_str(self) -> &'static str {
91 match self {
92 IssueType::Issue => "issue",
93 IssueType::Incident => "incident",
94 IssueType::TestCase => "test_case",
95 IssueType::Task => "task",
96 }
97 }
98}
99
100impl ParamValue<'static> for IssueType {
101 fn as_value(&self) -> Cow<'static, str> {
102 self.as_str().into()
103 }
104}
105
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum IssueEpic {
110 None,
112 Any,
114 Id(u64),
116}
117
118impl IssueEpic {
119 fn as_str(self) -> Cow<'static, str> {
120 match self {
121 IssueEpic::None => "None".into(),
122 IssueEpic::Any => "Any".into(),
123 IssueEpic::Id(id) => format!("{}", id).into(),
124 }
125 }
126}
127
128impl From<u64> for IssueEpic {
129 fn from(id: u64) -> Self {
130 Self::Id(id)
131 }
132}
133
134impl ParamValue<'static> for IssueEpic {
135 fn as_value(&self) -> Cow<'static, str> {
136 self.as_str()
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142#[non_exhaustive]
143pub enum IssueHealthStatus {
144 Any,
146 None,
148 OnTrack,
150 NeedsAttention,
152 AtRisk,
154}
155
156impl IssueHealthStatus {
157 fn as_str(self) -> &'static str {
158 match self {
159 IssueHealthStatus::Any => "Any",
160 IssueHealthStatus::None => "None",
161 IssueHealthStatus::OnTrack => "on_track",
162 IssueHealthStatus::NeedsAttention => "needs_attention",
163 IssueHealthStatus::AtRisk => "at_risk",
164 }
165 }
166}
167
168impl ParamValue<'static> for IssueHealthStatus {
169 fn as_value(&self) -> Cow<'static, str> {
170 self.as_str().into()
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq)]
176#[non_exhaustive]
177pub enum IssueIteration<'a> {
178 None,
180 Any,
182 Id(u64),
184 Title(Cow<'a, str>),
186}
187
188impl IssueIteration<'_> {
189 fn add_params<'b>(&'b self, params: &mut QueryParams<'b>) {
190 match self {
191 IssueIteration::None => {
192 params.push("iteration_id", "None");
193 },
194 IssueIteration::Any => {
195 params.push("iteration_id", "Any");
196 },
197 IssueIteration::Id(id) => {
198 params.push("iteration_id", *id);
199 },
200 IssueIteration::Title(title) => {
201 params.push("iteration_title", title);
202 },
203 }
204 }
205}
206
207#[derive(Debug, Clone)]
208#[non_exhaustive]
209enum Assignee<'a> {
210 Assigned,
211 Unassigned,
212 Id(u64),
213 Usernames(BTreeSet<Cow<'a, str>>),
214}
215
216impl Assignee<'_> {
217 fn add_params<'b>(&'b self, params: &mut QueryParams<'b>) {
218 match self {
219 Assignee::Assigned => {
220 params.push("assignee_id", "Any");
221 },
222 Assignee::Unassigned => {
223 params.push("assignee_id", "None");
224 },
225 Assignee::Id(id) => {
226 params.push("assignee_id", *id);
227 },
228 Assignee::Usernames(usernames) => {
229 params.extend(usernames.iter().map(|value| ("assignee_username[]", value)));
230 },
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
237#[non_exhaustive]
238pub enum IssueWeight {
239 Any,
241 None,
243 Weight(u64),
245}
246
247impl IssueWeight {
248 fn as_str(self) -> Cow<'static, str> {
249 match self {
250 IssueWeight::Any => "Any".into(),
251 IssueWeight::None => "None".into(),
252 IssueWeight::Weight(weight) => weight.to_string().into(),
253 }
254 }
255}
256
257impl ParamValue<'static> for IssueWeight {
258 fn as_value(&self) -> Cow<'static, str> {
259 self.as_str()
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq)]
265#[non_exhaustive]
266pub enum IssueSearchScope {
267 Title,
269 Description,
271}
272
273impl IssueSearchScope {
274 fn as_str(self) -> &'static str {
275 match self {
276 IssueSearchScope::Title => "title",
277 IssueSearchScope::Description => "description",
278 }
279 }
280}
281
282impl ParamValue<'static> for IssueSearchScope {
283 fn as_value(&self) -> Cow<'static, str> {
284 self.as_str().into()
285 }
286}
287
288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
290#[non_exhaustive]
291pub enum IssueDueDateFilter {
292 None,
294 Any,
296 Today,
298 Tomorrow,
300 ThisWeek,
302 ThisMonth,
304 BetweenTwoWeeksAgoAndNextMonth,
306 Overdue,
308}
309
310impl IssueDueDateFilter {
311 fn as_str(self) -> &'static str {
312 match self {
313 IssueDueDateFilter::None => "0",
314 IssueDueDateFilter::Any => "any",
315 IssueDueDateFilter::Today => "today",
316 IssueDueDateFilter::Tomorrow => "tomorrow",
317 IssueDueDateFilter::ThisWeek => "week",
318 IssueDueDateFilter::ThisMonth => "month",
319 IssueDueDateFilter::BetweenTwoWeeksAgoAndNextMonth => {
320 "next_month_and_previous_two_weeks"
321 },
322 IssueDueDateFilter::Overdue => "overdue",
323 }
324 }
325}
326
327impl ParamValue<'static> for IssueDueDateFilter {
328 fn as_value(&self) -> Cow<'static, str> {
329 self.as_str().into()
330 }
331}
332
333#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
335#[non_exhaustive]
336pub enum IssueOrderBy {
337 #[default]
339 CreatedAt,
340 UpdatedAt,
342 Priority,
344 DueDate,
346 RelativePosition,
350 LabelPriority,
352 MilestoneDue,
354 Popularity,
356 Weight,
358 Title,
360}
361
362impl IssueOrderBy {
363 fn as_str(self) -> &'static str {
364 match self {
365 IssueOrderBy::CreatedAt => "created_at",
366 IssueOrderBy::UpdatedAt => "updated_at",
367 IssueOrderBy::Priority => "priority",
368 IssueOrderBy::DueDate => "due_date",
369 IssueOrderBy::RelativePosition => "relative_position",
370 IssueOrderBy::LabelPriority => "label_priority",
371 IssueOrderBy::MilestoneDue => "milestone_due",
372 IssueOrderBy::Popularity => "popularity",
373 IssueOrderBy::Title => "title",
374 IssueOrderBy::Weight => "weight",
375 }
376 }
377}
378
379impl ParamValue<'static> for IssueOrderBy {
380 fn as_value(&self) -> Cow<'static, str> {
381 self.as_str().into()
382 }
383}
384
385#[derive(Debug, Clone)]
387#[non_exhaustive]
388pub enum IssueMilestone<'a> {
389 None,
391 Any,
393 Upcoming,
395 Started,
397 Named(Cow<'a, str>),
399}
400
401impl<'a> IssueMilestone<'a> {
402 pub fn named<N>(name: N) -> Self
404 where
405 N: Into<Cow<'a, str>>,
406 {
407 Self::Named(name.into())
408 }
409
410 fn as_str(&self) -> &str {
411 match self {
412 IssueMilestone::None => "None",
413 IssueMilestone::Any => "Any",
414 IssueMilestone::Upcoming => "Upcoming",
415 IssueMilestone::Started => "Started",
416 IssueMilestone::Named(name) => name.as_ref(),
417 }
418 }
419}
420
421impl<'a, 'b: 'a> ParamValue<'a> for &'b IssueMilestone<'a> {
422 fn as_value(&self) -> Cow<'a, str> {
423 self.as_str().into()
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use crate::api::issues::{
430 IssueDueDateFilter, IssueEpic, IssueHealthStatus, IssueMilestone, IssueOrderBy, IssueScope,
431 IssueSearchScope, IssueState, IssueType, IssueWeight,
432 };
433
434 #[test]
435 fn issue_state_as_str() {
436 let items = &[
437 (IssueState::Opened, "opened"),
438 (IssueState::Closed, "closed"),
439 ];
440
441 for (i, s) in items {
442 assert_eq!(i.as_str(), *s);
443 }
444 }
445
446 #[test]
447 fn issue_scope_as_str() {
448 let items = &[
449 (IssueScope::CreatedByMe, "created_by_me"),
450 (IssueScope::AssignedToMe, "assigned_to_me"),
451 (IssueScope::All, "all"),
452 ];
453
454 for (i, s) in items {
455 assert_eq!(i.as_str(), *s);
456 }
457 }
458
459 #[test]
460 fn issue_epic_from_u64() {
461 let items = &[(IssueEpic::Id(4), 4.into())];
462
463 for (i, s) in items {
464 assert_eq!(i, s);
465 }
466 }
467
468 #[test]
469 fn issue_epic_as_str() {
470 let items = &[
471 (IssueEpic::None, "None"),
472 (IssueEpic::Any, "Any"),
473 (IssueEpic::Id(4), "4"),
474 ];
475
476 for (i, s) in items {
477 assert_eq!(i.as_str(), *s);
478 }
479 }
480
481 #[test]
482 fn issue_health_status_as_str() {
483 let items = &[
484 (IssueHealthStatus::OnTrack, "on_track"),
485 (IssueHealthStatus::NeedsAttention, "needs_attention"),
486 (IssueHealthStatus::AtRisk, "at_risk"),
487 ];
488
489 for (i, s) in items {
490 assert_eq!(i.as_str(), *s);
491 }
492 }
493
494 #[test]
495 fn issue_type_as_str() {
496 let items = &[
497 (IssueType::Issue, "issue"),
498 (IssueType::Incident, "incident"),
499 (IssueType::TestCase, "test_case"),
500 (IssueType::Task, "task"),
501 ];
502
503 for (i, s) in items {
504 assert_eq!(i.as_str(), *s);
505 }
506 }
507
508 #[test]
509 fn issue_weight_as_str() {
510 let items = &[
511 (IssueWeight::Any, "Any"),
512 (IssueWeight::None, "None"),
513 (IssueWeight::Weight(0), "0"),
514 ];
515
516 for (i, s) in items {
517 assert_eq!(i.as_str(), *s);
518 }
519 }
520
521 #[test]
522 fn issue_search_scope_as_str() {
523 let items = &[
524 (IssueSearchScope::Title, "title"),
525 (IssueSearchScope::Description, "description"),
526 ];
527
528 for (i, s) in items {
529 assert_eq!(i.as_str(), *s);
530 }
531 }
532
533 #[test]
534 fn issue_due_date_filter_as_str() {
535 let items = &[
536 (IssueDueDateFilter::None, "0"),
537 (IssueDueDateFilter::Any, "any"),
538 (IssueDueDateFilter::Today, "today"),
539 (IssueDueDateFilter::Tomorrow, "tomorrow"),
540 (IssueDueDateFilter::ThisWeek, "week"),
541 (IssueDueDateFilter::ThisMonth, "month"),
542 (
543 IssueDueDateFilter::BetweenTwoWeeksAgoAndNextMonth,
544 "next_month_and_previous_two_weeks",
545 ),
546 (IssueDueDateFilter::Overdue, "overdue"),
547 ];
548
549 for (i, s) in items {
550 assert_eq!(i.as_str(), *s);
551 }
552 }
553
554 #[test]
555 fn issue_order_by_default() {
556 assert_eq!(IssueOrderBy::default(), IssueOrderBy::CreatedAt);
557 }
558
559 #[test]
560 fn issue_order_by_as_str() {
561 let items = &[
562 (IssueOrderBy::CreatedAt, "created_at"),
563 (IssueOrderBy::UpdatedAt, "updated_at"),
564 (IssueOrderBy::Priority, "priority"),
565 (IssueOrderBy::DueDate, "due_date"),
566 (IssueOrderBy::RelativePosition, "relative_position"),
567 (IssueOrderBy::LabelPriority, "label_priority"),
568 (IssueOrderBy::MilestoneDue, "milestone_due"),
569 (IssueOrderBy::Popularity, "popularity"),
570 (IssueOrderBy::Weight, "weight"),
571 (IssueOrderBy::Title, "title"),
572 ];
573
574 for (i, s) in items {
575 assert_eq!(i.as_str(), *s);
576 }
577 }
578
579 #[test]
580 fn issue_milestone_as_str() {
581 let items = &[
582 (IssueMilestone::Any, "Any"),
583 (IssueMilestone::None, "None"),
584 (IssueMilestone::Upcoming, "Upcoming"),
585 (IssueMilestone::Started, "Started"),
586 (IssueMilestone::Named("milestone".into()), "milestone"),
587 ];
588
589 for (i, s) in items {
590 assert_eq!(i.as_str(), *s);
591 }
592 }
593}