1use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7use thiserror::Error;
8use opys_mojang_rules::{OsArch, OsConstraint, OsName, Rule, RuleAction, Ruleset};
9
10#[derive(Debug, Error)]
11pub enum ShorthandError {
12 #[error("Unknown action '{0}'")]
13 UnknownAction(String),
14 #[error("missing OS name")]
15 MissingOsName,
16 #[error("missing feature name")]
17 MissingFeature,
18 #[error("missing arch")]
19 MissingArch,
20 #[error("unknown rule type '{0}'")]
21 UnknownRuleType(String),
22 #[error("invalid os name '{0}'")]
23 InvalidOsName(String),
24 #[error("invalid arch '{0}'")]
25 InvalidArch(String),
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
31#[serde(untagged)]
32pub enum RawSingle {
33 Short(String),
34 Expanded(Rule),
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(untagged)]
40pub enum RawRuleset {
41 One(RawSingle),
42 Many(Vec<RawSingle>),
43}
44
45fn parse_os_name(s: &str) -> Result<OsName, ShorthandError> {
46 match s {
47 "linux" => Ok(OsName::Linux),
48 "windows" => Ok(OsName::Windows),
49 "osx" => Ok(OsName::Osx),
50 _ => Err(ShorthandError::InvalidOsName(s.to_owned())),
51 }
52}
53
54fn parse_arch(s: &str) -> Result<OsArch, ShorthandError> {
55 match s {
56 "x86" => Ok(OsArch::X86),
57 "x86_64" => Ok(OsArch::X86_64),
58 "arm" => Ok(OsArch::Arm),
59 "aarch64" => Ok(OsArch::Aarch64),
60 "any" => Ok(OsArch::Any),
61 _ => Err(ShorthandError::InvalidArch(s.to_owned())),
62 }
63}
64
65pub fn parse_short_rule(raw: RawSingle) -> Result<Rule, ShorthandError> {
66 let s = match raw {
67 RawSingle::Expanded(r) => return Ok(r),
68 RawSingle::Short(s) => s,
69 };
70
71 let mut parts = s.splitn(3, '.');
72 let action_str = parts.next().unwrap_or("");
73 let action = match action_str {
74 "allow" => RuleAction::Allow,
75 "disallow" => RuleAction::Disallow,
76 other => return Err(ShorthandError::UnknownAction(other.to_owned())),
77 };
78 let type_part = parts.next();
79 let rest = parts.next().unwrap_or("");
80
81 let Some(typ) = type_part else {
82 return Ok(Rule::Plain { action });
83 };
84
85 match typ {
86 "os" => {
87 if rest.is_empty() {
88 return Err(ShorthandError::MissingOsName);
89 }
90 let (name_part, version) = match rest.find('@') {
91 Some(i) => (&rest[..i], Some(rest[i + 1..].to_owned())),
92 None => (rest, None),
93 };
94 let name = parse_os_name(name_part)?;
95 Ok(Rule::Os {
96 action,
97 os: OsConstraint {
98 name: Some(name),
99 version,
100 arch: None,
101 },
102 })
103 }
104 "features" => {
105 if rest.is_empty() {
106 return Err(ShorthandError::MissingFeature);
107 }
108 let mut m = BTreeMap::new();
109 m.insert(rest.to_owned(), true);
110 Ok(Rule::Features {
111 action,
112 features: m,
113 })
114 }
115 "arch" => {
116 if rest.is_empty() {
117 return Err(ShorthandError::MissingArch);
118 }
119 Ok(Rule::Os {
120 action,
121 os: OsConstraint {
122 name: None,
123 version: None,
124 arch: Some(parse_arch(rest)?),
125 },
126 })
127 }
128 other => Err(ShorthandError::UnknownRuleType(other.to_owned())),
129 }
130}
131
132fn os_name_str(n: OsName) -> &'static str {
133 match n {
134 OsName::Linux => "linux",
135 OsName::Windows => "windows",
136 OsName::Osx => "osx",
137 }
138}
139
140fn arch_str(a: OsArch) -> &'static str {
141 match a {
142 OsArch::X86 => "x86",
143 OsArch::X86_64 => "x86_64",
144 OsArch::Arm => "arm",
145 OsArch::Aarch64 => "aarch64",
146 OsArch::Any => "any",
147 }
148}
149
150pub fn encode_short_rule(rule: &Rule) -> RawSingle {
151 let action = match rule.action() {
152 RuleAction::Allow => "allow",
153 RuleAction::Disallow => "disallow",
154 };
155 match rule {
156 Rule::Os { os, .. } => {
157 if let Some(name) = os.name {
158 if let Some(ver) = &os.version {
159 return RawSingle::Short(format!(
160 "{action}.os.{}@{ver}",
161 os_name_str(name)
162 ));
163 }
164 return RawSingle::Short(format!("{action}.os.{}", os_name_str(name)));
165 }
166 if let Some(arch) = os.arch {
167 return RawSingle::Short(format!("{action}.arch.{}", arch_str(arch)));
168 }
169 RawSingle::Expanded(rule.clone())
170 }
171 Rule::Features { features, .. } => {
172 if features.len() == 1 {
173 let (k, _) = features.iter().next().unwrap();
174 return RawSingle::Short(format!("{action}.features.{k}"));
175 }
176 RawSingle::Expanded(rule.clone())
177 }
178 Rule::Plain { .. } => RawSingle::Short(action.to_owned()),
179 }
180}
181
182pub fn parse_short_ruleset(raw: RawRuleset) -> Result<Ruleset, ShorthandError> {
183 let arr = match raw {
184 RawRuleset::Many(v) => v,
185 RawRuleset::One(s) => vec![s],
186 };
187 arr.into_iter().map(parse_short_rule).collect()
188}
189
190pub fn encode_short_ruleset(ruleset: &Ruleset) -> RawRuleset {
191 let encoded: Vec<RawSingle> = ruleset.iter().map(encode_short_rule).collect();
192 if encoded.len() == 1 {
193 RawRuleset::One(encoded.into_iter().next().unwrap())
194 } else {
195 RawRuleset::Many(encoded)
196 }
197}