use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::client::{parse_link_header, InstallationClient, PagedResponse};
use crate::error::ApiError;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MilestoneState {
Open,
Closed,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Issue {
pub id: u64,
pub node_id: String,
pub number: u64,
pub title: String,
pub body: Option<String>,
pub state: String,
#[serde(default)]
pub locked: bool,
pub user: IssueUser,
pub assignees: Vec<IssueUser>,
pub labels: Vec<Label>,
pub milestone: Option<Milestone>,
pub comments: u64,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub closed_at: Option<DateTime<Utc>>,
pub html_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IssueUser {
pub login: String,
pub id: u64,
pub node_id: String,
#[serde(rename = "type")]
pub user_type: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Milestone {
pub id: u64,
pub node_id: String,
pub number: u64,
pub title: String,
pub description: Option<String>,
pub state: MilestoneState,
pub open_issues: u32,
pub closed_issues: u32,
pub due_on: Option<DateTime<Utc>>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub closed_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Label {
pub id: u64,
pub node_id: String,
pub name: String,
pub description: Option<String>,
pub color: String,
pub default: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Comment {
pub id: u64,
pub node_id: String,
pub body: String,
pub user: IssueUser,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub html_url: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateIssueRequest {
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignees: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub milestone: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct UpdateIssueRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub assignees: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub milestone: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub labels: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateLabelRequest {
pub name: String,
pub color: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct UpdateLabelRequest {
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub new_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateCommentRequest {
pub body: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct UpdateCommentRequest {
pub body: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LockReason {
OffTopic,
TooHeated,
Resolved,
Spam,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IssueActivityEvent {
pub id: u64,
pub event: String, pub actor: IssueUser,
pub created_at: DateTime<Utc>,
pub label: Option<Label>,
pub assignee: Option<IssueUser>,
pub milestone: Option<MilestoneSummary>,
pub rename: Option<IssueRename>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MilestoneSummary {
pub title: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IssueRename {
pub from: String,
pub to: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event", rename_all = "snake_case")]
pub enum TimelineEvent {
#[serde(rename = "commented")]
Commented {
id: u64,
user: IssueUser,
body: Option<String>,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
html_url: String,
},
Labeled {
id: u64,
actor: IssueUser,
label: Label,
created_at: DateTime<Utc>,
},
Unlabeled {
id: u64,
actor: IssueUser,
label: Label,
created_at: DateTime<Utc>,
},
Assigned {
id: u64,
actor: IssueUser,
assignee: IssueUser,
created_at: DateTime<Utc>,
},
Unassigned {
id: u64,
actor: IssueUser,
assignee: IssueUser,
created_at: DateTime<Utc>,
},
Milestoned {
id: u64,
actor: IssueUser,
milestone: MilestoneSummary,
created_at: DateTime<Utc>,
},
Demilestoned {
id: u64,
actor: IssueUser,
milestone: MilestoneSummary,
created_at: DateTime<Utc>,
},
Closed {
id: u64,
actor: IssueUser,
created_at: DateTime<Utc>,
},
Reopened {
id: u64,
actor: IssueUser,
created_at: DateTime<Utc>,
},
Locked {
id: u64,
actor: IssueUser,
lock_reason: Option<String>,
created_at: DateTime<Utc>,
},
Unlocked {
id: u64,
actor: IssueUser,
created_at: DateTime<Utc>,
},
Renamed {
id: u64,
actor: IssueUser,
rename: IssueRename,
created_at: DateTime<Utc>,
},
Referenced {
id: u64,
actor: IssueUser,
created_at: DateTime<Utc>,
},
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReactionContent {
#[serde(rename = "+1")]
PlusOne,
#[serde(rename = "-1")]
MinusOne,
Laugh,
Confused,
Heart,
Hooray,
Rocket,
Eyes,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reaction {
pub id: u64,
pub node_id: String,
pub user: IssueUser,
pub content: ReactionContent,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone)]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug, Clone)]
pub enum MilestoneSortField {
DueOn,
Completeness,
}
#[derive(Debug, Clone, Default)]
pub struct ListMilestonesQuery {
pub state: Option<MilestoneState>,
pub sort: Option<MilestoneSortField>,
pub direction: Option<SortDirection>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateMilestoneRequest {
pub title: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<MilestoneState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub due_on: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Default, Serialize)]
pub struct UpdateMilestoneRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<MilestoneState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub due_on: Option<DateTime<Utc>>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct LabelsRequest {
pub(crate) labels: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
struct LockIssueRequest {
#[serde(skip_serializing_if = "Option::is_none")]
lock_reason: Option<LockReason>,
}
#[derive(Debug, Clone, Serialize)]
struct AssigneesRequest {
assignees: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
struct CreateReactionRequest {
content: ReactionContent,
}
#[derive(Debug, Clone)]
pub struct IssuesClient {
client: InstallationClient,
}
impl IssuesClient {
pub(crate) fn new(client: InstallationClient) -> Self {
Self { client }
}
pub async fn list(
&self,
owner: &str,
repo: &str,
state: Option<&str>,
page: Option<u32>,
) -> Result<PagedResponse<Issue>, ApiError> {
let mut path = format!("/repos/{}/{}/issues", owner, repo);
let mut params: Vec<String> = Vec::new();
if let Some(s) = state {
params.push(format!("state={}", s));
}
if let Some(p) = page {
params.push(format!("page={}", p));
}
if !params.is_empty() {
path = format!("{}?{}", path, params.join("&"));
}
let response = self.client.get(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
let pagination = response
.headers()
.get("Link")
.and_then(|h| h.to_str().ok())
.map(|h| parse_link_header(Some(h)))
.unwrap_or_default();
let items: Vec<Issue> = response.json().await.map_err(ApiError::from)?;
Ok(PagedResponse {
items,
total_count: None,
pagination,
})
}
pub async fn get(&self, owner: &str, repo: &str, issue_number: u64) -> Result<Issue, ApiError> {
let path = format!("/repos/{}/{}/issues/{}", owner, repo, issue_number);
let response = self.client.get(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn create(
&self,
owner: &str,
repo: &str,
request: CreateIssueRequest,
) -> Result<Issue, ApiError> {
let path = format!("/repos/{}/{}/issues", owner, repo);
let response = self.client.post(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn update(
&self,
owner: &str,
repo: &str,
issue_number: u64,
request: UpdateIssueRequest,
) -> Result<Issue, ApiError> {
let path = format!("/repos/{}/{}/issues/{}", owner, repo, issue_number);
let response = self.client.patch(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn set_milestone(
&self,
owner: &str,
repo: &str,
issue_number: u64,
milestone_number: Option<u64>,
) -> Result<Issue, ApiError> {
self.update(
owner,
repo,
issue_number,
UpdateIssueRequest {
milestone: milestone_number,
..Default::default()
},
)
.await
}
pub async fn list_comments(
&self,
owner: &str,
repo: &str,
issue_number: u64,
) -> Result<Vec<Comment>, ApiError> {
let base = format!("/repos/{}/{}/issues/{}/comments", owner, repo, issue_number);
self.fetch_all(&base).await
}
pub async fn get_comment(
&self,
owner: &str,
repo: &str,
comment_id: u64,
) -> Result<Comment, ApiError> {
let path = format!("/repos/{}/{}/issues/comments/{}", owner, repo, comment_id);
let response = self.client.get(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn create_comment(
&self,
owner: &str,
repo: &str,
issue_number: u64,
request: CreateCommentRequest,
) -> Result<Comment, ApiError> {
let path = format!("/repos/{}/{}/issues/{}/comments", owner, repo, issue_number);
let response = self.client.post(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn update_comment(
&self,
owner: &str,
repo: &str,
comment_id: u64,
request: UpdateCommentRequest,
) -> Result<Comment, ApiError> {
let path = format!("/repos/{}/{}/issues/comments/{}", owner, repo, comment_id);
let response = self.client.patch(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn delete_comment(
&self,
owner: &str,
repo: &str,
comment_id: u64,
) -> Result<(), ApiError> {
let path = format!("/repos/{}/{}/issues/comments/{}", owner, repo, comment_id);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
pub async fn list_labels(
&self,
owner: &str,
repo: &str,
issue_number: u64,
) -> Result<Vec<Label>, ApiError> {
let base = format!("/repos/{}/{}/issues/{}/labels", owner, repo, issue_number);
self.fetch_all(&base).await
}
pub async fn add_labels(
&self,
owner: &str,
repo: &str,
issue_number: u64,
labels: Vec<String>,
) -> Result<Vec<Label>, ApiError> {
let path = format!("/repos/{}/{}/issues/{}/labels", owner, repo, issue_number);
let body = LabelsRequest { labels };
let response = self.client.post(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn remove_label(
&self,
owner: &str,
repo: &str,
issue_number: u64,
label_name: &str,
) -> Result<Vec<Label>, ApiError> {
let path = format!(
"/repos/{}/{}/issues/{}/labels/{}",
owner,
repo,
issue_number,
urlencoding::encode(label_name)
);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn replace_labels(
&self,
owner: &str,
repo: &str,
issue_number: u64,
labels: Vec<String>,
) -> Result<Vec<Label>, ApiError> {
let path = format!("/repos/{}/{}/issues/{}/labels", owner, repo, issue_number);
let body = LabelsRequest { labels };
let response = self.client.put(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn list_reactions(
&self,
owner: &str,
repo: &str,
issue_number: u64,
) -> Result<Vec<Reaction>, ApiError> {
let base = format!(
"/repos/{}/{}/issues/{}/reactions",
owner, repo, issue_number
);
self.fetch_all(&base).await
}
pub async fn create_reaction(
&self,
owner: &str,
repo: &str,
issue_number: u64,
content: ReactionContent,
) -> Result<Reaction, ApiError> {
let path = format!(
"/repos/{}/{}/issues/{}/reactions",
owner, repo, issue_number
);
let body = CreateReactionRequest { content };
let response = self.client.post(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn delete_reaction(
&self,
owner: &str,
repo: &str,
issue_number: u64,
reaction_id: u64,
) -> Result<(), ApiError> {
let path = format!(
"/repos/{}/{}/issues/{}/reactions/{}",
owner, repo, issue_number, reaction_id
);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
pub async fn list_comment_reactions(
&self,
owner: &str,
repo: &str,
comment_id: u64,
) -> Result<Vec<Reaction>, ApiError> {
let base = format!(
"/repos/{}/{}/issues/comments/{}/reactions",
owner, repo, comment_id
);
self.fetch_all(&base).await
}
pub async fn create_comment_reaction(
&self,
owner: &str,
repo: &str,
comment_id: u64,
content: ReactionContent,
) -> Result<Reaction, ApiError> {
let path = format!(
"/repos/{}/{}/issues/comments/{}/reactions",
owner, repo, comment_id
);
let body = CreateReactionRequest { content };
let response = self.client.post(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn delete_comment_reaction(
&self,
owner: &str,
repo: &str,
comment_id: u64,
reaction_id: u64,
) -> Result<(), ApiError> {
let path = format!(
"/repos/{}/{}/issues/comments/{}/reactions/{}",
owner, repo, comment_id, reaction_id
);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
pub async fn list_available_assignees(
&self,
owner: &str,
repo: &str,
) -> Result<Vec<IssueUser>, ApiError> {
let base = format!("/repos/{}/{}/assignees", owner, repo);
self.fetch_all(&base).await
}
pub async fn add_assignees(
&self,
owner: &str,
repo: &str,
issue_number: u64,
assignees: Vec<String>,
) -> Result<Issue, ApiError> {
let path = format!(
"/repos/{}/{}/issues/{}/assignees",
owner, repo, issue_number
);
let body = AssigneesRequest { assignees };
let response = self.client.post(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn remove_assignees(
&self,
owner: &str,
repo: &str,
issue_number: u64,
assignees: Vec<String>,
) -> Result<Issue, ApiError> {
let path = format!(
"/repos/{}/{}/issues/{}/assignees",
owner, repo, issue_number
);
let body = AssigneesRequest { assignees };
let response = self.client.delete_with_body(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn lock(
&self,
owner: &str,
repo: &str,
issue_number: u64,
reason: Option<LockReason>,
) -> Result<(), ApiError> {
let path = format!("/repos/{}/{}/issues/{}/lock", owner, repo, issue_number);
let body = LockIssueRequest {
lock_reason: reason,
};
let response = self.client.put(&path, &body).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
pub async fn unlock(&self, owner: &str, repo: &str, issue_number: u64) -> Result<(), ApiError> {
let path = format!("/repos/{}/{}/issues/{}/lock", owner, repo, issue_number);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
pub async fn list_activity_events(
&self,
owner: &str,
repo: &str,
issue_number: u64,
) -> Result<Vec<IssueActivityEvent>, ApiError> {
let base = format!("/repos/{}/{}/issues/{}/events", owner, repo, issue_number);
self.fetch_all(&base).await
}
pub async fn list_timeline(
&self,
owner: &str,
repo: &str,
issue_number: u64,
) -> Result<Vec<TimelineEvent>, ApiError> {
let base = format!("/repos/{}/{}/issues/{}/timeline", owner, repo, issue_number);
self.fetch_all(&base).await
}
async fn fetch_all<T: serde::de::DeserializeOwned>(
&self,
base_path: &str,
) -> Result<Vec<T>, ApiError> {
let first_page = format!("{}?per_page=100", base_path);
self.client.fetch_all_pages(&first_page).await
}
}
#[derive(Debug, Clone)]
pub struct LabelsClient {
client: InstallationClient,
}
impl LabelsClient {
pub(crate) fn new(client: InstallationClient) -> Self {
Self { client }
}
pub async fn list(&self, owner: &str, repo: &str) -> Result<Vec<Label>, ApiError> {
let first_page = format!("/repos/{}/{}/labels?per_page=100", owner, repo);
self.client.fetch_all_pages(&first_page).await
}
pub async fn get(&self, owner: &str, repo: &str, name: &str) -> Result<Label, ApiError> {
let path = format!(
"/repos/{}/{}/labels/{}",
owner,
repo,
urlencoding::encode(name)
);
let response = self.client.get(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn create(
&self,
owner: &str,
repo: &str,
request: CreateLabelRequest,
) -> Result<Label, ApiError> {
let path = format!("/repos/{}/{}/labels", owner, repo);
let response = self.client.post(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn update(
&self,
owner: &str,
repo: &str,
name: &str,
request: UpdateLabelRequest,
) -> Result<Label, ApiError> {
let path = format!(
"/repos/{}/{}/labels/{}",
owner,
repo,
urlencoding::encode(name)
);
let response = self.client.patch(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn delete(&self, owner: &str, repo: &str, name: &str) -> Result<(), ApiError> {
let path = format!(
"/repos/{}/{}/labels/{}",
owner,
repo,
urlencoding::encode(name)
);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct MilestonesClient {
client: InstallationClient,
}
impl MilestonesClient {
pub(crate) fn new(client: InstallationClient) -> Self {
Self { client }
}
pub async fn list(
&self,
owner: &str,
repo: &str,
query: Option<ListMilestonesQuery>,
) -> Result<Vec<Milestone>, ApiError> {
let base = format!("/repos/{}/{}/milestones", owner, repo);
let mut params: Vec<String> = vec!["per_page=100".to_string()];
if let Some(q) = query {
if let Some(state) = q.state {
let s = match state {
MilestoneState::Open => "open",
MilestoneState::Closed => "closed",
};
params.push(format!("state={}", s));
}
if let Some(sort) = q.sort {
let s = match sort {
MilestoneSortField::DueOn => "due_on",
MilestoneSortField::Completeness => "completeness",
};
params.push(format!("sort={}", s));
}
if let Some(dir) = q.direction {
let d = match dir {
SortDirection::Asc => "asc",
SortDirection::Desc => "desc",
};
params.push(format!("direction={}", d));
}
}
let first_page = format!("{}?{}", base, params.join("&"));
self.client.fetch_all_pages(&first_page).await
}
pub async fn get(
&self,
owner: &str,
repo: &str,
milestone_number: u64,
) -> Result<Milestone, ApiError> {
let path = format!("/repos/{}/{}/milestones/{}", owner, repo, milestone_number);
let response = self.client.get(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn create(
&self,
owner: &str,
repo: &str,
request: CreateMilestoneRequest,
) -> Result<Milestone, ApiError> {
let path = format!("/repos/{}/{}/milestones", owner, repo);
let response = self.client.post(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn update(
&self,
owner: &str,
repo: &str,
milestone_number: u64,
request: UpdateMilestoneRequest,
) -> Result<Milestone, ApiError> {
let path = format!("/repos/{}/{}/milestones/{}", owner, repo, milestone_number);
let response = self.client.patch(&path, &request).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
response.json().await.map_err(ApiError::from)
}
pub async fn delete(
&self,
owner: &str,
repo: &str,
milestone_number: u64,
) -> Result<(), ApiError> {
let path = format!("/repos/{}/{}/milestones/{}", owner, repo, milestone_number);
let response = self.client.delete(&path).await?;
let status = response.status();
if !status.is_success() {
return Err(super::map_http_error(status, response).await);
}
Ok(())
}
}
#[cfg(test)]
#[path = "issue_tests.rs"]
mod tests;