1use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use std::fmt;
10use std::str::FromStr;
11use std::sync::LazyLock;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34pub enum WamiAction {
35 All,
38 ServiceAll(WamiServicePrefix),
40
41 PlatformAdmin,
44 PlatformViewSettings,
46 PlatformUpdateSettings,
48 PlatformViewAuditLog,
50
51 SpaceCreate,
54 SpaceDelete,
56 SpaceRead,
58 SpaceUpdate,
60 SpaceConfigure,
62 SpaceManageMembers,
64 SpaceSuspend,
66 SpaceList,
68
69 TenantRead,
72 TenantUpdate,
74 TenantDelete,
76 TenantCreateSubTenant,
78 TenantManageUsers,
80 TenantManageRoles,
82 TenantManagePolicies,
84
85 IamCreateUser,
88 IamDeleteUser,
90 IamReadUser,
92 IamUpdateUser,
94 IamListUsers,
96 IamCreateGroup,
98 IamDeleteGroup,
100 IamManageGroupMembers,
102 IamCreateRole,
104 IamDeleteRole,
106 IamReadRole,
108 IamAssumeRole,
110 IamCreatePolicy,
112 IamDeletePolicy,
114 IamReadPolicy,
116 IamAttachPolicy,
118 IamDetachPolicy,
120 IamSetBoundary,
122 IamManageCredentials,
124
125 DbQuery,
128 DbWrite,
130 DbDelete,
132 DbCreate,
134 DbDrop,
136 DbList,
138 DbConfigureAccess,
140 DbImport,
142 DbExport,
144
145 ChatSend,
148 ChatReadHistory,
150 ChatDeleteConversation,
152 ChatStream,
154
155 PersonaCreate,
158 PersonaDelete,
160 PersonaRead,
162 PersonaUpdate,
164 PersonaList,
166 PersonaInvoke,
168
169 RoomCreate,
172 RoomDelete,
174 RoomJoin,
176 RoomRead,
178 RoomSend,
180 RoomManage,
182
183 InferenceListModels,
186 InferenceInvoke,
188 InferenceConfigureRouter,
190 InferenceViewUsage,
192
193 AnalyticsViewUsage,
196 AnalyticsViewConversations,
198 AnalyticsExport,
200 AnalyticsViewActivity,
202
203 IntegrationCreate,
206 IntegrationDelete,
208 IntegrationConfigure,
210 IntegrationList,
212 IntegrationReceive,
214 IntegrationSend,
216
217 CognitiveRead,
220 CognitiveWrite,
222 CognitiveReset,
224
225 GdprGrantConsent,
228 GdprRevokeConsent,
230 GdprExportData,
232 GdprEraseData,
234 GdprViewAudit,
236}
237
238#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
244pub enum WamiServicePrefix {
245 Platform,
246 Space,
247 Tenant,
248 Iam,
249 Db,
250 Chat,
251 Persona,
252 Room,
253 Inference,
254 Analytics,
255 Integration,
256 Cognitive,
257 Gdpr,
258}
259
260impl WamiServicePrefix {
261 pub fn as_str(&self) -> &'static str {
262 match self {
263 Self::Platform => "platform",
264 Self::Space => "space",
265 Self::Tenant => "tenant",
266 Self::Iam => "iam",
267 Self::Db => "db",
268 Self::Chat => "chat",
269 Self::Persona => "persona",
270 Self::Room => "room",
271 Self::Inference => "inference",
272 Self::Analytics => "analytics",
273 Self::Integration => "integration",
274 Self::Cognitive => "cognitive",
275 Self::Gdpr => "gdpr",
276 }
277 }
278
279 pub fn all() -> &'static [WamiServicePrefix] {
280 &[
281 Self::Platform,
282 Self::Space,
283 Self::Tenant,
284 Self::Iam,
285 Self::Db,
286 Self::Chat,
287 Self::Persona,
288 Self::Room,
289 Self::Inference,
290 Self::Analytics,
291 Self::Integration,
292 Self::Cognitive,
293 Self::Gdpr,
294 ]
295 }
296}
297
298impl FromStr for WamiServicePrefix {
299 type Err = ActionParseError;
300
301 fn from_str(s: &str) -> Result<Self, Self::Err> {
302 match s {
303 "platform" => Ok(Self::Platform),
304 "space" => Ok(Self::Space),
305 "tenant" => Ok(Self::Tenant),
306 "iam" => Ok(Self::Iam),
307 "db" => Ok(Self::Db),
308 "chat" => Ok(Self::Chat),
309 "persona" => Ok(Self::Persona),
310 "room" => Ok(Self::Room),
311 "inference" => Ok(Self::Inference),
312 "analytics" => Ok(Self::Analytics),
313 "integration" => Ok(Self::Integration),
314 "cognitive" => Ok(Self::Cognitive),
315 "gdpr" => Ok(Self::Gdpr),
316 _ => Err(ActionParseError::UnknownPrefix(s.to_string())),
317 }
318 }
319}
320
321impl fmt::Display for WamiServicePrefix {
322 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323 f.write_str(self.as_str())
324 }
325}
326
327impl fmt::Display for WamiAction {
332 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333 f.write_str(self.as_str())
334 }
335}
336
337impl WamiAction {
338 pub fn as_str(&self) -> &'static str {
340 match self {
341 Self::All => "*",
343 Self::ServiceAll(prefix) => match prefix {
344 WamiServicePrefix::Platform => "platform:*",
345 WamiServicePrefix::Space => "space:*",
346 WamiServicePrefix::Tenant => "tenant:*",
347 WamiServicePrefix::Iam => "iam:*",
348 WamiServicePrefix::Db => "db:*",
349 WamiServicePrefix::Chat => "chat:*",
350 WamiServicePrefix::Persona => "persona:*",
351 WamiServicePrefix::Room => "room:*",
352 WamiServicePrefix::Inference => "inference:*",
353 WamiServicePrefix::Analytics => "analytics:*",
354 WamiServicePrefix::Integration => "integration:*",
355 WamiServicePrefix::Cognitive => "cognitive:*",
356 WamiServicePrefix::Gdpr => "gdpr:*",
357 },
358 Self::PlatformAdmin => "platform:Admin",
360 Self::PlatformViewSettings => "platform:ViewSettings",
361 Self::PlatformUpdateSettings => "platform:UpdateSettings",
362 Self::PlatformViewAuditLog => "platform:ViewAuditLog",
363 Self::SpaceCreate => "space:Create",
365 Self::SpaceDelete => "space:Delete",
366 Self::SpaceRead => "space:Read",
367 Self::SpaceUpdate => "space:Update",
368 Self::SpaceConfigure => "space:Configure",
369 Self::SpaceManageMembers => "space:ManageMembers",
370 Self::SpaceSuspend => "space:Suspend",
371 Self::SpaceList => "space:List",
372 Self::TenantRead => "tenant:Read",
374 Self::TenantUpdate => "tenant:Update",
375 Self::TenantDelete => "tenant:Delete",
376 Self::TenantCreateSubTenant => "tenant:CreateSubTenant",
377 Self::TenantManageUsers => "tenant:ManageUsers",
378 Self::TenantManageRoles => "tenant:ManageRoles",
379 Self::TenantManagePolicies => "tenant:ManagePolicies",
380 Self::IamCreateUser => "iam:CreateUser",
382 Self::IamDeleteUser => "iam:DeleteUser",
383 Self::IamReadUser => "iam:ReadUser",
384 Self::IamUpdateUser => "iam:UpdateUser",
385 Self::IamListUsers => "iam:ListUsers",
386 Self::IamCreateGroup => "iam:CreateGroup",
387 Self::IamDeleteGroup => "iam:DeleteGroup",
388 Self::IamManageGroupMembers => "iam:ManageGroupMembers",
389 Self::IamCreateRole => "iam:CreateRole",
390 Self::IamDeleteRole => "iam:DeleteRole",
391 Self::IamReadRole => "iam:ReadRole",
392 Self::IamAssumeRole => "iam:AssumeRole",
393 Self::IamCreatePolicy => "iam:CreatePolicy",
394 Self::IamDeletePolicy => "iam:DeletePolicy",
395 Self::IamReadPolicy => "iam:ReadPolicy",
396 Self::IamAttachPolicy => "iam:AttachPolicy",
397 Self::IamDetachPolicy => "iam:DetachPolicy",
398 Self::IamSetBoundary => "iam:SetBoundary",
399 Self::IamManageCredentials => "iam:ManageCredentials",
400 Self::DbQuery => "db:Query",
402 Self::DbWrite => "db:Write",
403 Self::DbDelete => "db:Delete",
404 Self::DbCreate => "db:Create",
405 Self::DbDrop => "db:Drop",
406 Self::DbList => "db:List",
407 Self::DbConfigureAccess => "db:ConfigureAccess",
408 Self::DbImport => "db:Import",
409 Self::DbExport => "db:Export",
410 Self::ChatSend => "chat:Send",
412 Self::ChatReadHistory => "chat:ReadHistory",
413 Self::ChatDeleteConversation => "chat:DeleteConversation",
414 Self::ChatStream => "chat:Stream",
415 Self::PersonaCreate => "persona:Create",
417 Self::PersonaDelete => "persona:Delete",
418 Self::PersonaRead => "persona:Read",
419 Self::PersonaUpdate => "persona:Update",
420 Self::PersonaList => "persona:List",
421 Self::PersonaInvoke => "persona:Invoke",
422 Self::RoomCreate => "room:Create",
424 Self::RoomDelete => "room:Delete",
425 Self::RoomJoin => "room:Join",
426 Self::RoomRead => "room:Read",
427 Self::RoomSend => "room:Send",
428 Self::RoomManage => "room:Manage",
429 Self::InferenceListModels => "inference:ListModels",
431 Self::InferenceInvoke => "inference:Invoke",
432 Self::InferenceConfigureRouter => "inference:ConfigureRouter",
433 Self::InferenceViewUsage => "inference:ViewUsage",
434 Self::AnalyticsViewUsage => "analytics:ViewUsage",
436 Self::AnalyticsViewConversations => "analytics:ViewConversations",
437 Self::AnalyticsExport => "analytics:Export",
438 Self::AnalyticsViewActivity => "analytics:ViewActivity",
439 Self::IntegrationCreate => "integration:Create",
441 Self::IntegrationDelete => "integration:Delete",
442 Self::IntegrationConfigure => "integration:Configure",
443 Self::IntegrationList => "integration:List",
444 Self::IntegrationReceive => "integration:Receive",
445 Self::IntegrationSend => "integration:Send",
446 Self::CognitiveRead => "cognitive:Read",
448 Self::CognitiveWrite => "cognitive:Write",
449 Self::CognitiveReset => "cognitive:Reset",
450 Self::GdprGrantConsent => "gdpr:GrantConsent",
452 Self::GdprRevokeConsent => "gdpr:RevokeConsent",
453 Self::GdprExportData => "gdpr:ExportData",
454 Self::GdprEraseData => "gdpr:EraseData",
455 Self::GdprViewAudit => "gdpr:ViewAudit",
456 }
457 }
458
459 pub fn prefix(&self) -> Option<WamiServicePrefix> {
461 match self {
462 Self::All => None,
463 Self::ServiceAll(p) => Some(*p),
464 Self::PlatformAdmin
465 | Self::PlatformViewSettings
466 | Self::PlatformUpdateSettings
467 | Self::PlatformViewAuditLog => Some(WamiServicePrefix::Platform),
468 Self::SpaceCreate
469 | Self::SpaceDelete
470 | Self::SpaceRead
471 | Self::SpaceUpdate
472 | Self::SpaceConfigure
473 | Self::SpaceManageMembers
474 | Self::SpaceSuspend
475 | Self::SpaceList => Some(WamiServicePrefix::Space),
476 Self::TenantRead
477 | Self::TenantUpdate
478 | Self::TenantDelete
479 | Self::TenantCreateSubTenant
480 | Self::TenantManageUsers
481 | Self::TenantManageRoles
482 | Self::TenantManagePolicies => Some(WamiServicePrefix::Tenant),
483 Self::IamCreateUser
484 | Self::IamDeleteUser
485 | Self::IamReadUser
486 | Self::IamUpdateUser
487 | Self::IamListUsers
488 | Self::IamCreateGroup
489 | Self::IamDeleteGroup
490 | Self::IamManageGroupMembers
491 | Self::IamCreateRole
492 | Self::IamDeleteRole
493 | Self::IamReadRole
494 | Self::IamAssumeRole
495 | Self::IamCreatePolicy
496 | Self::IamDeletePolicy
497 | Self::IamReadPolicy
498 | Self::IamAttachPolicy
499 | Self::IamDetachPolicy
500 | Self::IamSetBoundary
501 | Self::IamManageCredentials => Some(WamiServicePrefix::Iam),
502 Self::DbQuery
503 | Self::DbWrite
504 | Self::DbDelete
505 | Self::DbCreate
506 | Self::DbDrop
507 | Self::DbList
508 | Self::DbConfigureAccess
509 | Self::DbImport
510 | Self::DbExport => Some(WamiServicePrefix::Db),
511 Self::ChatSend
512 | Self::ChatReadHistory
513 | Self::ChatDeleteConversation
514 | Self::ChatStream => Some(WamiServicePrefix::Chat),
515 Self::PersonaCreate
516 | Self::PersonaDelete
517 | Self::PersonaRead
518 | Self::PersonaUpdate
519 | Self::PersonaList
520 | Self::PersonaInvoke => Some(WamiServicePrefix::Persona),
521 Self::RoomCreate
522 | Self::RoomDelete
523 | Self::RoomJoin
524 | Self::RoomRead
525 | Self::RoomSend
526 | Self::RoomManage => Some(WamiServicePrefix::Room),
527 Self::InferenceListModels
528 | Self::InferenceInvoke
529 | Self::InferenceConfigureRouter
530 | Self::InferenceViewUsage => Some(WamiServicePrefix::Inference),
531 Self::AnalyticsViewUsage
532 | Self::AnalyticsViewConversations
533 | Self::AnalyticsExport
534 | Self::AnalyticsViewActivity => Some(WamiServicePrefix::Analytics),
535 Self::IntegrationCreate
536 | Self::IntegrationDelete
537 | Self::IntegrationConfigure
538 | Self::IntegrationList
539 | Self::IntegrationReceive
540 | Self::IntegrationSend => Some(WamiServicePrefix::Integration),
541 Self::CognitiveRead | Self::CognitiveWrite | Self::CognitiveReset => {
542 Some(WamiServicePrefix::Cognitive)
543 }
544 Self::GdprGrantConsent
545 | Self::GdprRevokeConsent
546 | Self::GdprExportData
547 | Self::GdprEraseData
548 | Self::GdprViewAudit => Some(WamiServicePrefix::Gdpr),
549 }
550 }
551}
552
553#[derive(Debug, Clone, thiserror::Error)]
558pub enum ActionParseError {
559 #[error("unknown action: {0}")]
560 Unknown(String),
561 #[error("unknown service prefix: {0}")]
562 UnknownPrefix(String),
563 #[error("invalid action format (expected 'service:Operation' or '*'): {0}")]
564 InvalidFormat(String),
565}
566
567impl FromStr for WamiAction {
572 type Err = ActionParseError;
573
574 fn from_str(s: &str) -> Result<Self, Self::Err> {
575 if s == "*" {
577 return Ok(Self::All);
578 }
579
580 let Some((prefix_str, operation)) = s.split_once(':') else {
582 return Err(ActionParseError::InvalidFormat(s.to_string()));
583 };
584
585 if operation == "*" {
587 let prefix = WamiServicePrefix::from_str(prefix_str)?;
588 return Ok(Self::ServiceAll(prefix));
589 }
590
591 match (prefix_str, operation) {
593 ("platform", "Admin") => Ok(Self::PlatformAdmin),
595 ("platform", "ViewSettings") => Ok(Self::PlatformViewSettings),
596 ("platform", "UpdateSettings") => Ok(Self::PlatformUpdateSettings),
597 ("platform", "ViewAuditLog") => Ok(Self::PlatformViewAuditLog),
598 ("space", "Create") => Ok(Self::SpaceCreate),
600 ("space", "Delete") => Ok(Self::SpaceDelete),
601 ("space", "Read") => Ok(Self::SpaceRead),
602 ("space", "Update") => Ok(Self::SpaceUpdate),
603 ("space", "Configure") => Ok(Self::SpaceConfigure),
604 ("space", "ManageMembers") => Ok(Self::SpaceManageMembers),
605 ("space", "Suspend") => Ok(Self::SpaceSuspend),
606 ("space", "List") => Ok(Self::SpaceList),
607 ("tenant", "Read") => Ok(Self::TenantRead),
609 ("tenant", "Update") => Ok(Self::TenantUpdate),
610 ("tenant", "Delete") => Ok(Self::TenantDelete),
611 ("tenant", "CreateSubTenant") => Ok(Self::TenantCreateSubTenant),
612 ("tenant", "ManageUsers") => Ok(Self::TenantManageUsers),
613 ("tenant", "ManageRoles") => Ok(Self::TenantManageRoles),
614 ("tenant", "ManagePolicies") => Ok(Self::TenantManagePolicies),
615 ("iam", "CreateUser") => Ok(Self::IamCreateUser),
617 ("iam", "DeleteUser") => Ok(Self::IamDeleteUser),
618 ("iam", "ReadUser") => Ok(Self::IamReadUser),
619 ("iam", "UpdateUser") => Ok(Self::IamUpdateUser),
620 ("iam", "ListUsers") => Ok(Self::IamListUsers),
621 ("iam", "CreateGroup") => Ok(Self::IamCreateGroup),
622 ("iam", "DeleteGroup") => Ok(Self::IamDeleteGroup),
623 ("iam", "ManageGroupMembers") => Ok(Self::IamManageGroupMembers),
624 ("iam", "CreateRole") => Ok(Self::IamCreateRole),
625 ("iam", "DeleteRole") => Ok(Self::IamDeleteRole),
626 ("iam", "ReadRole") => Ok(Self::IamReadRole),
627 ("iam", "AssumeRole") => Ok(Self::IamAssumeRole),
628 ("iam", "CreatePolicy") => Ok(Self::IamCreatePolicy),
629 ("iam", "DeletePolicy") => Ok(Self::IamDeletePolicy),
630 ("iam", "ReadPolicy") => Ok(Self::IamReadPolicy),
631 ("iam", "AttachPolicy") => Ok(Self::IamAttachPolicy),
632 ("iam", "DetachPolicy") => Ok(Self::IamDetachPolicy),
633 ("iam", "SetBoundary") => Ok(Self::IamSetBoundary),
634 ("iam", "ManageCredentials") => Ok(Self::IamManageCredentials),
635 ("db", "Query") => Ok(Self::DbQuery),
637 ("db", "Write") => Ok(Self::DbWrite),
638 ("db", "Delete") => Ok(Self::DbDelete),
639 ("db", "Create") => Ok(Self::DbCreate),
640 ("db", "Drop") => Ok(Self::DbDrop),
641 ("db", "List") => Ok(Self::DbList),
642 ("db", "ConfigureAccess") => Ok(Self::DbConfigureAccess),
643 ("db", "Import") => Ok(Self::DbImport),
644 ("db", "Export") => Ok(Self::DbExport),
645 ("chat", "Send") => Ok(Self::ChatSend),
647 ("chat", "ReadHistory") => Ok(Self::ChatReadHistory),
648 ("chat", "DeleteConversation") => Ok(Self::ChatDeleteConversation),
649 ("chat", "Stream") => Ok(Self::ChatStream),
650 ("persona", "Create") => Ok(Self::PersonaCreate),
652 ("persona", "Delete") => Ok(Self::PersonaDelete),
653 ("persona", "Read") => Ok(Self::PersonaRead),
654 ("persona", "Update") => Ok(Self::PersonaUpdate),
655 ("persona", "List") => Ok(Self::PersonaList),
656 ("persona", "Invoke") => Ok(Self::PersonaInvoke),
657 ("room", "Create") => Ok(Self::RoomCreate),
659 ("room", "Delete") => Ok(Self::RoomDelete),
660 ("room", "Join") => Ok(Self::RoomJoin),
661 ("room", "Read") => Ok(Self::RoomRead),
662 ("room", "Send") => Ok(Self::RoomSend),
663 ("room", "Manage") => Ok(Self::RoomManage),
664 ("inference", "ListModels") => Ok(Self::InferenceListModels),
666 ("inference", "Invoke") => Ok(Self::InferenceInvoke),
667 ("inference", "ConfigureRouter") => Ok(Self::InferenceConfigureRouter),
668 ("inference", "ViewUsage") => Ok(Self::InferenceViewUsage),
669 ("analytics", "ViewUsage") => Ok(Self::AnalyticsViewUsage),
671 ("analytics", "ViewConversations") => Ok(Self::AnalyticsViewConversations),
672 ("analytics", "Export") => Ok(Self::AnalyticsExport),
673 ("analytics", "ViewActivity") => Ok(Self::AnalyticsViewActivity),
674 ("integration", "Create") => Ok(Self::IntegrationCreate),
676 ("integration", "Delete") => Ok(Self::IntegrationDelete),
677 ("integration", "Configure") => Ok(Self::IntegrationConfigure),
678 ("integration", "List") => Ok(Self::IntegrationList),
679 ("integration", "Receive") => Ok(Self::IntegrationReceive),
680 ("integration", "Send") => Ok(Self::IntegrationSend),
681 ("cognitive", "Read") => Ok(Self::CognitiveRead),
683 ("cognitive", "Write") => Ok(Self::CognitiveWrite),
684 ("cognitive", "Reset") => Ok(Self::CognitiveReset),
685 ("gdpr", "GrantConsent") => Ok(Self::GdprGrantConsent),
687 ("gdpr", "RevokeConsent") => Ok(Self::GdprRevokeConsent),
688 ("gdpr", "ExportData") => Ok(Self::GdprExportData),
689 ("gdpr", "EraseData") => Ok(Self::GdprEraseData),
690 ("gdpr", "ViewAudit") => Ok(Self::GdprViewAudit),
691 _ => Err(ActionParseError::Unknown(s.to_string())),
692 }
693 }
694}
695
696impl Serialize for WamiAction {
701 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
702 serializer.serialize_str(self.as_str())
703 }
704}
705
706impl<'de> Deserialize<'de> for WamiAction {
707 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
708 let s = String::deserialize(deserializer)?;
709 WamiAction::from_str(&s).map_err(serde::de::Error::custom)
710 }
711}
712
713impl WamiAction {
718 pub fn matches(&self, requested: &WamiAction) -> bool {
728 match self {
729 Self::All => true,
731 Self::ServiceAll(prefix) => requested.prefix() == Some(*prefix),
733 other => other == requested,
735 }
736 }
737
738 pub fn matches_str(policy_action: &str, requested: &str) -> bool {
745 if policy_action == "*" {
746 return true;
747 }
748 if policy_action == requested {
749 return true;
750 }
751 if let Some(prefix) = policy_action.strip_suffix(":*") {
753 if let Some(req_prefix) = requested.split_once(':').map(|(p, _)| p) {
754 return prefix == req_prefix;
755 }
756 }
757 false
758 }
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
767pub struct ActionInfo {
768 pub action: String,
770 pub description: String,
772 pub category: String,
774}
775
776pub struct ActionRegistry;
778
779impl ActionRegistry {
780 pub fn list_actions() -> &'static [ActionInfo] {
782 ®ISTRY
783 }
784
785 pub fn list_by_prefix(prefix: WamiServicePrefix) -> Vec<&'static ActionInfo> {
787 let prefix_str = prefix.as_str();
788 REGISTRY
789 .iter()
790 .filter(|info| info.category == prefix_str)
791 .collect()
792 }
793
794 pub fn list_prefixes() -> &'static [WamiServicePrefix] {
796 WamiServicePrefix::all()
797 }
798}
799
800static REGISTRY: LazyLock<Vec<ActionInfo>> = LazyLock::new(|| {
802 vec![
803 ai("platform:Admin", "Full platform administration", "platform"),
805 ai(
806 "platform:ViewSettings",
807 "View platform settings and stats",
808 "platform",
809 ),
810 ai(
811 "platform:UpdateSettings",
812 "Update platform configuration",
813 "platform",
814 ),
815 ai(
816 "platform:ViewAuditLog",
817 "View platform audit logs",
818 "platform",
819 ),
820 ai("space:Create", "Create a new Space", "space"),
822 ai("space:Delete", "Delete a Space", "space"),
823 ai("space:Read", "Read Space metadata", "space"),
824 ai("space:Update", "Update Space settings", "space"),
825 ai(
826 "space:Configure",
827 "Configure Space domain and port",
828 "space",
829 ),
830 ai("space:ManageMembers", "Manage Space members", "space"),
831 ai("space:Suspend", "Suspend / reactivate a Space", "space"),
832 ai("space:List", "List all Spaces", "space"),
833 ai("tenant:Read", "Read tenant info", "tenant"),
835 ai("tenant:Update", "Update tenant", "tenant"),
836 ai("tenant:Delete", "Delete tenant", "tenant"),
837 ai("tenant:CreateSubTenant", "Create sub-tenant", "tenant"),
838 ai("tenant:ManageUsers", "Manage users within tenant", "tenant"),
839 ai("tenant:ManageRoles", "Manage roles within tenant", "tenant"),
840 ai(
841 "tenant:ManagePolicies",
842 "Manage policies within tenant",
843 "tenant",
844 ),
845 ai("iam:CreateUser", "Create IAM user", "iam"),
847 ai("iam:DeleteUser", "Delete IAM user", "iam"),
848 ai("iam:ReadUser", "Read IAM user info", "iam"),
849 ai("iam:UpdateUser", "Update IAM user", "iam"),
850 ai("iam:ListUsers", "List IAM users", "iam"),
851 ai("iam:CreateGroup", "Create IAM group", "iam"),
852 ai("iam:DeleteGroup", "Delete IAM group", "iam"),
853 ai("iam:ManageGroupMembers", "Manage group membership", "iam"),
854 ai("iam:CreateRole", "Create IAM role", "iam"),
855 ai("iam:DeleteRole", "Delete IAM role", "iam"),
856 ai("iam:ReadRole", "Read IAM role", "iam"),
857 ai("iam:AssumeRole", "Assume IAM role", "iam"),
858 ai("iam:CreatePolicy", "Create IAM policy", "iam"),
859 ai("iam:DeletePolicy", "Delete IAM policy", "iam"),
860 ai("iam:ReadPolicy", "Read IAM policy", "iam"),
861 ai("iam:AttachPolicy", "Attach policy to entity", "iam"),
862 ai("iam:DetachPolicy", "Detach policy from entity", "iam"),
863 ai("iam:SetBoundary", "Set permissions boundary", "iam"),
864 ai("iam:ManageCredentials", "Manage credentials", "iam"),
865 ai("db:Query", "Query a knowledge database", "db"),
867 ai("db:Write", "Write to a knowledge database", "db"),
868 ai("db:Delete", "Delete data from database", "db"),
869 ai("db:Create", "Create a knowledge database", "db"),
870 ai("db:Drop", "Drop a knowledge database", "db"),
871 ai("db:List", "List available databases", "db"),
872 ai("db:ConfigureAccess", "Configure database access", "db"),
873 ai("db:Import", "Import data into database", "db"),
874 ai("db:Export", "Export data from database", "db"),
875 ai("chat:Send", "Send a chat message", "chat"),
877 ai("chat:ReadHistory", "Read chat history", "chat"),
878 ai("chat:DeleteConversation", "Delete a conversation", "chat"),
879 ai("chat:Stream", "Use streaming chat (SSE)", "chat"),
880 ai("persona:Create", "Create a persona", "persona"),
882 ai("persona:Delete", "Delete a persona", "persona"),
883 ai("persona:Read", "Read persona info", "persona"),
884 ai("persona:Update", "Update persona configuration", "persona"),
885 ai("persona:List", "List personas", "persona"),
886 ai("persona:Invoke", "Invoke a persona in chat", "persona"),
887 ai("room:Create", "Create a room", "room"),
889 ai("room:Delete", "Delete a room", "room"),
890 ai("room:Join", "Join a room", "room"),
891 ai("room:Read", "Read room messages", "room"),
892 ai("room:Send", "Send message to room", "room"),
893 ai("room:Manage", "Manage room settings", "room"),
894 ai("inference:ListModels", "List available models", "inference"),
896 ai("inference:Invoke", "Invoke a model", "inference"),
897 ai(
898 "inference:ConfigureRouter",
899 "Configure model routing",
900 "inference",
901 ),
902 ai("inference:ViewUsage", "View model usage", "inference"),
903 ai("analytics:ViewUsage", "View usage analytics", "analytics"),
905 ai(
906 "analytics:ViewConversations",
907 "View conversation analytics",
908 "analytics",
909 ),
910 ai("analytics:Export", "Export analytics reports", "analytics"),
911 ai("analytics:ViewActivity", "View user activity", "analytics"),
912 ai(
914 "integration:Create",
915 "Create integration bridge",
916 "integration",
917 ),
918 ai(
919 "integration:Delete",
920 "Delete integration bridge",
921 "integration",
922 ),
923 ai(
924 "integration:Configure",
925 "Configure integration",
926 "integration",
927 ),
928 ai("integration:List", "List integrations", "integration"),
929 ai(
930 "integration:Receive",
931 "Receive inbound messages",
932 "integration",
933 ),
934 ai("integration:Send", "Send outbound messages", "integration"),
935 ai("cognitive:Read", "Read cognitive state", "cognitive"),
937 ai("cognitive:Write", "Write cognitive state", "cognitive"),
938 ai("cognitive:Reset", "Reset cognitive state", "cognitive"),
939 ai("gdpr:GrantConsent", "Grant data consent", "gdpr"),
941 ai("gdpr:RevokeConsent", "Revoke data consent", "gdpr"),
942 ai("gdpr:ExportData", "Export personal data", "gdpr"),
943 ai("gdpr:EraseData", "Erase personal data", "gdpr"),
944 ai("gdpr:ViewAudit", "View GDPR audit log", "gdpr"),
945 ]
946});
947
948fn ai(action: &str, description: &str, category: &str) -> ActionInfo {
950 ActionInfo {
951 action: action.to_string(),
952 description: description.to_string(),
953 category: category.to_string(),
954 }
955}
956
957#[cfg(test)]
962mod tests {
963 use super::*;
964
965 #[test]
966 fn serde_roundtrip() {
967 let action = WamiAction::DbQuery;
968 let json = serde_json::to_string(&action).unwrap();
969 assert_eq!(json, r#""db:Query""#);
970
971 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
972 assert_eq!(parsed, WamiAction::DbQuery);
973 }
974
975 #[test]
976 fn serde_wildcard_roundtrip() {
977 let all = WamiAction::All;
978 assert_eq!(serde_json::to_string(&all).unwrap(), r#""*""#);
979
980 let svc = WamiAction::ServiceAll(WamiServicePrefix::Iam);
981 assert_eq!(serde_json::to_string(&svc).unwrap(), r#""iam:*""#);
982
983 let parsed: WamiAction = serde_json::from_str(r#""iam:*""#).unwrap();
984 assert_eq!(parsed, WamiAction::ServiceAll(WamiServicePrefix::Iam));
985 }
986
987 #[test]
988 fn from_str_roundtrip_all_actions() {
989 let actions = vec![
991 WamiAction::All,
992 WamiAction::ServiceAll(WamiServicePrefix::Db),
993 WamiAction::PlatformAdmin,
994 WamiAction::SpaceCreate,
995 WamiAction::TenantRead,
996 WamiAction::IamCreateUser,
997 WamiAction::DbQuery,
998 WamiAction::ChatSend,
999 WamiAction::PersonaInvoke,
1000 WamiAction::RoomCreate,
1001 WamiAction::InferenceInvoke,
1002 WamiAction::AnalyticsExport,
1003 WamiAction::IntegrationCreate,
1004 WamiAction::CognitiveRead,
1005 WamiAction::GdprEraseData,
1006 ];
1007 for action in actions {
1008 let s = action.to_string();
1009 let parsed = WamiAction::from_str(&s).unwrap_or_else(|e| {
1010 panic!("Failed to parse '{}': {}", s, e);
1011 });
1012 assert_eq!(parsed, action, "Roundtrip failed for {}", s);
1013 }
1014 }
1015
1016 #[test]
1017 fn from_str_errors() {
1018 assert!(WamiAction::from_str("invalid").is_err());
1019 assert!(WamiAction::from_str("unknown:Action").is_err());
1020 assert!(WamiAction::from_str("db:NonExistent").is_err());
1021 assert!(WamiAction::from_str("").is_err());
1022 }
1023
1024 #[test]
1025 fn wildcard_matching() {
1026 let all = WamiAction::All;
1027 let iam_all = WamiAction::ServiceAll(WamiServicePrefix::Iam);
1028 let create_user = WamiAction::IamCreateUser;
1029 let db_query = WamiAction::DbQuery;
1030
1031 assert!(all.matches(&create_user));
1033 assert!(all.matches(&db_query));
1034 assert!(all.matches(&iam_all));
1035
1036 assert!(iam_all.matches(&create_user));
1038 assert!(!iam_all.matches(&db_query));
1039
1040 assert!(create_user.matches(&create_user));
1042 assert!(!create_user.matches(&db_query));
1043 }
1044
1045 #[test]
1046 fn matches_str_backward_compat() {
1047 assert!(WamiAction::matches_str("*", "db:Query"));
1048 assert!(WamiAction::matches_str("db:*", "db:Query"));
1049 assert!(WamiAction::matches_str("db:Query", "db:Query"));
1050 assert!(!WamiAction::matches_str("db:*", "iam:CreateUser"));
1051 assert!(!WamiAction::matches_str("db:Query", "db:Write"));
1052 }
1053
1054 #[test]
1055 fn prefix_extraction() {
1056 assert_eq!(WamiAction::All.prefix(), None);
1057 assert_eq!(
1058 WamiAction::ServiceAll(WamiServicePrefix::Db).prefix(),
1059 Some(WamiServicePrefix::Db)
1060 );
1061 assert_eq!(WamiAction::DbQuery.prefix(), Some(WamiServicePrefix::Db));
1062 assert_eq!(
1063 WamiAction::IamCreateUser.prefix(),
1064 Some(WamiServicePrefix::Iam)
1065 );
1066 }
1067
1068 #[test]
1069 fn registry() {
1070 let all = ActionRegistry::list_actions();
1071 assert!(
1073 all.len() >= 70,
1074 "Expected at least 70 actions, got {}",
1075 all.len()
1076 );
1077
1078 let db_actions = ActionRegistry::list_by_prefix(WamiServicePrefix::Db);
1079 assert_eq!(db_actions.len(), 9);
1080 assert!(db_actions.iter().all(|a| a.category == "db"));
1081
1082 let iam_actions = ActionRegistry::list_by_prefix(WamiServicePrefix::Iam);
1083 assert_eq!(iam_actions.len(), 19);
1084 }
1085
1086 fn all_concrete_actions() -> Vec<WamiAction> {
1091 vec![
1092 WamiAction::PlatformAdmin,
1094 WamiAction::PlatformViewSettings,
1095 WamiAction::PlatformUpdateSettings,
1096 WamiAction::PlatformViewAuditLog,
1097 WamiAction::SpaceCreate,
1099 WamiAction::SpaceDelete,
1100 WamiAction::SpaceRead,
1101 WamiAction::SpaceUpdate,
1102 WamiAction::SpaceConfigure,
1103 WamiAction::SpaceManageMembers,
1104 WamiAction::SpaceSuspend,
1105 WamiAction::SpaceList,
1106 WamiAction::TenantRead,
1108 WamiAction::TenantUpdate,
1109 WamiAction::TenantDelete,
1110 WamiAction::TenantCreateSubTenant,
1111 WamiAction::TenantManageUsers,
1112 WamiAction::TenantManageRoles,
1113 WamiAction::TenantManagePolicies,
1114 WamiAction::IamCreateUser,
1116 WamiAction::IamDeleteUser,
1117 WamiAction::IamReadUser,
1118 WamiAction::IamUpdateUser,
1119 WamiAction::IamListUsers,
1120 WamiAction::IamCreateGroup,
1121 WamiAction::IamDeleteGroup,
1122 WamiAction::IamManageGroupMembers,
1123 WamiAction::IamCreateRole,
1124 WamiAction::IamDeleteRole,
1125 WamiAction::IamReadRole,
1126 WamiAction::IamAssumeRole,
1127 WamiAction::IamCreatePolicy,
1128 WamiAction::IamDeletePolicy,
1129 WamiAction::IamReadPolicy,
1130 WamiAction::IamAttachPolicy,
1131 WamiAction::IamDetachPolicy,
1132 WamiAction::IamSetBoundary,
1133 WamiAction::IamManageCredentials,
1134 WamiAction::DbQuery,
1136 WamiAction::DbWrite,
1137 WamiAction::DbDelete,
1138 WamiAction::DbCreate,
1139 WamiAction::DbDrop,
1140 WamiAction::DbList,
1141 WamiAction::DbConfigureAccess,
1142 WamiAction::DbImport,
1143 WamiAction::DbExport,
1144 WamiAction::ChatSend,
1146 WamiAction::ChatReadHistory,
1147 WamiAction::ChatDeleteConversation,
1148 WamiAction::ChatStream,
1149 WamiAction::PersonaCreate,
1151 WamiAction::PersonaDelete,
1152 WamiAction::PersonaRead,
1153 WamiAction::PersonaUpdate,
1154 WamiAction::PersonaList,
1155 WamiAction::PersonaInvoke,
1156 WamiAction::RoomCreate,
1158 WamiAction::RoomDelete,
1159 WamiAction::RoomJoin,
1160 WamiAction::RoomRead,
1161 WamiAction::RoomSend,
1162 WamiAction::RoomManage,
1163 WamiAction::InferenceListModels,
1165 WamiAction::InferenceInvoke,
1166 WamiAction::InferenceConfigureRouter,
1167 WamiAction::InferenceViewUsage,
1168 WamiAction::AnalyticsViewUsage,
1170 WamiAction::AnalyticsViewConversations,
1171 WamiAction::AnalyticsExport,
1172 WamiAction::AnalyticsViewActivity,
1173 WamiAction::IntegrationCreate,
1175 WamiAction::IntegrationDelete,
1176 WamiAction::IntegrationConfigure,
1177 WamiAction::IntegrationList,
1178 WamiAction::IntegrationReceive,
1179 WamiAction::IntegrationSend,
1180 WamiAction::CognitiveRead,
1182 WamiAction::CognitiveWrite,
1183 WamiAction::CognitiveReset,
1184 WamiAction::GdprGrantConsent,
1186 WamiAction::GdprRevokeConsent,
1187 WamiAction::GdprExportData,
1188 WamiAction::GdprEraseData,
1189 WamiAction::GdprViewAudit,
1190 ]
1191 }
1192
1193 #[test]
1198 fn roundtrip_every_concrete_action() {
1199 for action in all_concrete_actions() {
1200 let s = action.as_str();
1201 let parsed = WamiAction::from_str(s)
1202 .unwrap_or_else(|e| panic!("from_str({:?}) failed: {}", s, e));
1203 assert_eq!(parsed, action, "roundtrip failed for {:?}", s);
1204 }
1205 }
1206
1207 #[test]
1208 fn roundtrip_all_service_wildcards() {
1209 for prefix in WamiServicePrefix::all() {
1210 let action = WamiAction::ServiceAll(*prefix);
1211 let s = action.as_str();
1212 assert!(s.ends_with(":*"), "ServiceAll as_str should end with :*");
1213 let parsed = WamiAction::from_str(s).unwrap();
1214 assert_eq!(parsed, action);
1215 }
1216 }
1217
1218 #[test]
1219 fn roundtrip_global_wildcard() {
1220 assert_eq!(WamiAction::from_str("*").unwrap(), WamiAction::All);
1221 assert_eq!(WamiAction::All.as_str(), "*");
1222 }
1223
1224 #[test]
1229 fn display_fromstr_every_action() {
1230 for action in all_concrete_actions() {
1231 let display = action.to_string();
1232 let parsed: WamiAction = display.parse().unwrap();
1233 assert_eq!(parsed, action);
1234 }
1235 let all_display = WamiAction::All.to_string();
1237 assert_eq!(all_display, "*");
1238 assert_eq!(all_display.parse::<WamiAction>().unwrap(), WamiAction::All);
1239
1240 for prefix in WamiServicePrefix::all() {
1241 let svc = WamiAction::ServiceAll(*prefix);
1242 let display = svc.to_string();
1243 assert_eq!(display.parse::<WamiAction>().unwrap(), svc);
1244 }
1245 }
1246
1247 #[test]
1252 fn prefix_every_concrete_action() {
1253 let expected: Vec<(WamiAction, WamiServicePrefix)> = vec![
1254 (WamiAction::PlatformAdmin, WamiServicePrefix::Platform),
1255 (
1256 WamiAction::PlatformViewSettings,
1257 WamiServicePrefix::Platform,
1258 ),
1259 (
1260 WamiAction::PlatformUpdateSettings,
1261 WamiServicePrefix::Platform,
1262 ),
1263 (
1264 WamiAction::PlatformViewAuditLog,
1265 WamiServicePrefix::Platform,
1266 ),
1267 (WamiAction::SpaceCreate, WamiServicePrefix::Space),
1268 (WamiAction::SpaceDelete, WamiServicePrefix::Space),
1269 (WamiAction::SpaceRead, WamiServicePrefix::Space),
1270 (WamiAction::SpaceUpdate, WamiServicePrefix::Space),
1271 (WamiAction::SpaceConfigure, WamiServicePrefix::Space),
1272 (WamiAction::SpaceManageMembers, WamiServicePrefix::Space),
1273 (WamiAction::SpaceSuspend, WamiServicePrefix::Space),
1274 (WamiAction::SpaceList, WamiServicePrefix::Space),
1275 (WamiAction::TenantRead, WamiServicePrefix::Tenant),
1276 (WamiAction::TenantUpdate, WamiServicePrefix::Tenant),
1277 (WamiAction::TenantDelete, WamiServicePrefix::Tenant),
1278 (WamiAction::TenantCreateSubTenant, WamiServicePrefix::Tenant),
1279 (WamiAction::TenantManageUsers, WamiServicePrefix::Tenant),
1280 (WamiAction::TenantManageRoles, WamiServicePrefix::Tenant),
1281 (WamiAction::TenantManagePolicies, WamiServicePrefix::Tenant),
1282 (WamiAction::IamCreateUser, WamiServicePrefix::Iam),
1283 (WamiAction::IamDeleteUser, WamiServicePrefix::Iam),
1284 (WamiAction::IamReadUser, WamiServicePrefix::Iam),
1285 (WamiAction::IamUpdateUser, WamiServicePrefix::Iam),
1286 (WamiAction::IamListUsers, WamiServicePrefix::Iam),
1287 (WamiAction::IamCreateGroup, WamiServicePrefix::Iam),
1288 (WamiAction::IamDeleteGroup, WamiServicePrefix::Iam),
1289 (WamiAction::IamManageGroupMembers, WamiServicePrefix::Iam),
1290 (WamiAction::IamCreateRole, WamiServicePrefix::Iam),
1291 (WamiAction::IamDeleteRole, WamiServicePrefix::Iam),
1292 (WamiAction::IamReadRole, WamiServicePrefix::Iam),
1293 (WamiAction::IamAssumeRole, WamiServicePrefix::Iam),
1294 (WamiAction::IamCreatePolicy, WamiServicePrefix::Iam),
1295 (WamiAction::IamDeletePolicy, WamiServicePrefix::Iam),
1296 (WamiAction::IamReadPolicy, WamiServicePrefix::Iam),
1297 (WamiAction::IamAttachPolicy, WamiServicePrefix::Iam),
1298 (WamiAction::IamDetachPolicy, WamiServicePrefix::Iam),
1299 (WamiAction::IamSetBoundary, WamiServicePrefix::Iam),
1300 (WamiAction::IamManageCredentials, WamiServicePrefix::Iam),
1301 (WamiAction::DbQuery, WamiServicePrefix::Db),
1302 (WamiAction::DbWrite, WamiServicePrefix::Db),
1303 (WamiAction::DbDelete, WamiServicePrefix::Db),
1304 (WamiAction::DbCreate, WamiServicePrefix::Db),
1305 (WamiAction::DbDrop, WamiServicePrefix::Db),
1306 (WamiAction::DbList, WamiServicePrefix::Db),
1307 (WamiAction::DbConfigureAccess, WamiServicePrefix::Db),
1308 (WamiAction::DbImport, WamiServicePrefix::Db),
1309 (WamiAction::DbExport, WamiServicePrefix::Db),
1310 (WamiAction::ChatSend, WamiServicePrefix::Chat),
1311 (WamiAction::ChatReadHistory, WamiServicePrefix::Chat),
1312 (WamiAction::ChatDeleteConversation, WamiServicePrefix::Chat),
1313 (WamiAction::ChatStream, WamiServicePrefix::Chat),
1314 (WamiAction::PersonaCreate, WamiServicePrefix::Persona),
1315 (WamiAction::PersonaDelete, WamiServicePrefix::Persona),
1316 (WamiAction::PersonaRead, WamiServicePrefix::Persona),
1317 (WamiAction::PersonaUpdate, WamiServicePrefix::Persona),
1318 (WamiAction::PersonaList, WamiServicePrefix::Persona),
1319 (WamiAction::PersonaInvoke, WamiServicePrefix::Persona),
1320 (WamiAction::RoomCreate, WamiServicePrefix::Room),
1321 (WamiAction::RoomDelete, WamiServicePrefix::Room),
1322 (WamiAction::RoomJoin, WamiServicePrefix::Room),
1323 (WamiAction::RoomRead, WamiServicePrefix::Room),
1324 (WamiAction::RoomSend, WamiServicePrefix::Room),
1325 (WamiAction::RoomManage, WamiServicePrefix::Room),
1326 (
1327 WamiAction::InferenceListModels,
1328 WamiServicePrefix::Inference,
1329 ),
1330 (WamiAction::InferenceInvoke, WamiServicePrefix::Inference),
1331 (
1332 WamiAction::InferenceConfigureRouter,
1333 WamiServicePrefix::Inference,
1334 ),
1335 (WamiAction::InferenceViewUsage, WamiServicePrefix::Inference),
1336 (WamiAction::AnalyticsViewUsage, WamiServicePrefix::Analytics),
1337 (
1338 WamiAction::AnalyticsViewConversations,
1339 WamiServicePrefix::Analytics,
1340 ),
1341 (WamiAction::AnalyticsExport, WamiServicePrefix::Analytics),
1342 (
1343 WamiAction::AnalyticsViewActivity,
1344 WamiServicePrefix::Analytics,
1345 ),
1346 (
1347 WamiAction::IntegrationCreate,
1348 WamiServicePrefix::Integration,
1349 ),
1350 (
1351 WamiAction::IntegrationDelete,
1352 WamiServicePrefix::Integration,
1353 ),
1354 (
1355 WamiAction::IntegrationConfigure,
1356 WamiServicePrefix::Integration,
1357 ),
1358 (WamiAction::IntegrationList, WamiServicePrefix::Integration),
1359 (
1360 WamiAction::IntegrationReceive,
1361 WamiServicePrefix::Integration,
1362 ),
1363 (WamiAction::IntegrationSend, WamiServicePrefix::Integration),
1364 (WamiAction::CognitiveRead, WamiServicePrefix::Cognitive),
1365 (WamiAction::CognitiveWrite, WamiServicePrefix::Cognitive),
1366 (WamiAction::CognitiveReset, WamiServicePrefix::Cognitive),
1367 (WamiAction::GdprGrantConsent, WamiServicePrefix::Gdpr),
1368 (WamiAction::GdprRevokeConsent, WamiServicePrefix::Gdpr),
1369 (WamiAction::GdprExportData, WamiServicePrefix::Gdpr),
1370 (WamiAction::GdprEraseData, WamiServicePrefix::Gdpr),
1371 (WamiAction::GdprViewAudit, WamiServicePrefix::Gdpr),
1372 ];
1373
1374 for (action, expected_prefix) in &expected {
1375 assert_eq!(
1376 action.prefix(),
1377 Some(*expected_prefix),
1378 "prefix() wrong for {:?}",
1379 action
1380 );
1381 }
1382 }
1383
1384 #[test]
1385 fn prefix_wildcards() {
1386 assert_eq!(WamiAction::All.prefix(), None);
1387 for prefix in WamiServicePrefix::all() {
1388 assert_eq!(WamiAction::ServiceAll(*prefix).prefix(), Some(*prefix),);
1389 }
1390 }
1391
1392 #[test]
1397 fn as_str_platform() {
1398 assert_eq!(WamiAction::PlatformAdmin.as_str(), "platform:Admin");
1399 assert_eq!(
1400 WamiAction::PlatformViewSettings.as_str(),
1401 "platform:ViewSettings"
1402 );
1403 assert_eq!(
1404 WamiAction::PlatformUpdateSettings.as_str(),
1405 "platform:UpdateSettings"
1406 );
1407 assert_eq!(
1408 WamiAction::PlatformViewAuditLog.as_str(),
1409 "platform:ViewAuditLog"
1410 );
1411 }
1412
1413 #[test]
1414 fn as_str_space() {
1415 assert_eq!(WamiAction::SpaceCreate.as_str(), "space:Create");
1416 assert_eq!(WamiAction::SpaceDelete.as_str(), "space:Delete");
1417 assert_eq!(WamiAction::SpaceRead.as_str(), "space:Read");
1418 assert_eq!(WamiAction::SpaceUpdate.as_str(), "space:Update");
1419 assert_eq!(WamiAction::SpaceConfigure.as_str(), "space:Configure");
1420 assert_eq!(
1421 WamiAction::SpaceManageMembers.as_str(),
1422 "space:ManageMembers"
1423 );
1424 assert_eq!(WamiAction::SpaceSuspend.as_str(), "space:Suspend");
1425 assert_eq!(WamiAction::SpaceList.as_str(), "space:List");
1426 }
1427
1428 #[test]
1429 fn as_str_tenant() {
1430 assert_eq!(WamiAction::TenantRead.as_str(), "tenant:Read");
1431 assert_eq!(WamiAction::TenantUpdate.as_str(), "tenant:Update");
1432 assert_eq!(WamiAction::TenantDelete.as_str(), "tenant:Delete");
1433 assert_eq!(
1434 WamiAction::TenantCreateSubTenant.as_str(),
1435 "tenant:CreateSubTenant"
1436 );
1437 assert_eq!(WamiAction::TenantManageUsers.as_str(), "tenant:ManageUsers");
1438 assert_eq!(WamiAction::TenantManageRoles.as_str(), "tenant:ManageRoles");
1439 assert_eq!(
1440 WamiAction::TenantManagePolicies.as_str(),
1441 "tenant:ManagePolicies"
1442 );
1443 }
1444
1445 #[test]
1446 fn as_str_iam() {
1447 assert_eq!(WamiAction::IamCreateUser.as_str(), "iam:CreateUser");
1448 assert_eq!(WamiAction::IamDeleteUser.as_str(), "iam:DeleteUser");
1449 assert_eq!(WamiAction::IamReadUser.as_str(), "iam:ReadUser");
1450 assert_eq!(WamiAction::IamUpdateUser.as_str(), "iam:UpdateUser");
1451 assert_eq!(WamiAction::IamListUsers.as_str(), "iam:ListUsers");
1452 assert_eq!(WamiAction::IamCreateGroup.as_str(), "iam:CreateGroup");
1453 assert_eq!(WamiAction::IamDeleteGroup.as_str(), "iam:DeleteGroup");
1454 assert_eq!(
1455 WamiAction::IamManageGroupMembers.as_str(),
1456 "iam:ManageGroupMembers"
1457 );
1458 assert_eq!(WamiAction::IamCreateRole.as_str(), "iam:CreateRole");
1459 assert_eq!(WamiAction::IamDeleteRole.as_str(), "iam:DeleteRole");
1460 assert_eq!(WamiAction::IamReadRole.as_str(), "iam:ReadRole");
1461 assert_eq!(WamiAction::IamAssumeRole.as_str(), "iam:AssumeRole");
1462 assert_eq!(WamiAction::IamCreatePolicy.as_str(), "iam:CreatePolicy");
1463 assert_eq!(WamiAction::IamDeletePolicy.as_str(), "iam:DeletePolicy");
1464 assert_eq!(WamiAction::IamReadPolicy.as_str(), "iam:ReadPolicy");
1465 assert_eq!(WamiAction::IamAttachPolicy.as_str(), "iam:AttachPolicy");
1466 assert_eq!(WamiAction::IamDetachPolicy.as_str(), "iam:DetachPolicy");
1467 assert_eq!(WamiAction::IamSetBoundary.as_str(), "iam:SetBoundary");
1468 assert_eq!(
1469 WamiAction::IamManageCredentials.as_str(),
1470 "iam:ManageCredentials"
1471 );
1472 }
1473
1474 #[test]
1475 fn as_str_db() {
1476 assert_eq!(WamiAction::DbQuery.as_str(), "db:Query");
1477 assert_eq!(WamiAction::DbWrite.as_str(), "db:Write");
1478 assert_eq!(WamiAction::DbDelete.as_str(), "db:Delete");
1479 assert_eq!(WamiAction::DbCreate.as_str(), "db:Create");
1480 assert_eq!(WamiAction::DbDrop.as_str(), "db:Drop");
1481 assert_eq!(WamiAction::DbList.as_str(), "db:List");
1482 assert_eq!(WamiAction::DbConfigureAccess.as_str(), "db:ConfigureAccess");
1483 assert_eq!(WamiAction::DbImport.as_str(), "db:Import");
1484 assert_eq!(WamiAction::DbExport.as_str(), "db:Export");
1485 }
1486
1487 #[test]
1488 fn as_str_chat() {
1489 assert_eq!(WamiAction::ChatSend.as_str(), "chat:Send");
1490 assert_eq!(WamiAction::ChatReadHistory.as_str(), "chat:ReadHistory");
1491 assert_eq!(
1492 WamiAction::ChatDeleteConversation.as_str(),
1493 "chat:DeleteConversation"
1494 );
1495 assert_eq!(WamiAction::ChatStream.as_str(), "chat:Stream");
1496 }
1497
1498 #[test]
1499 fn as_str_persona() {
1500 assert_eq!(WamiAction::PersonaCreate.as_str(), "persona:Create");
1501 assert_eq!(WamiAction::PersonaDelete.as_str(), "persona:Delete");
1502 assert_eq!(WamiAction::PersonaRead.as_str(), "persona:Read");
1503 assert_eq!(WamiAction::PersonaUpdate.as_str(), "persona:Update");
1504 assert_eq!(WamiAction::PersonaList.as_str(), "persona:List");
1505 assert_eq!(WamiAction::PersonaInvoke.as_str(), "persona:Invoke");
1506 }
1507
1508 #[test]
1509 fn as_str_room() {
1510 assert_eq!(WamiAction::RoomCreate.as_str(), "room:Create");
1511 assert_eq!(WamiAction::RoomDelete.as_str(), "room:Delete");
1512 assert_eq!(WamiAction::RoomJoin.as_str(), "room:Join");
1513 assert_eq!(WamiAction::RoomRead.as_str(), "room:Read");
1514 assert_eq!(WamiAction::RoomSend.as_str(), "room:Send");
1515 assert_eq!(WamiAction::RoomManage.as_str(), "room:Manage");
1516 }
1517
1518 #[test]
1519 fn as_str_inference() {
1520 assert_eq!(
1521 WamiAction::InferenceListModels.as_str(),
1522 "inference:ListModels"
1523 );
1524 assert_eq!(WamiAction::InferenceInvoke.as_str(), "inference:Invoke");
1525 assert_eq!(
1526 WamiAction::InferenceConfigureRouter.as_str(),
1527 "inference:ConfigureRouter"
1528 );
1529 assert_eq!(
1530 WamiAction::InferenceViewUsage.as_str(),
1531 "inference:ViewUsage"
1532 );
1533 }
1534
1535 #[test]
1536 fn as_str_analytics() {
1537 assert_eq!(
1538 WamiAction::AnalyticsViewUsage.as_str(),
1539 "analytics:ViewUsage"
1540 );
1541 assert_eq!(
1542 WamiAction::AnalyticsViewConversations.as_str(),
1543 "analytics:ViewConversations"
1544 );
1545 assert_eq!(WamiAction::AnalyticsExport.as_str(), "analytics:Export");
1546 assert_eq!(
1547 WamiAction::AnalyticsViewActivity.as_str(),
1548 "analytics:ViewActivity"
1549 );
1550 }
1551
1552 #[test]
1553 fn as_str_integration() {
1554 assert_eq!(WamiAction::IntegrationCreate.as_str(), "integration:Create");
1555 assert_eq!(WamiAction::IntegrationDelete.as_str(), "integration:Delete");
1556 assert_eq!(
1557 WamiAction::IntegrationConfigure.as_str(),
1558 "integration:Configure"
1559 );
1560 assert_eq!(WamiAction::IntegrationList.as_str(), "integration:List");
1561 assert_eq!(
1562 WamiAction::IntegrationReceive.as_str(),
1563 "integration:Receive"
1564 );
1565 assert_eq!(WamiAction::IntegrationSend.as_str(), "integration:Send");
1566 }
1567
1568 #[test]
1569 fn as_str_cognitive() {
1570 assert_eq!(WamiAction::CognitiveRead.as_str(), "cognitive:Read");
1571 assert_eq!(WamiAction::CognitiveWrite.as_str(), "cognitive:Write");
1572 assert_eq!(WamiAction::CognitiveReset.as_str(), "cognitive:Reset");
1573 }
1574
1575 #[test]
1576 fn as_str_gdpr() {
1577 assert_eq!(WamiAction::GdprGrantConsent.as_str(), "gdpr:GrantConsent");
1578 assert_eq!(WamiAction::GdprRevokeConsent.as_str(), "gdpr:RevokeConsent");
1579 assert_eq!(WamiAction::GdprExportData.as_str(), "gdpr:ExportData");
1580 assert_eq!(WamiAction::GdprEraseData.as_str(), "gdpr:EraseData");
1581 assert_eq!(WamiAction::GdprViewAudit.as_str(), "gdpr:ViewAudit");
1582 }
1583
1584 #[test]
1589 fn as_str_service_all_every_prefix() {
1590 assert_eq!(
1591 WamiAction::ServiceAll(WamiServicePrefix::Platform).as_str(),
1592 "platform:*"
1593 );
1594 assert_eq!(
1595 WamiAction::ServiceAll(WamiServicePrefix::Space).as_str(),
1596 "space:*"
1597 );
1598 assert_eq!(
1599 WamiAction::ServiceAll(WamiServicePrefix::Tenant).as_str(),
1600 "tenant:*"
1601 );
1602 assert_eq!(
1603 WamiAction::ServiceAll(WamiServicePrefix::Iam).as_str(),
1604 "iam:*"
1605 );
1606 assert_eq!(
1607 WamiAction::ServiceAll(WamiServicePrefix::Db).as_str(),
1608 "db:*"
1609 );
1610 assert_eq!(
1611 WamiAction::ServiceAll(WamiServicePrefix::Chat).as_str(),
1612 "chat:*"
1613 );
1614 assert_eq!(
1615 WamiAction::ServiceAll(WamiServicePrefix::Persona).as_str(),
1616 "persona:*"
1617 );
1618 assert_eq!(
1619 WamiAction::ServiceAll(WamiServicePrefix::Room).as_str(),
1620 "room:*"
1621 );
1622 assert_eq!(
1623 WamiAction::ServiceAll(WamiServicePrefix::Inference).as_str(),
1624 "inference:*"
1625 );
1626 assert_eq!(
1627 WamiAction::ServiceAll(WamiServicePrefix::Analytics).as_str(),
1628 "analytics:*"
1629 );
1630 assert_eq!(
1631 WamiAction::ServiceAll(WamiServicePrefix::Integration).as_str(),
1632 "integration:*"
1633 );
1634 assert_eq!(
1635 WamiAction::ServiceAll(WamiServicePrefix::Cognitive).as_str(),
1636 "cognitive:*"
1637 );
1638 assert_eq!(
1639 WamiAction::ServiceAll(WamiServicePrefix::Gdpr).as_str(),
1640 "gdpr:*"
1641 );
1642 }
1643
1644 #[test]
1649 fn matches_all_matches_everything() {
1650 let all = WamiAction::All;
1651 for action in all_concrete_actions() {
1652 assert!(all.matches(&action), "All should match {:?}", action);
1653 }
1654 for prefix in WamiServicePrefix::all() {
1656 assert!(all.matches(&WamiAction::ServiceAll(*prefix)));
1657 }
1658 assert!(all.matches(&WamiAction::All));
1659 }
1660
1661 #[test]
1662 fn service_all_matches_only_own_prefix() {
1663 let actions = all_concrete_actions();
1666 for prefix in WamiServicePrefix::all() {
1667 let svc_all = WamiAction::ServiceAll(*prefix);
1668 for action in &actions {
1669 if action.prefix() == Some(*prefix) {
1670 assert!(
1671 svc_all.matches(action),
1672 "{:?} should match {:?}",
1673 svc_all,
1674 action
1675 );
1676 } else {
1677 assert!(
1678 !svc_all.matches(action),
1679 "{:?} should NOT match {:?}",
1680 svc_all,
1681 action
1682 );
1683 }
1684 }
1685 }
1686 }
1687
1688 #[test]
1689 fn exact_match_only_matches_self() {
1690 let actions = all_concrete_actions();
1691 for a in &actions {
1692 assert!(a.matches(a), "{:?} should match itself", a);
1693 for b in &actions {
1694 if a != b {
1695 assert!(!a.matches(b), "{:?} should not match {:?}", a, b);
1696 }
1697 }
1698 }
1699 }
1700
1701 #[test]
1702 fn concrete_does_not_match_service_all() {
1703 let iam_create = WamiAction::IamCreateUser;
1705 let iam_all = WamiAction::ServiceAll(WamiServicePrefix::Iam);
1706 assert!(!iam_create.matches(&iam_all));
1707 }
1708
1709 #[test]
1714 fn matches_str_star_matches_everything() {
1715 for action in all_concrete_actions() {
1716 assert!(WamiAction::matches_str("*", action.as_str()));
1717 }
1718 }
1719
1720 #[test]
1721 fn matches_str_service_wildcard_every_prefix() {
1722 let prefixes_and_actions: Vec<(&str, &str, &str)> = vec![
1723 ("platform:*", "platform:Admin", "space:Create"),
1724 ("space:*", "space:Create", "tenant:Read"),
1725 ("tenant:*", "tenant:Read", "iam:CreateUser"),
1726 ("iam:*", "iam:CreateUser", "db:Query"),
1727 ("db:*", "db:Query", "chat:Send"),
1728 ("chat:*", "chat:Send", "persona:Create"),
1729 ("persona:*", "persona:Create", "room:Create"),
1730 ("room:*", "room:Create", "inference:Invoke"),
1731 ("inference:*", "inference:Invoke", "analytics:Export"),
1732 ("analytics:*", "analytics:Export", "integration:Create"),
1733 ("integration:*", "integration:Create", "cognitive:Read"),
1734 ("cognitive:*", "cognitive:Read", "gdpr:GrantConsent"),
1735 ("gdpr:*", "gdpr:GrantConsent", "platform:Admin"),
1736 ];
1737 for (pattern, should_match, should_not_match) in prefixes_and_actions {
1738 assert!(
1739 WamiAction::matches_str(pattern, should_match),
1740 "{} should match {}",
1741 pattern,
1742 should_match
1743 );
1744 assert!(
1745 !WamiAction::matches_str(pattern, should_not_match),
1746 "{} should NOT match {}",
1747 pattern,
1748 should_not_match
1749 );
1750 }
1751 }
1752
1753 #[test]
1754 fn matches_str_exact() {
1755 assert!(WamiAction::matches_str("iam:CreateUser", "iam:CreateUser"));
1756 assert!(!WamiAction::matches_str("iam:CreateUser", "iam:DeleteUser"));
1757 }
1758
1759 #[test]
1760 fn matches_str_no_colon_in_requested() {
1761 assert!(!WamiAction::matches_str("iam:*", "noColonHere"));
1763 }
1764
1765 #[test]
1766 fn matches_str_non_matching_patterns() {
1767 assert!(!WamiAction::matches_str("db:Query", "db:Write"));
1768 assert!(!WamiAction::matches_str("iam:*", "db:Query"));
1769 assert!(!WamiAction::matches_str("platform:Admin", "*"));
1770 }
1771
1772 #[test]
1777 fn from_str_empty_string() {
1778 let err = WamiAction::from_str("").unwrap_err();
1779 assert!(matches!(err, ActionParseError::InvalidFormat(_)));
1780 }
1781
1782 #[test]
1783 fn from_str_no_colon() {
1784 let err = WamiAction::from_str("noColon").unwrap_err();
1785 assert!(matches!(err, ActionParseError::InvalidFormat(_)));
1786 }
1787
1788 #[test]
1789 fn from_str_unknown_prefix() {
1790 let err = WamiAction::from_str("fake:*").unwrap_err();
1791 assert!(matches!(err, ActionParseError::UnknownPrefix(_)));
1792 }
1793
1794 #[test]
1795 fn from_str_unknown_operation() {
1796 let err = WamiAction::from_str("iam:FakeOp").unwrap_err();
1797 assert!(matches!(err, ActionParseError::Unknown(_)));
1798 }
1799
1800 #[test]
1801 fn from_str_case_sensitive() {
1802 assert!(WamiAction::from_str("IAM:CreateUser").is_err());
1804 assert!(WamiAction::from_str("iam:createuser").is_err());
1805 assert!(WamiAction::from_str("Iam:CreateUser").is_err());
1806 assert!(WamiAction::from_str("iam:createUser").is_err());
1807 }
1808
1809 #[test]
1810 fn from_str_valid_prefix_wrong_operation() {
1811 assert!(WamiAction::from_str("db:NotAnAction").is_err());
1812 assert!(WamiAction::from_str("chat:NotAnAction").is_err());
1813 assert!(WamiAction::from_str("room:NotAnAction").is_err());
1814 }
1815
1816 #[test]
1821 fn service_prefix_as_str_all() {
1822 assert_eq!(WamiServicePrefix::Platform.as_str(), "platform");
1823 assert_eq!(WamiServicePrefix::Space.as_str(), "space");
1824 assert_eq!(WamiServicePrefix::Tenant.as_str(), "tenant");
1825 assert_eq!(WamiServicePrefix::Iam.as_str(), "iam");
1826 assert_eq!(WamiServicePrefix::Db.as_str(), "db");
1827 assert_eq!(WamiServicePrefix::Chat.as_str(), "chat");
1828 assert_eq!(WamiServicePrefix::Persona.as_str(), "persona");
1829 assert_eq!(WamiServicePrefix::Room.as_str(), "room");
1830 assert_eq!(WamiServicePrefix::Inference.as_str(), "inference");
1831 assert_eq!(WamiServicePrefix::Analytics.as_str(), "analytics");
1832 assert_eq!(WamiServicePrefix::Integration.as_str(), "integration");
1833 assert_eq!(WamiServicePrefix::Cognitive.as_str(), "cognitive");
1834 assert_eq!(WamiServicePrefix::Gdpr.as_str(), "gdpr");
1835 }
1836
1837 #[test]
1838 fn service_prefix_from_str_all() {
1839 for prefix in WamiServicePrefix::all() {
1840 let s = prefix.as_str();
1841 let parsed = WamiServicePrefix::from_str(s).unwrap();
1842 assert_eq!(parsed, *prefix);
1843 }
1844 }
1845
1846 #[test]
1847 fn service_prefix_from_str_error() {
1848 let err = WamiServicePrefix::from_str("nonexistent").unwrap_err();
1849 assert!(matches!(err, ActionParseError::UnknownPrefix(_)));
1850 }
1851
1852 #[test]
1853 fn service_prefix_display() {
1854 for prefix in WamiServicePrefix::all() {
1855 assert_eq!(prefix.to_string(), prefix.as_str());
1856 }
1857 }
1858
1859 #[test]
1860 fn service_prefix_all_returns_all_13() {
1861 assert_eq!(WamiServicePrefix::all().len(), 13);
1862 }
1863
1864 #[test]
1869 fn serde_roundtrip_every_action() {
1870 for action in all_concrete_actions() {
1871 let json = serde_json::to_string(&action).unwrap();
1872 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
1873 assert_eq!(parsed, action);
1874 }
1875 }
1876
1877 #[test]
1878 fn serde_roundtrip_every_service_wildcard() {
1879 for prefix in WamiServicePrefix::all() {
1880 let action = WamiAction::ServiceAll(*prefix);
1881 let json = serde_json::to_string(&action).unwrap();
1882 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
1883 assert_eq!(parsed, action);
1884 }
1885 }
1886
1887 #[test]
1888 fn serde_deserialize_invalid() {
1889 let result: Result<WamiAction, _> = serde_json::from_str(r#""not:valid:action""#);
1890 assert!(result.is_err());
1891
1892 let result: Result<WamiAction, _> = serde_json::from_str(r#""garbage""#);
1893 assert!(result.is_err());
1894 }
1895
1896 #[test]
1901 fn action_parse_error_display() {
1902 let e1 = ActionParseError::Unknown("foo:bar".into());
1903 assert!(e1.to_string().contains("foo:bar"));
1904
1905 let e2 = ActionParseError::UnknownPrefix("xyz".into());
1906 assert!(e2.to_string().contains("xyz"));
1907
1908 let e3 = ActionParseError::InvalidFormat("nocolon".into());
1909 assert!(e3.to_string().contains("nocolon"));
1910 }
1911
1912 #[test]
1917 fn registry_list_by_every_prefix() {
1918 let expected_counts: Vec<(WamiServicePrefix, usize)> = vec![
1919 (WamiServicePrefix::Platform, 4),
1920 (WamiServicePrefix::Space, 8),
1921 (WamiServicePrefix::Tenant, 7),
1922 (WamiServicePrefix::Iam, 19),
1923 (WamiServicePrefix::Db, 9),
1924 (WamiServicePrefix::Chat, 4),
1925 (WamiServicePrefix::Persona, 6),
1926 (WamiServicePrefix::Room, 6),
1927 (WamiServicePrefix::Inference, 4),
1928 (WamiServicePrefix::Analytics, 4),
1929 (WamiServicePrefix::Integration, 6),
1930 (WamiServicePrefix::Cognitive, 3),
1931 (WamiServicePrefix::Gdpr, 5),
1932 ];
1933 for (prefix, expected) in expected_counts {
1934 let actions = ActionRegistry::list_by_prefix(prefix);
1935 assert_eq!(
1936 actions.len(),
1937 expected,
1938 "Wrong count for {:?}: got {}, expected {}",
1939 prefix,
1940 actions.len(),
1941 expected
1942 );
1943 for a in &actions {
1944 assert_eq!(a.category, prefix.as_str());
1945 }
1946 }
1947 }
1948
1949 #[test]
1950 fn registry_list_prefixes() {
1951 let prefixes = ActionRegistry::list_prefixes();
1952 assert_eq!(prefixes.len(), 13);
1953 }
1954
1955 #[test]
1956 fn registry_total_action_count() {
1957 let all = ActionRegistry::list_actions();
1958 assert_eq!(all.len(), 85);
1960 }
1961}