use crate::internal::request::urlencoding;
use crate::pagination::{ListOptions, QueryEncode};
use crate::types::enums::StateType;
use crate::types::serde_helpers::nullable_rfc3339;
use crate::{Deserialize, Serialize};
use time::OffsetDateTime;
#[derive(Debug, Clone, Default)]
pub struct ListIssueOption {
pub list_options: ListOptions,
pub state: Option<StateType>,
pub r#type: Option<crate::types::enums::IssueType>,
pub labels: Vec<String>,
pub milestones: Vec<String>,
pub key_word: String,
pub since: Option<OffsetDateTime>,
pub before: Option<OffsetDateTime>,
pub created_by: String,
pub assigned_by: String,
pub mentioned_by: String,
pub owner: String,
pub team: String,
}
impl QueryEncode for ListIssueOption {
fn query_encode(&self) -> String {
let mut out = self.list_options.query_encode();
if let Some(ref state) = self.state {
out.push_str(&format!("&state={}", state.as_ref()));
}
if !self.labels.is_empty() {
out.push_str(&format!("&labels={}", self.labels.join(",")));
}
if !self.key_word.is_empty() {
out.push_str(&format!("&q={}", urlencoding(&self.key_word)));
}
if let Some(ref t) = self.r#type {
out.push_str(&format!("&type={}", t.as_ref()));
}
if !self.milestones.is_empty() {
out.push_str(&format!("&milestones={}", self.milestones.join(",")));
}
if let Some(since) = self.since {
let formatted = since
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&since={}", urlencoding(&formatted)));
}
if let Some(before) = self.before {
let formatted = before
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&before={}", urlencoding(&formatted)));
}
if !self.created_by.is_empty() {
out.push_str(&format!("&created_by={}", urlencoding(&self.created_by)));
}
if !self.assigned_by.is_empty() {
out.push_str(&format!("&assigned_by={}", urlencoding(&self.assigned_by)));
}
if !self.mentioned_by.is_empty() {
out.push_str(&format!(
"&mentioned_by={}",
urlencoding(&self.mentioned_by)
));
}
if !self.owner.is_empty() {
out.push_str(&format!("&owner={}", urlencoding(&self.owner)));
}
if !self.team.is_empty() {
out.push_str(&format!("&team={}", urlencoding(&self.team)));
}
out
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateIssueOption {
pub title: String,
pub body: String,
#[serde(default)]
pub r#ref: String,
#[serde(default)]
pub assignees: Vec<String>,
#[serde(
rename = "due_date",
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub deadline: Option<OffsetDateTime>,
#[serde(default)]
pub milestone: i64,
#[serde(default)]
pub labels: Vec<i64>,
#[serde(default)]
pub closed: bool,
}
impl CreateIssueOption {
pub fn validate(&self) -> crate::Result<()> {
if self.title.trim().is_empty() {
return Err(crate::Error::Validation("title is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditIssueOption {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub r#ref: Option<String>,
#[serde(default)]
pub assignees: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub milestone: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state: Option<StateType>,
#[serde(
rename = "due_date",
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub deadline: Option<OffsetDateTime>,
#[serde(
rename = "unset_due_date",
default,
skip_serializing_if = "Option::is_none"
)]
pub remove_deadline: Option<bool>,
}
impl EditIssueOption {
pub fn validate(&self) -> crate::Result<()> {
if let Some(ref title) = self.title
&& title.trim().is_empty()
{
return Err(crate::Error::Validation("title is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIssueCommentOptions {
pub list_options: ListOptions,
pub since: Option<OffsetDateTime>,
pub before: Option<OffsetDateTime>,
}
impl QueryEncode for ListIssueCommentOptions {
fn query_encode(&self) -> String {
let mut out = self.list_options.query_encode();
if let Some(since) = self.since {
let formatted = since
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&since={}", urlencoding(&formatted)));
}
if let Some(before) = self.before {
let formatted = before
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&before={}", urlencoding(&formatted)));
}
out
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateIssueCommentOption {
pub body: String,
}
impl CreateIssueCommentOption {
pub fn validate(&self) -> crate::Result<()> {
if self.body.is_empty() {
return Err(crate::Error::Validation("body is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditIssueCommentOption {
pub body: String,
}
impl EditIssueCommentOption {
pub fn validate(&self) -> crate::Result<()> {
if self.body.is_empty() {
return Err(crate::Error::Validation("body is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IssueLabelsOption {
#[serde(default)]
pub labels: Vec<i64>,
}
#[derive(Debug, Clone, Default)]
pub struct ListMilestoneOption {
pub list_options: ListOptions,
pub state: Option<StateType>,
pub name: String,
}
impl QueryEncode for ListMilestoneOption {
fn query_encode(&self) -> String {
let mut out = self.list_options.query_encode();
if let Some(ref state) = self.state {
out.push_str(&format!("&state={}", state.as_ref()));
}
if !self.name.is_empty() {
out.push_str(&format!("&name={}", urlencoding(&self.name)));
}
out
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateMilestoneOption {
pub title: String,
#[serde(default)]
pub description: String,
pub state: StateType,
#[serde(
rename = "due_on",
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub deadline: Option<OffsetDateTime>,
}
impl CreateMilestoneOption {
pub fn validate(&self) -> crate::Result<()> {
if self.title.trim().is_empty() {
return Err(crate::Error::Validation("title is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct EditMilestoneOption {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub state: Option<StateType>,
#[serde(
rename = "due_on",
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub deadline: Option<OffsetDateTime>,
}
impl EditMilestoneOption {
pub fn validate(&self) -> crate::Result<()> {
if let Some(ref title) = self.title
&& title.trim().is_empty()
{
return Err(crate::Error::Validation("title is empty".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIssueReactionsOptions {
pub list_options: ListOptions,
}
impl QueryEncode for ListIssueReactionsOptions {
fn query_encode(&self) -> String {
self.list_options.query_encode()
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIssueSubscribersOptions {
pub list_options: ListOptions,
}
impl QueryEncode for ListIssueSubscribersOptions {
fn query_encode(&self) -> String {
self.list_options.query_encode()
}
}
#[derive(Debug, Clone, Default)]
pub struct ListStopwatchesOptions {
pub list_options: ListOptions,
}
impl QueryEncode for ListStopwatchesOptions {
fn query_encode(&self) -> String {
self.list_options.query_encode()
}
}
#[derive(Debug, Clone, Default)]
pub struct ListTrackedTimesOptions {
pub list_options: ListOptions,
pub since: Option<OffsetDateTime>,
pub before: Option<OffsetDateTime>,
pub user: String,
}
impl QueryEncode for ListTrackedTimesOptions {
fn query_encode(&self) -> String {
let mut out = self.list_options.query_encode();
if let Some(since) = self.since {
let formatted = since
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&since={}", urlencoding(&formatted)));
}
if let Some(before) = self.before {
let formatted = before
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default();
out.push_str(&format!("&before={}", urlencoding(&formatted)));
}
if !self.user.is_empty() {
out.push_str(&format!("&user={}", urlencoding(&self.user)));
}
out
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddTimeOption {
pub time: i64,
#[serde(
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub created: Option<OffsetDateTime>,
#[serde(default, rename = "user_name")]
pub user: String,
}
impl AddTimeOption {
pub fn validate(&self) -> crate::Result<()> {
if self.time == 0 {
return Err(crate::Error::Validation("no time to add".to_string()));
}
Ok(())
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIssueBlocksOptions {
pub list_options: ListOptions,
}
impl QueryEncode for ListIssueBlocksOptions {
fn query_encode(&self) -> String {
self.list_options.query_encode()
}
}
#[derive(Debug, Clone, Default)]
pub struct ListIssueDependenciesOptions {
pub list_options: ListOptions,
}
impl QueryEncode for ListIssueDependenciesOptions {
fn query_encode(&self) -> String {
self.list_options.query_encode()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LockIssueOption {
#[serde(default, rename = "lock_reason")]
pub lock_reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EditDeadlineOption {
#[serde(
rename = "due_date",
default,
with = "nullable_rfc3339",
skip_serializing_if = "Option::is_none"
)]
pub deadline: Option<OffsetDateTime>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_issue_option_validate_success() {
let opt = CreateIssueOption {
title: "bug report".to_string(),
body: String::new(),
r#ref: String::new(),
assignees: Vec::new(),
deadline: None,
milestone: 0,
labels: Vec::new(),
closed: false,
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_create_issue_option_validate_empty_title() {
let opt = CreateIssueOption {
title: String::new(),
body: String::new(),
r#ref: String::new(),
assignees: Vec::new(),
deadline: None,
milestone: 0,
labels: Vec::new(),
closed: false,
};
assert!(opt.validate().is_err());
}
#[test]
fn test_create_issue_option_validate_whitespace_title() {
let opt = CreateIssueOption {
title: " ".to_string(),
body: String::new(),
r#ref: String::new(),
assignees: Vec::new(),
deadline: None,
milestone: 0,
labels: Vec::new(),
closed: false,
};
assert!(opt.validate().is_err());
}
#[test]
fn test_edit_issue_option_validate_success() {
let opt = EditIssueOption {
title: Some("new title".to_string()),
body: None,
r#ref: None,
assignees: Vec::new(),
milestone: None,
state: None,
deadline: None,
remove_deadline: None,
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_edit_issue_option_validate_empty_title() {
let opt = EditIssueOption {
title: Some(" ".to_string()),
body: None,
r#ref: None,
assignees: Vec::new(),
milestone: None,
state: None,
deadline: None,
remove_deadline: None,
};
assert!(opt.validate().is_err());
}
#[test]
fn test_create_issue_comment_option_validate_success() {
let opt = CreateIssueCommentOption {
body: "comment".to_string(),
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_create_issue_comment_option_validate_empty_body() {
let opt = CreateIssueCommentOption {
body: String::new(),
};
assert!(opt.validate().is_err());
}
#[test]
fn test_edit_issue_comment_option_validate_success() {
let opt = EditIssueCommentOption {
body: "updated comment".to_string(),
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_edit_issue_comment_option_validate_empty_body() {
let opt = EditIssueCommentOption {
body: String::new(),
};
assert!(opt.validate().is_err());
}
#[test]
fn test_create_milestone_option_validate_success() {
let opt = CreateMilestoneOption {
title: "v1.0".to_string(),
description: String::new(),
state: StateType::Open,
deadline: None,
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_create_milestone_option_validate_empty_title() {
let opt = CreateMilestoneOption {
title: String::new(),
description: String::new(),
state: StateType::Open,
deadline: None,
};
assert!(opt.validate().is_err());
}
#[test]
fn test_edit_milestone_option_validate_success() {
let opt = EditMilestoneOption {
title: Some("v2.0".to_string()),
..Default::default()
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_edit_milestone_option_validate_empty_title() {
let opt = EditMilestoneOption {
title: Some(" ".to_string()),
..Default::default()
};
assert!(opt.validate().is_err());
}
#[test]
fn test_add_time_option_validate_success() {
let opt = AddTimeOption {
time: 3600,
created: None,
user: String::new(),
};
assert!(opt.validate().is_ok());
}
#[test]
fn test_add_time_option_validate_zero_time() {
let opt = AddTimeOption {
time: 0,
created: None,
user: String::new(),
};
assert!(opt.validate().is_err());
}
}