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