use serde::Deserialize;
use serde_json::{Value, json};
use crate::api::client::{ApiError, GitHub};
use crate::api::model::RepoId;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Severity {
Level(String),
Cvss(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NewAdvisory {
pub summary: String,
pub description: String,
pub ecosystem: String,
pub package: String,
pub affected: String,
pub patched: String,
pub severity: Severity,
pub cve: Option<String>,
pub cwes: Vec<String>,
}
impl NewAdvisory {
pub fn body(&self) -> Value {
let some = |s: &str| (!s.trim().is_empty()).then(|| s.trim().to_owned());
let mut body = json!({
"summary": self.summary,
"description": self.description,
"vulnerabilities": [{
"package": { "ecosystem": self.ecosystem, "name": self.package },
"vulnerable_version_range": some(&self.affected),
"patched_versions": some(&self.patched),
}],
"cve_id": self.cve,
"cwe_ids": self.cwes,
});
if let Some(body) = body.as_object_mut() {
let (name, value) = match &self.severity {
Severity::Level(level) => ("severity", level),
Severity::Cvss(vector) => ("cvss_vector_string", vector),
};
body.insert(name.into(), value.as_str().into());
}
body
}
}
impl GitHub {
pub async fn draft_advisory(
&self,
repo: &RepoId,
advisory: &NewAdvisory,
) -> Result<String, ApiError> {
#[derive(Deserialize)]
struct Made {
ghsa_id: String,
}
let path = format!("/repos/{repo}/security-advisories");
let body = advisory.body();
let made: Made = (self.rest_write_json(http::Method::POST, &path, Some(&body))).await?;
Ok(made.ghsa_id)
}
}