1use crate::report::STICKY_MARKER;
12use crate::secret::Secret;
13
14#[derive(Debug, Clone)]
16pub struct GithubContext {
17 pub repo: String,
19 pub pr: u64,
21 pub token: Secret,
25 pub api_base: Option<String>,
27}
28
29impl GithubContext {
30 pub fn api_base(&self) -> &str {
31 self.api_base.as_deref().unwrap_or("https://api.github.com")
32 }
33}
34
35pub fn default_headers(token: &Secret) -> Vec<(&'static str, String)> {
40 vec![
41 ("Authorization", format!("Bearer {}", token.reveal())),
42 ("Accept", "application/vnd.github+json".to_string()),
43 ("X-GitHub-Api-Version", "2022-11-28".to_string()),
44 ("User-Agent", format!("aatxe/{}", env!("CARGO_PKG_VERSION"))),
45 ]
46}
47
48pub fn list_comments_url(ctx: &GithubContext, page: u32) -> String {
50 format!(
51 "{}/repos/{}/issues/{}/comments?per_page=100&page={}",
52 ctx.api_base(),
53 ctx.repo,
54 ctx.pr,
55 page,
56 )
57}
58
59pub fn patch_comment_url(ctx: &GithubContext, comment_id: u64) -> String {
61 format!(
62 "{}/repos/{}/issues/comments/{}",
63 ctx.api_base(),
64 ctx.repo,
65 comment_id,
66 )
67}
68
69pub fn create_comment_url(ctx: &GithubContext) -> String {
71 format!(
72 "{}/repos/{}/issues/{}/comments",
73 ctx.api_base(),
74 ctx.repo,
75 ctx.pr,
76 )
77}
78
79pub fn validate_sticky(body: &str) -> Result<(), &'static str> {
83 if body.contains(STICKY_MARKER) {
84 Ok(())
85 } else {
86 Err("comment body is missing the sticky marker; render with crate::render_markdown")
87 }
88}
89
90#[derive(Debug, Clone, Default)]
94pub struct DetectedContext {
95 pub repo: Option<String>,
96 pub pr: Option<u64>,
97 pub token: Option<Secret>,
98}
99
100pub fn detect_context<F: Fn(&str) -> Option<String>>(get: F) -> DetectedContext {
103 let token = get("GITHUB_TOKEN")
104 .or_else(|| get("GH_TOKEN"))
105 .map(Secret::new);
106 let repo = get("GITHUB_REPOSITORY");
107 let pr = detect_pr_number(&get);
108 DetectedContext { repo, pr, token }
109}
110
111fn detect_pr_number<F: Fn(&str) -> Option<String>>(get: &F) -> Option<u64> {
112 if let Some(v) = get("AATXE_PR") {
113 if let Ok(n) = v.parse::<u64>() {
114 if n > 0 {
115 return Some(n);
116 }
117 }
118 }
119 if let Some(reff) = get("GITHUB_REF") {
121 if let Some(rest) = reff.strip_prefix("refs/pull/") {
122 if let Some(slash) = rest.find('/') {
123 if let Ok(n) = rest[..slash].parse::<u64>() {
124 return Some(n);
125 }
126 }
127 }
128 }
129 if let Some(name) = get("GITHUB_REF_NAME") {
131 if let Some((num, suffix)) = name.split_once('/') {
132 if matches!(suffix, "merge" | "head") {
133 if let Ok(n) = num.parse::<u64>() {
134 return Some(n);
135 }
136 }
137 }
138 }
139 None
140}