1use crate::controls::{ConstraintPlatform, root};
2use crate::de::{self, Error, ErrorKind, Obj, fail};
3use crate::json::canonical;
4use crate::model::RepoPath;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ActionMetadata {
13 pub name: String,
14 pub description: String,
15 pub main: RepoPath,
16}
17
18impl ActionMetadata {
19 pub fn parse(bytes: &[u8]) -> Result<Self, Error> {
28 let trimmed = bytes
29 .strip_suffix(b"\n")
30 .ok_or_else(|| Error::new("$", ErrorKind::InvalidValue))?;
31 let value = root(trimmed)?;
32 if canonical(&value) != trimmed {
33 return fail("$", ErrorKind::InvalidValue);
34 }
35 let mut obj = Obj::new("$", value)?;
36 let name = de::string(&obj.field("name"), obj.take("name")?)?;
37 let description = de::string(&obj.field("description"), obj.take("description")?)?;
38 let runs_path = obj.field("runs");
39 let mut runs = Obj::new(&runs_path, obj.take("runs")?)?;
40 let main_path = runs.field("main");
41 let main = RepoPath::new(de::string(&main_path, runs.take("main")?)?)
42 .ok_or_else(|| Error::new(&main_path, ErrorKind::InvalidValue))?;
43 de::const_str(&runs.field("using"), runs.take("using")?, "node20")?;
44 runs.finish()?;
45 obj.finish()?;
46 if name.is_empty() || description.is_empty() {
47 return fail("$", ErrorKind::InvalidValue);
48 }
49 Ok(Self {
50 name,
51 description,
52 main,
53 })
54 }
55}
56
57#[must_use]
62pub fn executable_platform(bytes: &[u8]) -> Option<ConstraintPlatform> {
63 elf_platform(bytes)
64 .or_else(|| mach_o_platform(bytes))
65 .or_else(|| pe_platform(bytes))
66}
67
68fn elf_platform(bytes: &[u8]) -> Option<ConstraintPlatform> {
71 if bytes.get(..4) != Some(&[0x7f, b'E', b'L', b'F']) {
72 return None;
73 }
74 if bytes.get(4) != Some(&2) || bytes.get(5) != Some(&1) {
75 return None;
76 }
77 match bytes.get(18..20)? {
78 [0x3e, 0x00] => Some(ConstraintPlatform::LinuxX8664),
79 [0xb7, 0x00] => Some(ConstraintPlatform::LinuxAarch64),
80 _ => None,
81 }
82}
83
84fn mach_o_platform(bytes: &[u8]) -> Option<ConstraintPlatform> {
87 if bytes.get(..4) != Some(&[0xcf, 0xfa, 0xed, 0xfe]) {
88 return None;
89 }
90 match bytes.get(4..8)? {
91 [0x07, 0x00, 0x00, 0x01] => Some(ConstraintPlatform::MacosX8664),
92 [0x0c, 0x00, 0x00, 0x01] => Some(ConstraintPlatform::MacosAarch64),
93 _ => None,
94 }
95}
96
97fn pe_platform(bytes: &[u8]) -> Option<ConstraintPlatform> {
100 if bytes.get(..2) != Some(b"MZ") {
101 return None;
102 }
103 let header = bytes.get(0x3c..0x40)?;
104 let [a, b, c, d] = header else { return None };
105 let offset = usize::try_from(u32::from_le_bytes([*a, *b, *c, *d])).ok()?;
106 if bytes.get(offset..offset.checked_add(4)?) != Some(b"PE\0\0") {
107 return None;
108 }
109 let machine = offset.checked_add(4)?;
110 match bytes.get(machine..machine.checked_add(2)?)? {
111 [0x64, 0x86] => Some(ConstraintPlatform::WindowsX8664),
112 [0x64, 0xaa] => Some(ConstraintPlatform::WindowsAarch64),
113 _ => None,
114 }
115}
116
117#[must_use]
121pub const fn host_platform() -> Option<ConstraintPlatform> {
122 match (
123 cfg!(target_os = "linux"),
124 cfg!(target_os = "macos"),
125 cfg!(target_os = "windows"),
126 ) {
127 (true, _, _) => arch(
128 ConstraintPlatform::LinuxX8664,
129 ConstraintPlatform::LinuxAarch64,
130 ),
131 (_, true, _) => arch(
132 ConstraintPlatform::MacosX8664,
133 ConstraintPlatform::MacosAarch64,
134 ),
135 (_, _, true) => arch(
136 ConstraintPlatform::WindowsX8664,
137 ConstraintPlatform::WindowsAarch64,
138 ),
139 _ => None,
140 }
141}
142
143const fn arch(
144 x86_64: ConstraintPlatform,
145 aarch64: ConstraintPlatform,
146) -> Option<ConstraintPlatform> {
147 if cfg!(target_arch = "x86_64") {
148 Some(x86_64)
149 } else if cfg!(target_arch = "aarch64") {
150 Some(aarch64)
151 } else {
152 None
153 }
154}