use anyhow::Result;
use sloc_core::AnalysisRun;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AtlassianTier {
Cloud,
ServerDc,
}
pub fn detect_tier(base_url: &str) -> AtlassianTier {
if base_url.to_lowercase().contains(".atlassian.net") {
AtlassianTier::Cloud
} else {
AtlassianTier::ServerDc
}
}
pub fn atlassian_auth(username: Option<&str>, token: &str) -> String {
use base64::Engine as _;
match username {
Some(u) if !u.is_empty() => {
let enc = base64::engine::general_purpose::STANDARD.encode(format!("{u}:{token}"));
format!("Basic {enc}")
}
_ => format!("Bearer {token}"),
}
}
pub fn summary_facts(run: &AnalysisRun) -> Vec<(String, String)> {
let t = &run.summary_totals;
let mut facts = vec![
(
"Files analyzed".to_string(),
crate::fmt_thousands(t.files_analyzed),
),
("Code lines".to_string(), crate::fmt_thousands(t.code_lines)),
(
"Comment lines".to_string(),
crate::fmt_thousands(t.comment_lines),
),
(
"Blank lines".to_string(),
crate::fmt_thousands(t.blank_lines),
),
(
"Languages".to_string(),
run.totals_by_language.len().to_string(),
),
];
if let Some(branch) = run.git_branch.as_deref() {
facts.push(("Branch".to_string(), branch.to_string()));
}
if let Some(commit) = run.git_commit_short.as_deref() {
facts.push(("Commit".to_string(), commit.to_string()));
}
facts
}
pub fn render_wiki_summary(run: &AnalysisRun, report_url: Option<&str>) -> String {
use std::fmt::Write as _;
let mut out = String::new();
out.push_str("h2. SLOC Report\n\n");
out.push_str("||Metric||Value||\n");
for (k, v) in summary_facts(run) {
writeln!(out, "|{k}|{v}|").expect("write to String is infallible");
}
if let Some(url) = report_url {
writeln!(out, "\n[View full report|{url}]").expect("write to String is infallible");
}
out.push_str("\n_Generated by oxide-sloc._\n");
out
}
pub fn render_adf_summary(run: &AnalysisRun, report_url: Option<&str>) -> serde_json::Value {
let mut content = vec![serde_json::json!({
"type": "heading",
"attrs": { "level": 2 },
"content": [ { "type": "text", "text": "SLOC Report" } ]
})];
let items: Vec<serde_json::Value> = summary_facts(run)
.into_iter()
.map(|(k, v)| {
serde_json::json!({
"type": "listItem",
"content": [ {
"type": "paragraph",
"content": [ { "type": "text", "text": format!("{k}: {v}") } ]
} ]
})
})
.collect();
content.push(serde_json::json!({ "type": "bulletList", "content": items }));
if let Some(url) = report_url {
content.push(serde_json::json!({
"type": "paragraph",
"content": [ {
"type": "text",
"text": "View full report",
"marks": [ { "type": "link", "attrs": { "href": url } } ]
} ]
}));
}
serde_json::json!({ "version": 1, "type": "doc", "content": content })
}
pub fn summary_oneline(run: &AnalysisRun) -> String {
let t = &run.summary_totals;
format!(
"{} files, {} code, {} comment, {} blank across {} languages",
crate::fmt_thousands(t.files_analyzed),
crate::fmt_thousands(t.code_lines),
crate::fmt_thousands(t.comment_lines),
crate::fmt_thousands(t.blank_lines),
run.totals_by_language.len(),
)
}
pub fn atlassian_ssrf_check(url: &str, allow_private_net: bool) -> Result<()> {
crate::validate_webhook_url(url, allow_private_net)
}
#[cfg(test)]
mod tests {
use super::*;
use sloc_core::AnalysisRun;
fn sample_run() -> AnalysisRun {
let json = serde_json::json!({
"tool": {
"name": "oxide-sloc",
"version": "0.0.0",
"run_id": "test",
"timestamp_utc": "2026-01-01T00:00:00Z"
},
"environment": {
"operating_system": "test-os",
"architecture": "x86_64",
"runtime_mode": "cli",
"initiator_username": "tester",
"initiator_hostname": "host"
},
"effective_configuration": {},
"input_roots": ["."],
"summary_totals": {
"files_considered": 12,
"files_analyzed": 12,
"files_skipped": 0,
"total_physical_lines": 4368,
"code_lines": 3456,
"comment_lines": 789,
"blank_lines": 123,
"mixed_lines_separate": 0
},
"totals_by_language": [
{ "language": "rust", "files": 10, "total_physical_lines": 4000,
"code_lines": 3400, "comment_lines": 700, "blank_lines": 100,
"mixed_lines_separate": 0 },
{ "language": "shell", "files": 2, "total_physical_lines": 400,
"code_lines": 56, "comment_lines": 89, "blank_lines": 23,
"mixed_lines_separate": 0 }
],
"per_file_records": [],
"skipped_file_records": [],
"warnings": [],
"git_branch": "main",
"git_commit_short": "abc1234"
});
serde_json::from_value(json).expect("sample AnalysisRun deserializes")
}
#[test]
fn detect_tier_cloud_vs_server() {
assert_eq!(
detect_tier("https://myco.atlassian.net"),
AtlassianTier::Cloud
);
assert_eq!(
detect_tier("https://MYCO.ATLASSIAN.NET/wiki"),
AtlassianTier::Cloud
);
assert_eq!(
detect_tier("https://jira.corp.com/"),
AtlassianTier::ServerDc
);
assert_eq!(
detect_tier("https://confluence.internal:8090"),
AtlassianTier::ServerDc
);
}
#[test]
fn auth_basic_with_username_bearer_without() {
use base64::Engine as _;
let basic = atlassian_auth(Some("alice@corp.com"), "tok123");
let expected = base64::engine::general_purpose::STANDARD.encode("alice@corp.com:tok123");
assert_eq!(basic, format!("Basic {expected}"));
assert_eq!(atlassian_auth(None, "pat-xyz"), "Bearer pat-xyz");
assert_eq!(atlassian_auth(Some(""), "pat-xyz"), "Bearer pat-xyz");
}
#[test]
fn wiki_summary_has_header_and_counts() {
let run = sample_run();
let wiki = render_wiki_summary(&run, Some("https://reports.corp/r/1.html"));
assert!(wiki.contains("h2. SLOC Report"));
assert!(wiki.contains("||Metric||Value||"));
assert!(wiki.contains("|Code lines|3,456|"));
assert!(wiki.contains("|Languages|2|"));
assert!(wiki.contains("[View full report|https://reports.corp/r/1.html]"));
}
#[test]
fn adf_summary_is_valid_doc() {
let run = sample_run();
let adf = render_adf_summary(&run, None);
assert_eq!(adf["version"], 1);
assert_eq!(adf["type"], "doc");
assert_eq!(adf["content"].as_array().map(Vec::len), Some(2));
let with_link = render_adf_summary(&run, Some("https://reports.corp/r/1.html"));
assert_eq!(with_link["content"].as_array().map(Vec::len), Some(3));
}
#[test]
fn ssrf_guard_blocks_metadata_allows_optin() {
assert!(atlassian_ssrf_check("http://169.254.169.254/latest/meta-data", false).is_err());
assert!(
atlassian_ssrf_check("http://10.0.0.5/rest/api/2/issue/X-1/comment", false).is_err()
);
assert!(atlassian_ssrf_check("http://10.0.0.5/rest/api/2/issue/X-1/comment", true).is_ok());
assert!(
atlassian_ssrf_check("https://jira.corp.com/rest/api/2/issue/X-1/comment", false)
.is_ok()
);
}
}