1use std::{collections::BTreeSet, iter::once};
4
5use rustc_hash::{FxHashMap, FxHashSet};
6use steel_utils::Identifier;
7use thiserror::Error;
8
9use super::{
10 brigadier::{
11 CommandDispatcher, CommandNodeBuilder, CommandRequirement, CommandRequirementRoute, NodeId,
12 RegistrationError,
13 },
14 execution::{CommandPermissionSource, SteelCommandRuntime},
15};
16use crate::permission::{
17 PermissionExpr, PermissionKey, PermissionKeyError, PermissionSegment, PermissionState,
18};
19
20type CommandFactory<S> =
21 dyn FnOnce(NodeId) -> CommandNodeBuilder<S, SteelCommandRuntime> + Send + 'static;
22
23pub(crate) const ENTITY_SELECTOR_PERMISSION_KEY: &str = "minecraft.selector";
24pub(crate) const ENTITY_SELECTOR_ADVANCED_PERMISSION_KEY: &str = "minecraft.selector.advanced";
25
26pub(crate) fn entity_selector_permission_expr() -> Result<PermissionExpr, PermissionKeyError> {
27 PermissionKey::parse(ENTITY_SELECTOR_PERMISSION_KEY).map(PermissionExpr::key)
28}
29
30pub(crate) fn entity_selector_advanced_permission_expr()
31-> Result<PermissionExpr, PermissionKeyError> {
32 PermissionKey::parse(ENTITY_SELECTOR_ADVANCED_PERMISSION_KEY).map(PermissionExpr::key)
33}
34
35pub(crate) struct CommandRegistration<S>
37where
38 S: CommandPermissionSource,
39{
40 id: Identifier,
41 aliases: Vec<Box<str>>,
42 permission: Option<PermissionExpr>,
43 subcommand_permissions: Vec<Vec<Box<str>>>,
44 default_access: bool,
45 factory: Box<CommandFactory<S>>,
46}
47
48impl<S> CommandRegistration<S>
49where
50 S: CommandPermissionSource,
51{
52 pub(crate) fn new(
54 id: Identifier,
55 factory: impl FnOnce(NodeId) -> CommandNodeBuilder<S, SteelCommandRuntime> + Send + 'static,
56 ) -> Self {
57 Self {
58 id,
59 aliases: Vec::new(),
60 permission: None,
61 subcommand_permissions: Vec::new(),
62 default_access: false,
63 factory: Box::new(factory),
64 }
65 }
66
67 #[must_use]
69 pub(crate) fn alias(mut self, alias: impl Into<Box<str>>) -> Self {
70 self.aliases.push(alias.into());
71 self
72 }
73
74 #[must_use]
76 pub(crate) const fn default_access(mut self) -> Self {
77 self.default_access = true;
78 self
79 }
80
81 #[must_use]
83 pub(crate) fn permission(mut self, permission: PermissionExpr) -> Self {
84 self.permission = Some(permission);
85 self
86 }
87
88 #[must_use]
94 pub(crate) fn subcommand_permission<I, T>(mut self, path: I) -> Self
95 where
96 I: IntoIterator<Item = T>,
97 T: Into<Box<str>>,
98 {
99 self.subcommand_permissions
100 .push(path.into_iter().map(Into::into).collect());
101 self
102 }
103
104 fn validate(&self) -> Result<(), CommandRegistrationError> {
105 if self.id.namespace.is_empty()
106 || self.id.path.is_empty()
107 || !Identifier::validate(&self.id.namespace, &self.id.path)
108 {
109 return Err(CommandRegistrationError::InvalidCommandId(self.id.clone()));
110 }
111
112 let mut roots = FxHashSet::default();
113 roots.insert(self.id.path.as_ref());
114 for alias in &self.aliases {
115 validate_alias(alias)?;
116 if !roots.insert(alias.as_ref()) {
117 return Err(CommandRegistrationError::DuplicateOwnedRoot {
118 id: self.id.clone(),
119 root: alias.clone(),
120 });
121 }
122 }
123 if self.permission.is_some() && !self.subcommand_permissions.is_empty() {
124 return Err(
125 CommandRegistrationError::SubcommandPermissionsRequireDerivedRoot {
126 id: self.id.clone(),
127 },
128 );
129 }
130 let mut permission_paths = FxHashSet::default();
131 for path in &self.subcommand_permissions {
132 if path.is_empty() {
133 return Err(CommandRegistrationError::EmptySubcommandPermissionPath {
134 id: self.id.clone(),
135 });
136 }
137 for segment in path {
138 PermissionSegment::parse(segment.to_string()).map_err(|source| {
139 CommandRegistrationError::InvalidSubcommandPermissionPath {
140 id: self.id.clone(),
141 path: display_permission_path(path),
142 source,
143 }
144 })?;
145 }
146 let path = display_permission_path(path);
147 if !permission_paths.insert(path.clone()) {
148 return Err(
149 CommandRegistrationError::DuplicateSubcommandPermissionPath {
150 id: self.id.clone(),
151 path,
152 },
153 );
154 }
155 }
156 Ok(())
157 }
158}
159
160pub(crate) struct CommandDispatcherBuilder<S>
162where
163 S: CommandPermissionSource,
164{
165 registrations: Vec<CommandRegistration<S>>,
166 ids: FxHashSet<Identifier>,
167 declared_permissions: BTreeSet<PermissionKey>,
168}
169
170pub(crate) struct RegisteredCommandDispatcher<S>
172where
173 S: CommandPermissionSource,
174{
175 pub(crate) dispatcher: CommandDispatcher<S, SteelCommandRuntime>,
176 pub(crate) permissions: Vec<PermissionKey>,
177}
178
179impl<S> CommandDispatcherBuilder<S>
180where
181 S: CommandPermissionSource,
182{
183 pub(crate) fn new() -> Self {
184 Self {
185 registrations: Vec::new(),
186 ids: FxHashSet::default(),
187 declared_permissions: BTreeSet::new(),
188 }
189 }
190
191 pub(crate) fn declare_permission(
193 &mut self,
194 permission: impl Into<String>,
195 ) -> Result<(), CommandRegistrationError> {
196 let value = permission.into();
197 let permission = PermissionKey::parse(value.clone()).map_err(|source| {
198 CommandRegistrationError::InvalidInternalPermission { value, source }
199 })?;
200 self.declared_permissions.insert(permission);
201 Ok(())
202 }
203
204 pub(crate) fn register(
206 &mut self,
207 registration: CommandRegistration<S>,
208 ) -> Result<(), CommandRegistrationError> {
209 registration.validate()?;
210 if !self.ids.insert(registration.id.clone()) {
211 return Err(CommandRegistrationError::DuplicateCommandId(
212 registration.id,
213 ));
214 }
215 self.registrations.push(registration);
216 Ok(())
217 }
218
219 pub(crate) fn extend(&mut self, other: Self) -> Result<(), CommandRegistrationError> {
220 let Self {
221 registrations,
222 declared_permissions,
223 ..
224 } = other;
225 self.declared_permissions.extend(declared_permissions);
226 for registration in registrations {
227 self.register(registration)?;
228 }
229 Ok(())
230 }
231
232 #[cfg(test)]
234 pub(crate) fn build(
235 self,
236 ) -> Result<CommandDispatcher<S, SteelCommandRuntime>, CommandRegistrationError> {
237 self.build_with_permissions()
238 .map(|registered| registered.dispatcher)
239 }
240
241 pub(crate) fn build_with_permissions(
243 self,
244 ) -> Result<RegisteredCommandDispatcher<S>, CommandRegistrationError> {
245 let mut dispatcher = CommandDispatcher::new();
246 let dispatcher_root = dispatcher.root();
247 let mut resolved = Vec::with_capacity(self.registrations.len());
248 let mut permissions = self.declared_permissions;
249
250 for registration in self.registrations {
251 let CommandRegistration {
252 id,
253 aliases,
254 permission,
255 subcommand_permissions,
256 default_access,
257 factory,
258 } = registration;
259 let (root_permission, derived_root) = if let Some(permission) = permission {
260 (permission, None)
261 } else {
262 let key = derived_command_permission_key(&id)?;
263 (PermissionExpr::key(key.clone()), Some(key))
264 };
265 collect_permission_keys(&root_permission, &mut permissions);
266 let root = apply_registration_requirements(
267 factory(dispatcher_root),
268 &id,
269 root_permission,
270 derived_root.as_ref(),
271 &subcommand_permissions,
272 default_access,
273 &mut permissions,
274 )?;
275 let Some(root_name) = root.literal_name() else {
276 return Err(CommandRegistrationError::RootMustBeLiteral { id });
277 };
278 if root_name != id.path {
279 return Err(CommandRegistrationError::RootDoesNotMatchId {
280 id,
281 root: root_name.into(),
282 });
283 }
284 resolved.push(ResolvedCommand { id, aliases, root });
285 }
286
287 let mut claim_counts = FxHashMap::<Box<str>, usize>::default();
288 for command in &resolved {
289 for root in command.roots() {
290 *claim_counts.entry(root.into()).or_default() += 1;
291 }
292 }
293
294 let mut claimed_roots = FxHashSet::<Box<str>>::default();
295 for command in &resolved {
296 for root in command.roots() {
297 if !claimed_roots.insert(root.into()) {
298 continue;
299 }
300 register_renamed_root(&mut dispatcher, &command.root, root)?;
301 }
302 }
303
304 for command in &resolved {
305 let collided = command
306 .roots()
307 .any(|root| claim_counts.get(root).is_some_and(|count| *count > 1));
308 if collided {
309 register_renamed_root(&mut dispatcher, &command.root, command.id.to_string())?;
310 }
311 }
312
313 Ok(RegisteredCommandDispatcher {
314 dispatcher,
315 permissions: permissions.into_iter().collect(),
316 })
317 }
318}
319
320fn apply_registration_requirements<S>(
321 mut root: CommandNodeBuilder<S, SteelCommandRuntime>,
322 id: &Identifier,
323 root_permission: PermissionExpr,
324 derived_root: Option<&PermissionKey>,
325 subcommand_permissions: &[Vec<Box<str>>],
326 default_access: bool,
327 permissions: &mut BTreeSet<PermissionKey>,
328) -> Result<CommandNodeBuilder<S, SteelCommandRuntime>, CommandRegistrationError>
329where
330 S: CommandPermissionSource,
331{
332 if subcommand_permissions.is_empty() {
333 return Ok(root.also_requires(root_permission_requirement(
334 root_permission,
335 Vec::new(),
336 default_access,
337 )));
338 }
339 let Some(derived_root) = derived_root else {
340 return Err(
341 CommandRegistrationError::SubcommandPermissionsRequireDerivedRoot { id: id.clone() },
342 );
343 };
344
345 let mut scoped_permissions = Vec::with_capacity(subcommand_permissions.len());
346 for path in subcommand_permissions {
347 let permission = derived_subcommand_permission(id, derived_root, path)?;
348 permissions.insert(permission.clone());
349 match root.literal_path_match_count(path) {
350 1 => scoped_permissions.push(permission),
351 0 => {
352 return Err(CommandRegistrationError::MissingSubcommandPermissionPath {
353 id: id.clone(),
354 path: display_permission_path(path),
355 });
356 }
357 matches => {
358 return Err(
359 CommandRegistrationError::AmbiguousSubcommandPermissionPath {
360 id: id.clone(),
361 path: display_permission_path(path),
362 matches,
363 },
364 );
365 }
366 }
367 }
368 root.apply_scoped_requirements(
369 subcommand_permissions,
370 |governing_scope, descendant_scopes| {
371 let descendants = descendant_scopes
372 .iter()
373 .map(|index| scoped_permissions[*index].clone())
374 .collect::<Vec<_>>();
375 let traversal = if let Some(index) = governing_scope {
376 scoped_permission_requirement(derived_root, &scoped_permissions[index], descendants)
377 } else {
378 root_permission_requirement(root_permission.clone(), descendants, default_access)
379 };
380 let execution = if let Some(index) = governing_scope {
381 scoped_permission_requirement(derived_root, &scoped_permissions[index], Vec::new())
382 } else {
383 root_permission_requirement(root_permission.clone(), Vec::new(), default_access)
384 };
385 CommandRequirementRoute::new(traversal, execution)
386 },
387 );
388 Ok(root)
389}
390
391impl<S> Default for CommandDispatcherBuilder<S>
392where
393 S: CommandPermissionSource,
394{
395 fn default() -> Self {
396 Self::new()
397 }
398}
399
400struct ResolvedCommand<S>
401where
402 S: CommandPermissionSource,
403{
404 id: Identifier,
405 aliases: Vec<Box<str>>,
406 root: CommandNodeBuilder<S, SteelCommandRuntime>,
407}
408
409impl<S> ResolvedCommand<S>
410where
411 S: CommandPermissionSource,
412{
413 fn roots(&self) -> impl Iterator<Item = &str> {
414 once(self.id.path.as_ref()).chain(self.aliases.iter().map(AsRef::as_ref))
415 }
416}
417
418fn derived_command_permission_key(
419 id: &Identifier,
420) -> Result<PermissionKey, CommandRegistrationError> {
421 PermissionKey::parse(format!("{}.command.{}", id.namespace, id.path)).map_err(|source| {
422 CommandRegistrationError::InvalidDerivedPermission {
423 id: id.clone(),
424 source,
425 }
426 })
427}
428
429fn derived_subcommand_permission(
430 id: &Identifier,
431 root: &PermissionKey,
432 path: &[Box<str>],
433) -> Result<PermissionKey, CommandRegistrationError> {
434 let mut permission = root.clone();
435 for segment in path {
436 let segment = PermissionSegment::parse(segment.to_string()).map_err(|source| {
437 CommandRegistrationError::InvalidSubcommandPermissionPath {
438 id: id.clone(),
439 path: display_permission_path(path),
440 source,
441 }
442 })?;
443 permission = permission.child(&segment).map_err(|source| {
444 CommandRegistrationError::InvalidSubcommandPermissionPath {
445 id: id.clone(),
446 path: display_permission_path(path),
447 source,
448 }
449 })?;
450 }
451 Ok(permission)
452}
453
454fn root_permission_requirement<S>(
455 root: PermissionExpr,
456 alternatives: Vec<PermissionKey>,
457 default_access: bool,
458) -> CommandRequirement<S>
459where
460 S: CommandPermissionSource,
461{
462 if default_access {
463 let alternatives = if alternatives.is_empty() {
464 None
465 } else {
466 Some(PermissionExpr::Any(
467 alternatives.into_iter().map(PermissionExpr::key).collect(),
468 ))
469 };
470 return CommandRequirement::contextual(move |source: &S| {
471 source.permission_state(&root) != Some(PermissionState::Deny)
472 || alternatives.as_ref().is_some_and(|alternatives| {
473 source.permission_state(alternatives) == Some(PermissionState::Allow)
474 })
475 });
476 }
477
478 let permission = alternatives
479 .into_iter()
480 .fold(root, |permission, alternative| {
481 permission | PermissionExpr::key(alternative)
482 });
483 permission_requirement(permission)
484}
485
486fn permission_requirement<S>(permission: PermissionExpr) -> CommandRequirement<S>
487where
488 S: CommandPermissionSource,
489{
490 CommandRequirement::authorization(move |source: &S| {
491 source.permission_state(&permission) == Some(PermissionState::Allow)
492 })
493}
494
495fn scoped_permission_requirement<S>(
496 root: &PermissionKey,
497 scoped: &PermissionKey,
498 alternatives: Vec<PermissionKey>,
499) -> CommandRequirement<S>
500where
501 S: CommandPermissionSource,
502{
503 let permission = alternatives.into_iter().fold(
504 PermissionExpr::scoped_key(root.clone(), scoped.clone()),
505 |permission, alternative| permission | PermissionExpr::key(alternative),
506 );
507 permission_requirement(permission)
508}
509
510fn collect_permission_keys(expression: &PermissionExpr, keys: &mut BTreeSet<PermissionKey>) {
511 match expression {
512 PermissionExpr::Key(key) => {
513 keys.insert(key.clone());
514 }
515 PermissionExpr::ScopedKey { parent, key } => {
516 keys.insert(parent.clone());
517 keys.insert(key.clone());
518 }
519 PermissionExpr::All(expressions) | PermissionExpr::Any(expressions) => {
520 for expression in expressions {
521 collect_permission_keys(expression, keys);
522 }
523 }
524 }
525}
526
527fn display_permission_path(path: &[Box<str>]) -> String {
528 path.iter()
529 .map(AsRef::as_ref)
530 .collect::<Vec<&str>>()
531 .join(".")
532}
533
534fn register_renamed_root<S>(
535 dispatcher: &mut CommandDispatcher<S, SteelCommandRuntime>,
536 root: &CommandNodeBuilder<S, SteelCommandRuntime>,
537 name: impl Into<Box<str>>,
538) -> Result<(), CommandRegistrationError>
539where
540 S: CommandPermissionSource,
541{
542 let renamed = root
543 .clone()
544 .with_literal_name(name)
545 .ok_or(CommandRegistrationError::UnexpectedArgumentRoot)?;
546 dispatcher.register(renamed)?;
547 Ok(())
548}
549
550fn validate_alias(alias: &str) -> Result<(), CommandRegistrationError> {
551 if alias.is_empty() {
552 return Err(CommandRegistrationError::EmptyAlias);
553 }
554 if alias.chars().any(char::is_whitespace) {
555 return Err(CommandRegistrationError::AliasContainsWhitespace(
556 alias.into(),
557 ));
558 }
559 if alias.contains(':') {
560 return Err(CommandRegistrationError::NamespacedAlias(alias.into()));
561 }
562 Ok(())
563}
564
565#[derive(Debug, Error)]
567pub(crate) enum CommandRegistrationError {
568 #[error("invalid command id '{0}'")]
569 InvalidCommandId(Identifier),
570 #[error("command id '{0}' is already registered")]
571 DuplicateCommandId(Identifier),
572 #[error("command '{id}' claims root '{root}' more than once")]
573 DuplicateOwnedRoot { id: Identifier, root: Box<str> },
574 #[error("command '{id}' must produce a literal root")]
575 RootMustBeLiteral { id: Identifier },
576 #[error("command '{id}' produced root '{root}' instead of its id path")]
577 RootDoesNotMatchId { id: Identifier, root: Box<str> },
578 #[error("command alias cannot be empty")]
579 EmptyAlias,
580 #[error("command alias '{0}' cannot contain whitespace")]
581 AliasContainsWhitespace(Box<str>),
582 #[error("command alias '{0}' cannot be namespaced")]
583 NamespacedAlias(Box<str>),
584 #[error("command '{id}' cannot derive a permission from its id: {source}")]
585 InvalidDerivedPermission {
586 id: Identifier,
587 #[source]
588 source: PermissionKeyError,
589 },
590 #[error("invalid internal permission declaration '{value}': {source}")]
591 InvalidInternalPermission {
592 value: String,
593 #[source]
594 source: PermissionKeyError,
595 },
596 #[error("command '{id}' has an invalid explicit permission: {source}")]
597 InvalidExplicitPermission {
598 id: Identifier,
599 #[source]
600 source: PermissionKeyError,
601 },
602 #[error("command '{id}' cannot combine explicit and derived subcommand permissions")]
603 SubcommandPermissionsRequireDerivedRoot { id: Identifier },
604 #[error("command '{id}' has an empty subcommand permission path")]
605 EmptySubcommandPermissionPath { id: Identifier },
606 #[error("command '{id}' has invalid subcommand permission path '{path}': {source}")]
607 InvalidSubcommandPermissionPath {
608 id: Identifier,
609 path: String,
610 #[source]
611 source: PermissionKeyError,
612 },
613 #[error("command '{id}' declares subcommand permission path '{path}' more than once")]
614 DuplicateSubcommandPermissionPath { id: Identifier, path: String },
615 #[error("command '{id}' has no literal at subcommand permission path '{path}'")]
616 MissingSubcommandPermissionPath { id: Identifier, path: String },
617 #[error("command '{id}' has {matches} literals at subcommand permission path '{path}'")]
618 AmbiguousSubcommandPermissionPath {
619 id: Identifier,
620 path: String,
621 matches: usize,
622 },
623 #[error("a validated command root unexpectedly became an argument")]
624 UnexpectedArgumentRoot,
625 #[error(transparent)]
626 InvalidGraph(#[from] RegistrationError),
627}
628
629#[cfg(test)]
630mod tests;