Merge pull request 'Support basic operations on issues' (#3) from issues into main
Reviewed-on: https://codeberg.org/Cyborus/forgejo-api/pulls/3
This commit is contained in:
		
						commit
						9305545983
					
				
					 5 changed files with 313 additions and 1 deletions
				
			
		
							
								
								
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -794,6 +794,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "deranged",
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "powerfmt",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "time-core",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,4 +12,4 @@ thiserror = "1.0.43"
 | 
			
		|||
tokio = { version = "1.29.1", features = ["net"] }
 | 
			
		||||
url = { version = "2.4.0", features = ["serde"] }
 | 
			
		||||
serde = { version = "1.0.168", features = ["derive"] }
 | 
			
		||||
time = { version = "0.3.22", features = ["parsing", "serde"] }
 | 
			
		||||
time = { version = "0.3.22", features = ["parsing", "serde", "formatting"] }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										257
									
								
								src/issue.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										257
									
								
								src/issue.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,257 @@
 | 
			
		|||
use super::*;
 | 
			
		||||
 | 
			
		||||
impl Forgejo {
 | 
			
		||||
    pub async fn get_repo_issues(&self, owner: &str, repo: &str, query: IssueQuery) -> Result<Vec<Issue>, ForgejoError> {
 | 
			
		||||
        self.get(&query.to_string(owner, repo)).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn create_issue(&self, owner: &str, repo: &str, opts: CreateIssueOption) -> Result<Issue, ForgejoError> {
 | 
			
		||||
        self.post(&format!("repos/{owner}/{repo}/issues"), &opts).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_issue(&self, owner: &str, repo: &str, id: u64) -> Result<Option<Issue>, ForgejoError> {
 | 
			
		||||
        self.get_opt(&format!("repos/{owner}/{repo}/issues/{id}")).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn delete_issue(&self, owner: &str, repo: &str, id: u64) -> Result<(), ForgejoError> {
 | 
			
		||||
        self.delete(&format!("repos/{owner}/{repo}/issues/{id}")).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn edit_issue(&self, owner: &str, repo: &str, id: u64, opts: EditIssueOption) -> Result<Issue, ForgejoError> {
 | 
			
		||||
        self.patch(&format!("repos/{owner}/{repo}/issues/{id}"), &opts).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    pub async fn get_repo_comments(&self, owner: &str, repo: &str, query: RepoCommentQuery) -> Result<Vec<Comment>, ForgejoError> {
 | 
			
		||||
        self.get(&query.to_string(owner, repo)).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_issue_comments(&self, owner: &str, repo: &str, issue_id: u64, query: IssueCommentQuery) -> Result<Vec<Comment>, ForgejoError> {
 | 
			
		||||
        self.get(&query.to_string(owner, repo, issue_id)).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn create_comment(&self, owner: &str, repo: &str, issue_id: u64, opts: CreateIssueCommentOption) -> Result<Comment, ForgejoError> {
 | 
			
		||||
        self.post(&format!("repos/{owner}/{repo}/issues/{issue_id}/comments"), &opts).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn get_comment(&self, owner: &str, repo: &str, id: u64) -> Result<Option<Comment>, ForgejoError> {
 | 
			
		||||
        self.get_opt(&format!("repos/{owner}/{repo}/issues/comments/{id}")).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn delete_comment(&self, owner: &str, repo: &str, id: u64) -> Result<(), ForgejoError> {
 | 
			
		||||
        self.delete(&format!("repos/{owner}/{repo}/issues/comments/{id}")).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn edit_comment(&self, owner: &str, repo: &str, id: u64, opts: EditIssueCommentOption) -> Result<Comment, ForgejoError> {
 | 
			
		||||
        self.patch(&format!("repos/{owner}/{repo}/issues/comments/{id}"), &opts).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct Issue {
 | 
			
		||||
    pub assets: Vec<Attachment>,
 | 
			
		||||
    pub assignee: User,
 | 
			
		||||
    pub assignees: Vec<User>,
 | 
			
		||||
    pub body: String,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub closed_at: time::OffsetDateTime,
 | 
			
		||||
    pub comments: u64,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub created_at: time::OffsetDateTime,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub due_date: time::OffsetDateTime,
 | 
			
		||||
    pub html_url: Url,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub is_locked: bool,
 | 
			
		||||
    pub labels: Vec<Label>,
 | 
			
		||||
    pub milestone: Milestone,
 | 
			
		||||
    pub number: u64,
 | 
			
		||||
    pub original_author: String,
 | 
			
		||||
    pub original_author_id: u64,
 | 
			
		||||
    pub pin_order: u64,
 | 
			
		||||
    pub pull_request: PullRequestMeta,
 | 
			
		||||
    #[serde(rename = "ref")]
 | 
			
		||||
    pub _ref: String,
 | 
			
		||||
    pub repository: RepositoryMeta,
 | 
			
		||||
    pub state: State,
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub updated_at: time::OffsetDateTime,
 | 
			
		||||
    pub url: Url,
 | 
			
		||||
    pub user: User,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct Label {
 | 
			
		||||
    pub color: String,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    pub exclusive: bool,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub url: Url,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct Attachment {
 | 
			
		||||
    pub browser_download_url: Url,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub created_at: time::OffsetDateTime,
 | 
			
		||||
    pub download_count: u64,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub size: u64,
 | 
			
		||||
    pub uuid: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone, Copy)]
 | 
			
		||||
pub enum State {
 | 
			
		||||
    Open,
 | 
			
		||||
    Closed,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl State {
 | 
			
		||||
    fn as_str(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            State::Open => "open",
 | 
			
		||||
            State::Closed => "closed",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct Comment {
 | 
			
		||||
    pub assets: Vec<Attachment>,
 | 
			
		||||
    pub body: String,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub created_at: time::OffsetDateTime,
 | 
			
		||||
    pub html_url: Url,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub issue_url: Url,
 | 
			
		||||
    pub original_author: String,
 | 
			
		||||
    pub original_author_id: u64,
 | 
			
		||||
    pub pull_request_url: Url,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub updated_at: time::OffsetDateTime,
 | 
			
		||||
    pub user: User,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug)]
 | 
			
		||||
pub struct IssueQuery {
 | 
			
		||||
    pub state: Option<State>,
 | 
			
		||||
    pub labels: Vec<String>,
 | 
			
		||||
    pub query: Option<String>,
 | 
			
		||||
    pub _type: Option<IssueQueryType>,
 | 
			
		||||
    pub milestones: Vec<String>,
 | 
			
		||||
    pub since: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub before: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub created_by: Option<String>,
 | 
			
		||||
    pub assigned_by: Option<String>,
 | 
			
		||||
    pub mentioned_by: Option<String>,
 | 
			
		||||
    pub page: Option<u32>,
 | 
			
		||||
    pub limit: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IssueQuery {
 | 
			
		||||
    fn to_string(&self, owner: &str, repo: &str) -> String {
 | 
			
		||||
        format!("repos/{owner}/{repo}/issues?state={}&labels={}&q={}&type={}&milestones={}&since={}&before={}&created_by={}&assigned_by={}&mentioned_by={}&page={}&limit={}",
 | 
			
		||||
            self.state.map(|s| s.as_str()).unwrap_or_default(),
 | 
			
		||||
            self.labels.join(","),
 | 
			
		||||
            self.query.as_deref().unwrap_or_default(),
 | 
			
		||||
            self._type.map(|t| t.as_str()).unwrap_or_default(),
 | 
			
		||||
            self.milestones.join(","),
 | 
			
		||||
            self.since.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
            self.before.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
            self.created_by.as_deref().unwrap_or_default(),
 | 
			
		||||
            self.assigned_by.as_deref().unwrap_or_default(),
 | 
			
		||||
            self.mentioned_by.as_deref().unwrap_or_default(),
 | 
			
		||||
            self.page.map(|page| page.to_string()).unwrap_or_default(),
 | 
			
		||||
            self.limit.map(|page| page.to_string()).unwrap_or_default(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq, Clone, Copy)]
 | 
			
		||||
pub enum IssueQueryType {
 | 
			
		||||
    Issues,
 | 
			
		||||
    Pulls,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IssueQueryType {
 | 
			
		||||
    fn as_str(&self) -> &'static str {
 | 
			
		||||
        match self {
 | 
			
		||||
            IssueQueryType::Issues => "issues",
 | 
			
		||||
            IssueQueryType::Pulls => "pulls",
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug)]
 | 
			
		||||
pub struct IssueCommentQuery {
 | 
			
		||||
    pub since: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub before: Option<time::OffsetDateTime>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IssueCommentQuery {
 | 
			
		||||
    fn to_string(&self, owner: &str, repo: &str, issue_id: u64) -> String {
 | 
			
		||||
        format!("repos/{owner}/{repo}/issues/{issue_id}/comments?since={}&before={}",
 | 
			
		||||
            self.since.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
            self.before.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Default, Debug)]
 | 
			
		||||
pub struct RepoCommentQuery {
 | 
			
		||||
    pub since: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub before: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub page: Option<u32>,
 | 
			
		||||
    pub limit: Option<u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl RepoCommentQuery {
 | 
			
		||||
    fn to_string(&self, owner: &str, repo: &str) -> String {
 | 
			
		||||
        format!("repos/{owner}/{repo}/issues/comments?since={}&before={}&page={}&limit={}",
 | 
			
		||||
            self.since.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
            self.before.map(|t| t.format(&time::format_description::well_known::Rfc3339).unwrap()).unwrap_or_default(),
 | 
			
		||||
            self.page.map(|page| page.to_string()).unwrap_or_default(),
 | 
			
		||||
            self.limit.map(|page| page.to_string()).unwrap_or_default(),
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, Debug, PartialEq, Default)]
 | 
			
		||||
pub struct CreateIssueOption {
 | 
			
		||||
    pub assignees: Vec<String>,
 | 
			
		||||
    pub body: Option<String>,
 | 
			
		||||
    pub closed: Option<bool>,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339::option")]
 | 
			
		||||
    pub due_date: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub labels: Vec<u64>,
 | 
			
		||||
    pub milestone: Option<u64>,
 | 
			
		||||
    pub _ref: Option<String>,
 | 
			
		||||
    pub title: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, Debug, PartialEq, Default)]
 | 
			
		||||
pub struct EditIssueOption {
 | 
			
		||||
    pub assignees: Vec<String>,
 | 
			
		||||
    pub body: Option<String>,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339::option")]
 | 
			
		||||
    pub due_date: Option<time::OffsetDateTime>,
 | 
			
		||||
    pub labels: Vec<u64>,
 | 
			
		||||
    pub milestone: Option<u64>,
 | 
			
		||||
    pub _ref: Option<String>,
 | 
			
		||||
    pub state: Option<State>,
 | 
			
		||||
    pub title: Option<String>,
 | 
			
		||||
    pub unset_due_date: Option<bool>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, Debug, PartialEq, Default)]
 | 
			
		||||
pub struct CreateIssueCommentOption {
 | 
			
		||||
    body: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, Debug, PartialEq, Default)]
 | 
			
		||||
pub struct EditIssueCommentOption {
 | 
			
		||||
    body: String,
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								src/lib.rs
									
										
									
									
									
								
							
							
						
						
									
										18
									
								
								src/lib.rs
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -8,9 +8,11 @@ pub struct Forgejo {
 | 
			
		|||
    client: Client,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod issue;
 | 
			
		||||
mod repository;
 | 
			
		||||
mod user;
 | 
			
		||||
 | 
			
		||||
pub use issue::*;
 | 
			
		||||
pub use repository::*;
 | 
			
		||||
pub use user::*;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +95,22 @@ impl Forgejo {
 | 
			
		|||
        self.execute(request).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn delete(&self, path: &str) -> Result<(), ForgejoError> {
 | 
			
		||||
        let url = self.url.join("api/v1/").unwrap().join(path).unwrap();
 | 
			
		||||
        let request = self.client.delete(url).build()?;
 | 
			
		||||
        self.execute(request).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn patch<T: Serialize, U: DeserializeOwned>(
 | 
			
		||||
        &self,
 | 
			
		||||
        path: &str,
 | 
			
		||||
        body: &T,
 | 
			
		||||
    ) -> Result<U, ForgejoError> {
 | 
			
		||||
        let url = self.url.join("api/v1/").unwrap().join(path).unwrap();
 | 
			
		||||
        let request = self.client.patch(url).json(body).build()?;
 | 
			
		||||
        self.execute(request).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn execute<T: DeserializeOwned>(&self, request: Request) -> Result<T, ForgejoError> {
 | 
			
		||||
        let response = self.client.execute(dbg!(request)).await?;
 | 
			
		||||
        match response.status() {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,6 +27,14 @@ pub struct Repo {
 | 
			
		|||
    pub owner: User,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct RepositoryMeta {
 | 
			
		||||
    pub full_name: String,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub name: String,
 | 
			
		||||
    pub owner: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize, Debug, PartialEq)]
 | 
			
		||||
pub struct CreateRepoOption {
 | 
			
		||||
    pub auto_init: bool,
 | 
			
		||||
| 
						 | 
				
			
			@ -50,3 +58,31 @@ pub enum TrustModel {
 | 
			
		|||
    #[serde(rename = "collaboratorcommiter")]
 | 
			
		||||
    CollaboratorCommitter,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct Milestone {
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub closed_at: time::OffsetDateTime,
 | 
			
		||||
    pub closed_issues: u64,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub created_at: time::OffsetDateTime,
 | 
			
		||||
    pub description: String,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub due_on: time::OffsetDateTime,
 | 
			
		||||
    pub id: u64,
 | 
			
		||||
    pub open_issues: u64,
 | 
			
		||||
    pub state: State,
 | 
			
		||||
    pub title: String,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub updated_at: time::OffsetDateTime,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PR structs
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Deserialize, Debug, PartialEq)]
 | 
			
		||||
pub struct PullRequestMeta {
 | 
			
		||||
    pub merged: bool,
 | 
			
		||||
    #[serde(with = "time::serde::rfc3339")]
 | 
			
		||||
    pub merged_at: time::OffsetDateTime,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue