Skip to content

Commit daea3b7

Browse files
authored
feat: add issue create command (#44)
1 parent a9580ba commit daea3b7

4 files changed

Lines changed: 710 additions & 22 deletions

File tree

src/cli.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::auth::{AuthService, LoginRequest, LoginTokenSource};
66
use crate::command::{CommandError, CommandOutcome, EXIT_OK, OutputFormat};
77
use crate::gitee_api::PullRequestListFilters;
88
use crate::issue::{
9-
IssueCommentBodySource, IssueCommentRequest, IssueListRequest, IssueService, IssueStateFilter,
10-
IssueViewRequest,
9+
IssueBodySource, IssueCommentRequest, IssueCreateRequest, IssueListRequest, IssueService,
10+
IssueStateFilter, IssueViewRequest,
1111
};
1212
use crate::pr::{
1313
PrCheckoutRequest, PrCommentRequest, PrCreateRequest, PrListRequest, PrService,
@@ -73,6 +73,9 @@ fn run_issue(args: &[String]) -> Result<CommandOutcome, CommandError> {
7373
let issue = IssueService::from_env();
7474

7575
match subcommand.as_str() {
76+
"create" => execute_parsed(parse_issue_create_args(rest), |request| {
77+
issue.create(request)
78+
}),
7679
"comment" => execute_parsed(parse_issue_comment_args(rest), |request| {
7780
issue.comment(request)
7881
}),
@@ -265,11 +268,11 @@ fn parse_issue_comment_args(
265268

266269
let Some(body) = body_values
267270
.last()
268-
.map(|value| IssueCommentBodySource::Flag(value.clone()))
271+
.map(|value| IssueBodySource::Inline(value.clone()))
269272
.or_else(|| {
270273
body_file_values
271274
.last()
272-
.map(|value| IssueCommentBodySource::File(PathBuf::from(value)))
275+
.map(|value| IssueBodySource::File(PathBuf::from(value)))
273276
})
274277
else {
275278
return Err(CommandError::usage(
@@ -286,6 +289,44 @@ fn parse_issue_comment_args(
286289
})
287290
}
288291

292+
fn parse_issue_create_args(
293+
args: &[String],
294+
) -> Result<ParseOutcome<IssueCreateRequest>, CommandError> {
295+
map_parsed(parse_matches(issue_create_command(), args), |matches| {
296+
let output = output_format(&matches);
297+
let repo = last_value(&matches, "repo");
298+
let title = last_value(&matches, "title");
299+
let body_values = values(&matches, "body");
300+
let body_file_values = values(&matches, "body_file");
301+
302+
if body_values.len() + body_file_values.len() > 1 {
303+
return Err(CommandError::usage(
304+
"provide only one of --body or --body-file",
305+
));
306+
}
307+
308+
let Some(title) = title else {
309+
return Err(CommandError::usage("issue create requires --title"));
310+
};
311+
312+
let body = body_values
313+
.last()
314+
.map(|value| IssueBodySource::Inline(value.clone()))
315+
.or_else(|| {
316+
body_file_values
317+
.last()
318+
.map(|value| IssueBodySource::File(PathBuf::from(value)))
319+
});
320+
321+
Ok(IssueCreateRequest {
322+
output,
323+
repo,
324+
title,
325+
body,
326+
})
327+
})
328+
}
329+
289330
fn parse_pr_view_args(args: &[String]) -> Result<ParseOutcome<PrViewRequest>, CommandError> {
290331
map_parsed(parse_matches(pr_view_command(), args), |matches| {
291332
let output = output_format(&matches);
@@ -661,6 +702,7 @@ fn auth_help_command() -> Command {
661702

662703
fn issue_help_command() -> Command {
663704
base_command("issue")
705+
.subcommand(issue_create_command())
664706
.subcommand(issue_comment_command())
665707
.subcommand(issue_list_command())
666708
.subcommand(issue_view_command())
@@ -726,6 +768,15 @@ fn issue_comment_command() -> Command {
726768
.arg(positionals_arg())
727769
}
728770

771+
fn issue_create_command() -> Command {
772+
base_command("create")
773+
.arg(json_flag())
774+
.arg(string_option("repo", "repo", "REPO"))
775+
.arg(string_option("title", "title", "TITLE"))
776+
.arg(string_option("body", "body", "BODY"))
777+
.arg(string_option("body_file", "body-file", "PATH"))
778+
}
779+
729780
fn pr_view_command() -> Command {
730781
base_command("view")
731782
.arg(json_flag())

src/gitee_api.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ pub struct CreatePullRequestComment<'a> {
1212
pub body: &'a str,
1313
}
1414

15+
pub struct CreateIssue<'a> {
16+
pub repo: &'a str,
17+
pub title: &'a str,
18+
pub body: Option<&'a str>,
19+
}
20+
1521
pub struct CreatePullRequest<'a> {
1622
pub title: &'a str,
1723
pub head: &'a str,
@@ -306,6 +312,55 @@ impl GiteeClient {
306312
Err(IssueError::UnexpectedStatus(response.status().as_u16()))
307313
}
308314

315+
pub fn create_issue(
316+
&self,
317+
owner: &str,
318+
token: &str,
319+
request: &CreateIssue<'_>,
320+
) -> Result<Issue, IssueError> {
321+
let mut form = vec![
322+
("access_token", token.to_string()),
323+
("repo", request.repo.to_string()),
324+
("title", request.title.to_string()),
325+
];
326+
327+
if let Some(body) = request.body {
328+
form.push(("body", body.to_string()));
329+
}
330+
331+
let response = self
332+
.client
333+
.post(format!("{}/v5/repos/{owner}/issues", self.base_url))
334+
.form(&form)
335+
.send()
336+
.map_err(IssueError::Transport)?;
337+
338+
if response.status().is_success() {
339+
let issue = response
340+
.json::<IssueResponse>()
341+
.map_err(IssueError::Transport)?
342+
.into_issue();
343+
return Ok(issue);
344+
}
345+
346+
let status = response.status().as_u16();
347+
let error_message = parse_api_error_message(response);
348+
349+
if status == 401 {
350+
return Err(IssueError::InvalidToken);
351+
}
352+
353+
if status == 404 {
354+
return Err(IssueError::NotFound);
355+
}
356+
357+
if let Some(message) = error_message {
358+
return Err(IssueError::UnexpectedStatusWithMessage(status, message));
359+
}
360+
361+
Err(IssueError::UnexpectedStatus(status))
362+
}
363+
309364
pub fn fetch_pull_request(
310365
&self,
311366
owner: &str,
@@ -519,6 +574,7 @@ pub enum IssueError {
519574
NotFound,
520575
Transport(reqwest::Error),
521576
UnexpectedStatus(u16),
577+
UnexpectedStatusWithMessage(u16, String),
522578
}
523579

524580
pub enum PullRequestError {

0 commit comments

Comments
 (0)