//! The `[versions]` subsection of an advisory.

use crate::{osv, Error};
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};

/// The `[versions]` subsection of an advisory: future home to information
/// about which versions are patched and/or unaffected.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
#[serde(try_from = "RawVersions")]
pub struct Versions {
    /// Versions which are patched and not vulnerable (expressed as semantic version requirements)
    patched: Vec<VersionReq>,

    /// Versions which were never affected in the first place
    #[serde(default)]
    unaffected: Vec<VersionReq>,
}

impl Versions {
    /// Is the given version of a package vulnerable?
    pub fn is_vulnerable(&self, version: &Version) -> bool {
        for range in osv::ranges_for_advisory(self).iter() {
            if range.affects(version) {
                return true;
            }
        }
        false
    }

    /// Creates a new `[versions]` entry.
    /// Checks consistency of the passed version requirements.
    pub fn new(patched: Vec<VersionReq>, unaffected: Vec<VersionReq>) -> Result<Self, Error> {
        RawVersions {
            patched,
            unaffected,
        }
        .try_into()
    }

    /// Versions which are patched and not vulnerable (expressed as semantic version requirements)
    pub fn patched(&self) -> &[VersionReq] {
        self.patched.as_slice()
    }

    /// Versions which were never affected in the first place
    pub fn unaffected(&self) -> &[VersionReq] {
        self.unaffected.as_slice()
    }
}

impl TryFrom<RawVersions> for Versions {
    type Error = Error;

    fn try_from(raw: RawVersions) -> Result<Self, Self::Error> {
        validate_ranges(&raw)?;
        Ok(Versions {
            patched: raw.patched,
            unaffected: raw.unaffected,
        })
    }
}

#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
/// Raw deserialized data that didn't pass validation yet
pub(crate) struct RawVersions {
    pub patched: Vec<VersionReq>,

    #[serde(default)]
    pub unaffected: Vec<VersionReq>,
}

fn validate_ranges(versions: &RawVersions) -> Result<(), Error> {
    let _ = osv::ranges_for_unvalidated_advisory(versions)?;
    Ok(())
}
