1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use crate::registry::RecordId;
use core::fmt;
use indexmap::IndexSet;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::{str::FromStr, time::SystemTime};
use warg_crypto::hash::{AnyHash, HashAlgorithm};
use warg_crypto::signing;

/// A package record is a collection of entries published together by the same author
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PackageRecord {
    /// The hash of the previous package record envelope
    pub prev: Option<RecordId>,
    /// The version of the registry protocol used
    pub version: u32,
    /// When this record was published
    pub timestamp: SystemTime,
    /// The entries being published in this record
    pub entries: Vec<PackageEntry>,
}

impl crate::Record for PackageRecord {
    fn contents(&self) -> IndexSet<&AnyHash> {
        self.entries
            .iter()
            .filter_map(PackageEntry::content)
            .collect()
    }
}

/// Each permission represents the ability to use the specified entry
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub enum Permission {
    Release,
    Yank,
}

impl Permission {
    /// Gets an array of all permissions.
    pub const fn all() -> [Permission; 2] {
        [Permission::Release, Permission::Yank]
    }
}

impl fmt::Display for Permission {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Permission::Release => write!(f, "release"),
            Permission::Yank => write!(f, "yank"),
        }
    }
}

impl FromStr for Permission {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "release" => Ok(Permission::Release),
            "yank" => Ok(Permission::Yank),
            _ => Err(format!("invalid permission {s:?}")),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum PackageEntry {
    /// Initializes a package log.
    /// Must be the first entry of every log and not appear elsewhere.
    Init {
        /// The hash algorithm this log will use for linking
        hash_algorithm: HashAlgorithm,
        /// The key of the original package maintainer
        key: signing::PublicKey,
    },
    /// Grant the specified key a permission.
    /// The author of this entry must have the permission.
    GrantFlat {
        key: signing::PublicKey,
        permissions: Vec<Permission>,
    },
    /// Remove a permission from a key.
    /// The author of this entry must have the permission.
    RevokeFlat {
        key_id: signing::KeyID,
        permissions: Vec<Permission>,
    },
    /// Release a version of a package.
    /// The version must not have been released yet.
    Release { version: Version, content: AnyHash },
    /// Yank a version of a package.
    /// The version must have been released and not yanked.
    Yank { version: Version },
}

impl PackageEntry {
    /// Check permission is required to submit this entry
    pub fn required_permission(&self) -> Option<Permission> {
        match self {
            Self::Init { .. } | Self::GrantFlat { .. } | Self::RevokeFlat { .. } => None,
            Self::Release { .. } => Some(Permission::Release),
            Self::Yank { .. } => Some(Permission::Yank),
        }
    }

    /// Gets the content associated with the entry.
    ///
    /// Returns `None` if the entry does not have content.
    pub fn content(&self) -> Option<&AnyHash> {
        match self {
            Self::Release { content, .. } => Some(content),
            _ => None,
        }
    }
}