1use crate::de::{self, Error, ErrorKind, Obj, fail};
2use crate::digest::{Digest, hj};
3use crate::json::Value;
4use crate::model::{ObjectFormat, Oid, RepoPathText, RepositoryIdentity};
5
6use super::{decode_digest, decode_repo_path, decode_repository, root};
7
8const EXECUTION_CONSTRAINT_SCHEMA: &str = "amiss/scanner-execution-constraint";
9const ACTION_BOOTSTRAP_CONTRACT: &str = "amiss-action-bootstrap";
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum ConstraintPlatform {
13 LinuxX8664,
14 LinuxAarch64,
15 MacosX8664,
16 MacosAarch64,
17 WindowsX8664,
18 WindowsAarch64,
19}
20
21impl ConstraintPlatform {
22 #[must_use]
23 pub const fn as_str(self) -> &'static str {
24 match self {
25 Self::LinuxX8664 => "linux-x86_64",
26 Self::LinuxAarch64 => "linux-aarch64",
27 Self::MacosX8664 => "macos-x86_64",
28 Self::MacosAarch64 => "macos-aarch64",
29 Self::WindowsX8664 => "windows-x86_64",
30 Self::WindowsAarch64 => "windows-aarch64",
31 }
32 }
33
34 pub fn decode(path: &str, value: Value) -> Result<Self, Error> {
38 match de::string(path, value)?.as_str() {
39 "linux-x86_64" => Ok(Self::LinuxX8664),
40 "linux-aarch64" => Ok(Self::LinuxAarch64),
41 "macos-x86_64" => Ok(Self::MacosX8664),
42 "macos-aarch64" => Ok(Self::MacosAarch64),
43 "windows-x86_64" => Ok(Self::WindowsX8664),
44 "windows-aarch64" => Ok(Self::WindowsAarch64),
45 _ => fail(path, ErrorKind::InvalidValue),
46 }
47 }
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
53pub struct ExecutionConstraintDescriptor {
54 pub digest: Digest,
55 pub action_repository: RepositoryIdentity,
56 pub action_object_format: ObjectFormat,
57 pub action_commit_oid: Oid,
58 pub action_tree_oid: Oid,
59 pub manifest_path: RepoPathText,
60 pub release_manifest_digest: Digest,
61 pub selected_platform: ConstraintPlatform,
62 pub required_status_name: String,
63 pub bootstrap_digest: Digest,
64}
65
66fn decode_status_name(path: &str, value: Value) -> Result<String, Error> {
67 let raw = de::string(path, value)?;
68 let bytes = raw.as_bytes();
69 let interior = |byte: &u8| {
70 byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'.' | b'_' | b'/' | b'-')
71 };
72 let edge =
73 |byte: &u8| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'/' | b'-');
74 let valid = match (bytes.first(), bytes.last()) {
75 (Some(first), Some(last)) => {
76 bytes.len() <= 160
77 && first.is_ascii_alphanumeric()
78 && (bytes.len() == 1 || edge(last))
79 && bytes.iter().all(interior)
80 }
81 _ => false,
82 };
83 if valid {
84 Ok(raw)
85 } else {
86 fail(path, ErrorKind::InvalidValue)
87 }
88}
89
90impl ExecutionConstraintDescriptor {
91 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
96 let value = root(bytes)?;
97 let digest = hj(EXECUTION_CONSTRAINT_SCHEMA, &value);
98 let mut obj = Obj::new("$", value)?;
99 de::const_str(
100 &obj.field("schema"),
101 obj.take("schema")?,
102 EXECUTION_CONSTRAINT_SCHEMA,
103 )?;
104 let action_repository = decode_repository(
105 &obj.field("action_repository"),
106 obj.take("action_repository")?,
107 )?;
108 let format_path = obj.field("action_object_format");
109 let action_object_format =
110 match de::string(&format_path, obj.take("action_object_format")?)?.as_str() {
111 "sha1" => ObjectFormat::Sha1,
112 "sha256" => ObjectFormat::Sha256,
113 _ => return fail(&format_path, ErrorKind::InvalidValue),
114 };
115 let commit_path = obj.field("action_commit_oid");
116 let action_commit_oid = Oid::new(
117 action_object_format,
118 de::string(&commit_path, obj.take("action_commit_oid")?)?,
119 )
120 .ok_or_else(|| Error::new(&commit_path, ErrorKind::InvalidValue))?;
121 let tree_path = obj.field("action_tree_oid");
122 let action_tree_oid = Oid::new(
123 action_object_format,
124 de::string(&tree_path, obj.take("action_tree_oid")?)?,
125 )
126 .ok_or_else(|| Error::new(&tree_path, ErrorKind::InvalidValue))?;
127 let manifest_path =
128 decode_repo_path(&obj.field("manifest_path"), obj.take("manifest_path")?)?;
129 let release_manifest_digest = decode_digest(
130 &obj.field("release_manifest_digest"),
131 obj.take("release_manifest_digest")?,
132 )?;
133 let selected_platform = ConstraintPlatform::decode(
134 &obj.field("selected_platform"),
135 obj.take("selected_platform")?,
136 )?;
137 let required_status_name = decode_status_name(
138 &obj.field("required_status_name"),
139 obj.take("required_status_name")?,
140 )?;
141 de::const_str(
142 &obj.field("bootstrap_contract"),
143 obj.take("bootstrap_contract")?,
144 ACTION_BOOTSTRAP_CONTRACT,
145 )?;
146 let bootstrap_digest = decode_digest(
147 &obj.field("bootstrap_digest"),
148 obj.take("bootstrap_digest")?,
149 )?;
150 obj.finish()?;
151 Ok(Self {
152 digest,
153 action_repository,
154 action_object_format,
155 action_commit_oid,
156 action_tree_oid,
157 manifest_path,
158 release_manifest_digest,
159 selected_platform,
160 required_status_name,
161 bootstrap_digest,
162 })
163 }
164}