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)]
33pub enum WamiAction {
34 All,
37 ServiceAll(WamiServicePrefix),
39
40 PlatformAdmin,
43 PlatformViewSettings,
45 PlatformUpdateSettings,
47 PlatformViewAuditLog,
49
50 TenantCreate,
53 TenantConfigure,
55 TenantManageMembers,
57 TenantSuspend,
64 TenantResume,
66 TenantList,
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,
115 IamDeletePolicy,
119 IamReadPolicy,
121 IamAttachPolicy,
123 IamDetachPolicy,
125 IamSetBoundary,
127 IamManageCredentials,
129
130 DbQuery,
133 DbWrite,
135 DbDelete,
137 DbCreate,
139 DbDrop,
141 DbList,
143 DbConfigureAccess,
145 DbImport,
147 DbExport,
149
150 ChatSend,
153 ChatReadHistory,
155 ChatDeleteConversation,
157 ChatStream,
159
160 PersonaCreate,
163 PersonaDelete,
165 PersonaRead,
167 PersonaUpdate,
169 PersonaList,
171 PersonaInvoke,
173
174 RoomCreate,
177 RoomDelete,
179 RoomJoin,
181 RoomRead,
183 RoomSend,
185 RoomManage,
187
188 InferenceListModels,
191 InferenceInvoke,
193 InferenceConfigureRouter,
195 InferenceViewUsage,
197
198 AnalyticsViewUsage,
201 AnalyticsViewConversations,
203 AnalyticsExport,
205 AnalyticsViewActivity,
207
208 IntegrationCreate,
211 IntegrationDelete,
213 IntegrationConfigure,
215 IntegrationList,
217 IntegrationReceive,
219 IntegrationSend,
221
222 CognitiveRead,
225 CognitiveWrite,
227 CognitiveReset,
229
230 GdprGrantConsent,
233 GdprRevokeConsent,
235 GdprExportData,
237 GdprEraseData,
239 GdprViewAudit,
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
249pub enum WamiServicePrefix {
250 Platform,
251 Tenant,
252 Iam,
253 Db,
254 Chat,
255 Persona,
256 Room,
257 Inference,
258 Analytics,
259 Integration,
260 Cognitive,
261 Gdpr,
262}
263
264impl WamiServicePrefix {
265 pub fn as_str(&self) -> &'static str {
266 match self {
267 Self::Platform => "platform",
268 Self::Tenant => "tenant",
269 Self::Iam => "iam",
270 Self::Db => "db",
271 Self::Chat => "chat",
272 Self::Persona => "persona",
273 Self::Room => "room",
274 Self::Inference => "inference",
275 Self::Analytics => "analytics",
276 Self::Integration => "integration",
277 Self::Cognitive => "cognitive",
278 Self::Gdpr => "gdpr",
279 }
280 }
281
282 pub fn all() -> &'static [WamiServicePrefix] {
283 &[
284 Self::Platform,
285 Self::Tenant,
286 Self::Iam,
287 Self::Db,
288 Self::Chat,
289 Self::Persona,
290 Self::Room,
291 Self::Inference,
292 Self::Analytics,
293 Self::Integration,
294 Self::Cognitive,
295 Self::Gdpr,
296 ]
297 }
298}
299
300impl FromStr for WamiServicePrefix {
301 type Err = ActionParseError;
302
303 fn from_str(s: &str) -> Result<Self, Self::Err> {
304 match s {
305 "platform" => Ok(Self::Platform),
306 "tenant" => Ok(Self::Tenant),
307 "iam" => Ok(Self::Iam),
308 "db" => Ok(Self::Db),
309 "chat" => Ok(Self::Chat),
310 "persona" => Ok(Self::Persona),
311 "room" => Ok(Self::Room),
312 "inference" => Ok(Self::Inference),
313 "analytics" => Ok(Self::Analytics),
314 "integration" => Ok(Self::Integration),
315 "cognitive" => Ok(Self::Cognitive),
316 "gdpr" => Ok(Self::Gdpr),
317 _ => Err(ActionParseError::UnknownPrefix(s.to_string())),
318 }
319 }
320}
321
322impl fmt::Display for WamiServicePrefix {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 f.write_str(self.as_str())
325 }
326}
327
328impl fmt::Display for WamiAction {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 f.write_str(self.as_str())
335 }
336}
337
338impl WamiAction {
339 pub fn as_str(&self) -> &'static str {
341 match self {
342 Self::All => "*",
344 Self::ServiceAll(prefix) => match prefix {
345 WamiServicePrefix::Platform => "platform:*",
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::TenantCreate => "tenant:Create",
364 Self::TenantConfigure => "tenant:Configure",
365 Self::TenantManageMembers => "tenant:ManageMembers",
366 Self::TenantSuspend => "tenant:Suspend",
367 Self::TenantResume => "tenant:Resume",
368 Self::TenantList => "tenant:List",
369 Self::TenantRead => "tenant:Read",
371 Self::TenantUpdate => "tenant:Update",
372 Self::TenantDelete => "tenant:Delete",
373 Self::TenantCreateSubTenant => "tenant:CreateSubTenant",
374 Self::TenantManageUsers => "tenant:ManageUsers",
375 Self::TenantManageRoles => "tenant:ManageRoles",
376 Self::TenantManagePolicies => "tenant:ManagePolicies",
377 Self::IamCreateUser => "iam:CreateUser",
379 Self::IamDeleteUser => "iam:DeleteUser",
380 Self::IamReadUser => "iam:ReadUser",
381 Self::IamUpdateUser => "iam:UpdateUser",
382 Self::IamListUsers => "iam:ListUsers",
383 Self::IamCreateGroup => "iam:CreateGroup",
384 Self::IamDeleteGroup => "iam:DeleteGroup",
385 Self::IamManageGroupMembers => "iam:ManageGroupMembers",
386 Self::IamCreateRole => "iam:CreateRole",
387 Self::IamDeleteRole => "iam:DeleteRole",
388 Self::IamReadRole => "iam:ReadRole",
389 Self::IamAssumeRole => "iam:AssumeRole",
390 Self::IamCreatePolicy => "iam:CreatePolicy",
391 Self::IamDeletePolicy => "iam:DeletePolicy",
392 Self::IamReadPolicy => "iam:ReadPolicy",
393 Self::IamAttachPolicy => "iam:AttachPolicy",
394 Self::IamDetachPolicy => "iam:DetachPolicy",
395 Self::IamSetBoundary => "iam:SetBoundary",
396 Self::IamManageCredentials => "iam:ManageCredentials",
397 Self::DbQuery => "db:Query",
399 Self::DbWrite => "db:Write",
400 Self::DbDelete => "db:Delete",
401 Self::DbCreate => "db:Create",
402 Self::DbDrop => "db:Drop",
403 Self::DbList => "db:List",
404 Self::DbConfigureAccess => "db:ConfigureAccess",
405 Self::DbImport => "db:Import",
406 Self::DbExport => "db:Export",
407 Self::ChatSend => "chat:Send",
409 Self::ChatReadHistory => "chat:ReadHistory",
410 Self::ChatDeleteConversation => "chat:DeleteConversation",
411 Self::ChatStream => "chat:Stream",
412 Self::PersonaCreate => "persona:Create",
414 Self::PersonaDelete => "persona:Delete",
415 Self::PersonaRead => "persona:Read",
416 Self::PersonaUpdate => "persona:Update",
417 Self::PersonaList => "persona:List",
418 Self::PersonaInvoke => "persona:Invoke",
419 Self::RoomCreate => "room:Create",
421 Self::RoomDelete => "room:Delete",
422 Self::RoomJoin => "room:Join",
423 Self::RoomRead => "room:Read",
424 Self::RoomSend => "room:Send",
425 Self::RoomManage => "room:Manage",
426 Self::InferenceListModels => "inference:ListModels",
428 Self::InferenceInvoke => "inference:Invoke",
429 Self::InferenceConfigureRouter => "inference:ConfigureRouter",
430 Self::InferenceViewUsage => "inference:ViewUsage",
431 Self::AnalyticsViewUsage => "analytics:ViewUsage",
433 Self::AnalyticsViewConversations => "analytics:ViewConversations",
434 Self::AnalyticsExport => "analytics:Export",
435 Self::AnalyticsViewActivity => "analytics:ViewActivity",
436 Self::IntegrationCreate => "integration:Create",
438 Self::IntegrationDelete => "integration:Delete",
439 Self::IntegrationConfigure => "integration:Configure",
440 Self::IntegrationList => "integration:List",
441 Self::IntegrationReceive => "integration:Receive",
442 Self::IntegrationSend => "integration:Send",
443 Self::CognitiveRead => "cognitive:Read",
445 Self::CognitiveWrite => "cognitive:Write",
446 Self::CognitiveReset => "cognitive:Reset",
447 Self::GdprGrantConsent => "gdpr:GrantConsent",
449 Self::GdprRevokeConsent => "gdpr:RevokeConsent",
450 Self::GdprExportData => "gdpr:ExportData",
451 Self::GdprEraseData => "gdpr:EraseData",
452 Self::GdprViewAudit => "gdpr:ViewAudit",
453 }
454 }
455
456 pub fn prefix(&self) -> Option<WamiServicePrefix> {
458 match self {
459 Self::All => None,
460 Self::ServiceAll(p) => Some(*p),
461 Self::PlatformAdmin
462 | Self::PlatformViewSettings
463 | Self::PlatformUpdateSettings
464 | Self::PlatformViewAuditLog => Some(WamiServicePrefix::Platform),
465 Self::TenantCreate
466 | Self::TenantConfigure
467 | Self::TenantManageMembers
468 | Self::TenantSuspend
469 | Self::TenantResume
470 | Self::TenantList
471 | Self::TenantRead
472 | Self::TenantUpdate
473 | Self::TenantDelete
474 | Self::TenantCreateSubTenant
475 | Self::TenantManageUsers
476 | Self::TenantManageRoles
477 | Self::TenantManagePolicies => Some(WamiServicePrefix::Tenant),
478 Self::IamCreateUser
479 | Self::IamDeleteUser
480 | Self::IamReadUser
481 | Self::IamUpdateUser
482 | Self::IamListUsers
483 | Self::IamCreateGroup
484 | Self::IamDeleteGroup
485 | Self::IamManageGroupMembers
486 | Self::IamCreateRole
487 | Self::IamDeleteRole
488 | Self::IamReadRole
489 | Self::IamAssumeRole
490 | Self::IamCreatePolicy
491 | Self::IamDeletePolicy
492 | Self::IamReadPolicy
493 | Self::IamAttachPolicy
494 | Self::IamDetachPolicy
495 | Self::IamSetBoundary
496 | Self::IamManageCredentials => Some(WamiServicePrefix::Iam),
497 Self::DbQuery
498 | Self::DbWrite
499 | Self::DbDelete
500 | Self::DbCreate
501 | Self::DbDrop
502 | Self::DbList
503 | Self::DbConfigureAccess
504 | Self::DbImport
505 | Self::DbExport => Some(WamiServicePrefix::Db),
506 Self::ChatSend
507 | Self::ChatReadHistory
508 | Self::ChatDeleteConversation
509 | Self::ChatStream => Some(WamiServicePrefix::Chat),
510 Self::PersonaCreate
511 | Self::PersonaDelete
512 | Self::PersonaRead
513 | Self::PersonaUpdate
514 | Self::PersonaList
515 | Self::PersonaInvoke => Some(WamiServicePrefix::Persona),
516 Self::RoomCreate
517 | Self::RoomDelete
518 | Self::RoomJoin
519 | Self::RoomRead
520 | Self::RoomSend
521 | Self::RoomManage => Some(WamiServicePrefix::Room),
522 Self::InferenceListModels
523 | Self::InferenceInvoke
524 | Self::InferenceConfigureRouter
525 | Self::InferenceViewUsage => Some(WamiServicePrefix::Inference),
526 Self::AnalyticsViewUsage
527 | Self::AnalyticsViewConversations
528 | Self::AnalyticsExport
529 | Self::AnalyticsViewActivity => Some(WamiServicePrefix::Analytics),
530 Self::IntegrationCreate
531 | Self::IntegrationDelete
532 | Self::IntegrationConfigure
533 | Self::IntegrationList
534 | Self::IntegrationReceive
535 | Self::IntegrationSend => Some(WamiServicePrefix::Integration),
536 Self::CognitiveRead | Self::CognitiveWrite | Self::CognitiveReset => {
537 Some(WamiServicePrefix::Cognitive)
538 }
539 Self::GdprGrantConsent
540 | Self::GdprRevokeConsent
541 | Self::GdprExportData
542 | Self::GdprEraseData
543 | Self::GdprViewAudit => Some(WamiServicePrefix::Gdpr),
544 }
545 }
546}
547
548#[derive(Debug, Clone, thiserror::Error)]
553pub enum ActionParseError {
554 #[error("unknown action: {0}")]
555 Unknown(String),
556 #[error("unknown service prefix: {0}")]
557 UnknownPrefix(String),
558 #[error("invalid action format (expected 'service:Operation' or '*'): {0}")]
559 InvalidFormat(String),
560}
561
562impl FromStr for WamiAction {
567 type Err = ActionParseError;
568
569 fn from_str(s: &str) -> Result<Self, Self::Err> {
570 if s == "*" {
572 return Ok(Self::All);
573 }
574
575 let Some((prefix_str, operation)) = s.split_once(':') else {
577 return Err(ActionParseError::InvalidFormat(s.to_string()));
578 };
579
580 if operation == "*" {
582 let prefix = WamiServicePrefix::from_str(prefix_str)?;
583 return Ok(Self::ServiceAll(prefix));
584 }
585
586 match (prefix_str, operation) {
588 ("platform", "Admin") => Ok(Self::PlatformAdmin),
590 ("platform", "ViewSettings") => Ok(Self::PlatformViewSettings),
591 ("platform", "UpdateSettings") => Ok(Self::PlatformUpdateSettings),
592 ("platform", "ViewAuditLog") => Ok(Self::PlatformViewAuditLog),
593 ("tenant", "Create") => Ok(Self::TenantCreate),
595 ("tenant", "Configure") => Ok(Self::TenantConfigure),
596 ("tenant", "ManageMembers") => Ok(Self::TenantManageMembers),
597 ("tenant", "Suspend") => Ok(Self::TenantSuspend),
598 ("tenant", "Resume") => Ok(Self::TenantResume),
599 ("tenant", "List") => Ok(Self::TenantList),
600 ("tenant", "Read") => Ok(Self::TenantRead),
601 ("tenant", "Update") => Ok(Self::TenantUpdate),
602 ("tenant", "Delete") => Ok(Self::TenantDelete),
603 ("tenant", "CreateSubTenant") => Ok(Self::TenantCreateSubTenant),
604 ("tenant", "ManageUsers") => Ok(Self::TenantManageUsers),
605 ("tenant", "ManageRoles") => Ok(Self::TenantManageRoles),
606 ("tenant", "ManagePolicies") => Ok(Self::TenantManagePolicies),
607 ("iam", "CreateUser") => Ok(Self::IamCreateUser),
609 ("iam", "DeleteUser") => Ok(Self::IamDeleteUser),
610 ("iam", "ReadUser") => Ok(Self::IamReadUser),
611 ("iam", "UpdateUser") => Ok(Self::IamUpdateUser),
612 ("iam", "ListUsers") => Ok(Self::IamListUsers),
613 ("iam", "CreateGroup") => Ok(Self::IamCreateGroup),
614 ("iam", "DeleteGroup") => Ok(Self::IamDeleteGroup),
615 ("iam", "ManageGroupMembers") => Ok(Self::IamManageGroupMembers),
616 ("iam", "CreateRole") => Ok(Self::IamCreateRole),
617 ("iam", "DeleteRole") => Ok(Self::IamDeleteRole),
618 ("iam", "ReadRole") => Ok(Self::IamReadRole),
619 ("iam", "AssumeRole") => Ok(Self::IamAssumeRole),
620 ("iam", "CreatePolicy") => Ok(Self::IamCreatePolicy),
621 ("iam", "DeletePolicy") => Ok(Self::IamDeletePolicy),
622 ("iam", "ReadPolicy") => Ok(Self::IamReadPolicy),
623 ("iam", "AttachPolicy") => Ok(Self::IamAttachPolicy),
624 ("iam", "DetachPolicy") => Ok(Self::IamDetachPolicy),
625 ("iam", "SetBoundary") => Ok(Self::IamSetBoundary),
626 ("iam", "ManageCredentials") => Ok(Self::IamManageCredentials),
627 ("db", "Query") => Ok(Self::DbQuery),
629 ("db", "Write") => Ok(Self::DbWrite),
630 ("db", "Delete") => Ok(Self::DbDelete),
631 ("db", "Create") => Ok(Self::DbCreate),
632 ("db", "Drop") => Ok(Self::DbDrop),
633 ("db", "List") => Ok(Self::DbList),
634 ("db", "ConfigureAccess") => Ok(Self::DbConfigureAccess),
635 ("db", "Import") => Ok(Self::DbImport),
636 ("db", "Export") => Ok(Self::DbExport),
637 ("chat", "Send") => Ok(Self::ChatSend),
639 ("chat", "ReadHistory") => Ok(Self::ChatReadHistory),
640 ("chat", "DeleteConversation") => Ok(Self::ChatDeleteConversation),
641 ("chat", "Stream") => Ok(Self::ChatStream),
642 ("persona", "Create") => Ok(Self::PersonaCreate),
644 ("persona", "Delete") => Ok(Self::PersonaDelete),
645 ("persona", "Read") => Ok(Self::PersonaRead),
646 ("persona", "Update") => Ok(Self::PersonaUpdate),
647 ("persona", "List") => Ok(Self::PersonaList),
648 ("persona", "Invoke") => Ok(Self::PersonaInvoke),
649 ("room", "Create") => Ok(Self::RoomCreate),
651 ("room", "Delete") => Ok(Self::RoomDelete),
652 ("room", "Join") => Ok(Self::RoomJoin),
653 ("room", "Read") => Ok(Self::RoomRead),
654 ("room", "Send") => Ok(Self::RoomSend),
655 ("room", "Manage") => Ok(Self::RoomManage),
656 ("inference", "ListModels") => Ok(Self::InferenceListModels),
658 ("inference", "Invoke") => Ok(Self::InferenceInvoke),
659 ("inference", "ConfigureRouter") => Ok(Self::InferenceConfigureRouter),
660 ("inference", "ViewUsage") => Ok(Self::InferenceViewUsage),
661 ("analytics", "ViewUsage") => Ok(Self::AnalyticsViewUsage),
663 ("analytics", "ViewConversations") => Ok(Self::AnalyticsViewConversations),
664 ("analytics", "Export") => Ok(Self::AnalyticsExport),
665 ("analytics", "ViewActivity") => Ok(Self::AnalyticsViewActivity),
666 ("integration", "Create") => Ok(Self::IntegrationCreate),
668 ("integration", "Delete") => Ok(Self::IntegrationDelete),
669 ("integration", "Configure") => Ok(Self::IntegrationConfigure),
670 ("integration", "List") => Ok(Self::IntegrationList),
671 ("integration", "Receive") => Ok(Self::IntegrationReceive),
672 ("integration", "Send") => Ok(Self::IntegrationSend),
673 ("cognitive", "Read") => Ok(Self::CognitiveRead),
675 ("cognitive", "Write") => Ok(Self::CognitiveWrite),
676 ("cognitive", "Reset") => Ok(Self::CognitiveReset),
677 ("gdpr", "GrantConsent") => Ok(Self::GdprGrantConsent),
679 ("gdpr", "RevokeConsent") => Ok(Self::GdprRevokeConsent),
680 ("gdpr", "ExportData") => Ok(Self::GdprExportData),
681 ("gdpr", "EraseData") => Ok(Self::GdprEraseData),
682 ("gdpr", "ViewAudit") => Ok(Self::GdprViewAudit),
683 _ => Err(ActionParseError::Unknown(s.to_string())),
684 }
685 }
686}
687
688impl Serialize for WamiAction {
693 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
694 serializer.serialize_str(self.as_str())
695 }
696}
697
698impl<'de> Deserialize<'de> for WamiAction {
699 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
700 let s = String::deserialize(deserializer)?;
701 WamiAction::from_str(&s).map_err(serde::de::Error::custom)
702 }
703}
704
705impl WamiAction {
710 pub fn matches(&self, requested: &WamiAction) -> bool {
720 match self {
721 Self::All => true,
723 Self::ServiceAll(prefix) => requested.prefix() == Some(*prefix),
725 other => other == requested,
727 }
728 }
729
730 pub fn matches_str(policy_action: &str, requested: &str) -> bool {
737 if policy_action == "*" {
738 return true;
739 }
740 if policy_action == requested {
741 return true;
742 }
743 if let Some(prefix) = policy_action.strip_suffix(":*") {
745 if let Some(req_prefix) = requested.split_once(':').map(|(p, _)| p) {
746 return prefix == req_prefix;
747 }
748 }
749 false
750 }
751}
752
753#[derive(Debug, Clone, Serialize, Deserialize)]
759pub struct ActionInfo {
760 pub action: String,
762 pub description: String,
764 pub category: String,
766}
767
768pub struct ActionRegistry;
770
771impl ActionRegistry {
772 pub fn list_actions() -> &'static [ActionInfo] {
774 ®ISTRY
775 }
776
777 pub fn list_by_prefix(prefix: WamiServicePrefix) -> Vec<&'static ActionInfo> {
779 let prefix_str = prefix.as_str();
780 REGISTRY
781 .iter()
782 .filter(|info| info.category == prefix_str)
783 .collect()
784 }
785
786 pub fn list_prefixes() -> &'static [WamiServicePrefix] {
788 WamiServicePrefix::all()
789 }
790}
791
792static REGISTRY: LazyLock<Vec<ActionInfo>> = LazyLock::new(|| {
794 vec![
795 ai("platform:Admin", "Full platform administration", "platform"),
797 ai(
798 "platform:ViewSettings",
799 "View platform settings and stats",
800 "platform",
801 ),
802 ai(
803 "platform:UpdateSettings",
804 "Update platform configuration",
805 "platform",
806 ),
807 ai(
808 "platform:ViewAuditLog",
809 "View platform audit logs",
810 "platform",
811 ),
812 ai("tenant:Create", "Create a new tenant", "tenant"),
814 ai(
815 "tenant:Configure",
816 "Configure tenant domain and port",
817 "tenant",
818 ),
819 ai("tenant:ManageMembers", "Manage tenant members", "tenant"),
820 ai("tenant:Suspend", "Suspend a tenant", "tenant"),
821 ai("tenant:Resume", "Lift a tenant suspension", "tenant"),
822 ai("tenant:List", "List all tenants (platform-level)", "tenant"),
823 ai("tenant:Read", "Read tenant info", "tenant"),
824 ai("tenant:Update", "Update tenant", "tenant"),
825 ai("tenant:Delete", "Delete tenant", "tenant"),
826 ai("tenant:CreateSubTenant", "Create sub-tenant", "tenant"),
827 ai("tenant:ManageUsers", "Manage users within tenant", "tenant"),
828 ai("tenant:ManageRoles", "Manage roles within tenant", "tenant"),
829 ai(
830 "tenant:ManagePolicies",
831 "Manage policies within tenant",
832 "tenant",
833 ),
834 ai("iam:CreateUser", "Create IAM user", "iam"),
836 ai("iam:DeleteUser", "Delete IAM user", "iam"),
837 ai("iam:ReadUser", "Read IAM user info", "iam"),
838 ai("iam:UpdateUser", "Update IAM user", "iam"),
839 ai("iam:ListUsers", "List IAM users", "iam"),
840 ai("iam:CreateGroup", "Create IAM group", "iam"),
841 ai("iam:DeleteGroup", "Delete IAM group", "iam"),
842 ai("iam:ManageGroupMembers", "Manage group membership", "iam"),
843 ai("iam:CreateRole", "Create IAM role", "iam"),
844 ai("iam:DeleteRole", "Delete IAM role", "iam"),
845 ai("iam:ReadRole", "Read IAM role", "iam"),
846 ai("iam:AssumeRole", "Assume IAM role", "iam"),
847 ai("iam:CreatePolicy", "Create IAM policy", "iam"),
848 ai("iam:DeletePolicy", "Delete IAM policy", "iam"),
849 ai("iam:ReadPolicy", "Read IAM policy", "iam"),
850 ai("iam:AttachPolicy", "Attach policy to entity", "iam"),
851 ai("iam:DetachPolicy", "Detach policy from entity", "iam"),
852 ai("iam:SetBoundary", "Set permissions boundary", "iam"),
853 ai("iam:ManageCredentials", "Manage credentials", "iam"),
854 ai("db:Query", "Query a knowledge database", "db"),
856 ai("db:Write", "Write to a knowledge database", "db"),
857 ai("db:Delete", "Delete data from database", "db"),
858 ai("db:Create", "Create a knowledge database", "db"),
859 ai("db:Drop", "Drop a knowledge database", "db"),
860 ai("db:List", "List available databases", "db"),
861 ai("db:ConfigureAccess", "Configure database access", "db"),
862 ai("db:Import", "Import data into database", "db"),
863 ai("db:Export", "Export data from database", "db"),
864 ai("chat:Send", "Send a chat message", "chat"),
866 ai("chat:ReadHistory", "Read chat history", "chat"),
867 ai("chat:DeleteConversation", "Delete a conversation", "chat"),
868 ai("chat:Stream", "Use streaming chat (SSE)", "chat"),
869 ai("persona:Create", "Create a persona", "persona"),
871 ai("persona:Delete", "Delete a persona", "persona"),
872 ai("persona:Read", "Read persona info", "persona"),
873 ai("persona:Update", "Update persona configuration", "persona"),
874 ai("persona:List", "List personas", "persona"),
875 ai("persona:Invoke", "Invoke a persona in chat", "persona"),
876 ai("room:Create", "Create a room", "room"),
878 ai("room:Delete", "Delete a room", "room"),
879 ai("room:Join", "Join a room", "room"),
880 ai("room:Read", "Read room messages", "room"),
881 ai("room:Send", "Send message to room", "room"),
882 ai("room:Manage", "Manage room settings", "room"),
883 ai("inference:ListModels", "List available models", "inference"),
885 ai("inference:Invoke", "Invoke a model", "inference"),
886 ai(
887 "inference:ConfigureRouter",
888 "Configure model routing",
889 "inference",
890 ),
891 ai("inference:ViewUsage", "View model usage", "inference"),
892 ai("analytics:ViewUsage", "View usage analytics", "analytics"),
894 ai(
895 "analytics:ViewConversations",
896 "View conversation analytics",
897 "analytics",
898 ),
899 ai("analytics:Export", "Export analytics reports", "analytics"),
900 ai("analytics:ViewActivity", "View user activity", "analytics"),
901 ai(
903 "integration:Create",
904 "Create integration bridge",
905 "integration",
906 ),
907 ai(
908 "integration:Delete",
909 "Delete integration bridge",
910 "integration",
911 ),
912 ai(
913 "integration:Configure",
914 "Configure integration",
915 "integration",
916 ),
917 ai("integration:List", "List integrations", "integration"),
918 ai(
919 "integration:Receive",
920 "Receive inbound messages",
921 "integration",
922 ),
923 ai("integration:Send", "Send outbound messages", "integration"),
924 ai("cognitive:Read", "Read cognitive state", "cognitive"),
926 ai("cognitive:Write", "Write cognitive state", "cognitive"),
927 ai("cognitive:Reset", "Reset cognitive state", "cognitive"),
928 ai("gdpr:GrantConsent", "Grant data consent", "gdpr"),
930 ai("gdpr:RevokeConsent", "Revoke data consent", "gdpr"),
931 ai("gdpr:ExportData", "Export personal data", "gdpr"),
932 ai("gdpr:EraseData", "Erase personal data", "gdpr"),
933 ai("gdpr:ViewAudit", "View GDPR audit log", "gdpr"),
934 ]
935});
936
937fn ai(action: &str, description: &str, category: &str) -> ActionInfo {
939 ActionInfo {
940 action: action.to_string(),
941 description: description.to_string(),
942 category: category.to_string(),
943 }
944}
945
946#[cfg(test)]
951mod tests {
952 use super::*;
953
954 #[test]
955 fn serde_roundtrip() {
956 let action = WamiAction::DbQuery;
957 let json = serde_json::to_string(&action).unwrap();
958 assert_eq!(json, r#""db:Query""#);
959
960 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
961 assert_eq!(parsed, WamiAction::DbQuery);
962 }
963
964 #[test]
965 fn serde_wildcard_roundtrip() {
966 let all = WamiAction::All;
967 assert_eq!(serde_json::to_string(&all).unwrap(), r#""*""#);
968
969 let svc = WamiAction::ServiceAll(WamiServicePrefix::Iam);
970 assert_eq!(serde_json::to_string(&svc).unwrap(), r#""iam:*""#);
971
972 let parsed: WamiAction = serde_json::from_str(r#""iam:*""#).unwrap();
973 assert_eq!(parsed, WamiAction::ServiceAll(WamiServicePrefix::Iam));
974 }
975
976 #[test]
977 fn from_str_roundtrip_all_actions() {
978 let actions = vec![
980 WamiAction::All,
981 WamiAction::ServiceAll(WamiServicePrefix::Db),
982 WamiAction::PlatformAdmin,
983 WamiAction::TenantCreate,
984 WamiAction::TenantRead,
985 WamiAction::IamCreateUser,
986 WamiAction::DbQuery,
987 WamiAction::ChatSend,
988 WamiAction::PersonaInvoke,
989 WamiAction::RoomCreate,
990 WamiAction::InferenceInvoke,
991 WamiAction::AnalyticsExport,
992 WamiAction::IntegrationCreate,
993 WamiAction::CognitiveRead,
994 WamiAction::GdprEraseData,
995 ];
996 for action in actions {
997 let s = action.to_string();
998 let parsed = WamiAction::from_str(&s).unwrap_or_else(|e| {
999 panic!("Failed to parse '{}': {}", s, e);
1000 });
1001 assert_eq!(parsed, action, "Roundtrip failed for {}", s);
1002 }
1003 }
1004
1005 #[test]
1006 fn from_str_errors() {
1007 assert!(WamiAction::from_str("invalid").is_err());
1008 assert!(WamiAction::from_str("unknown:Action").is_err());
1009 assert!(WamiAction::from_str("db:NonExistent").is_err());
1010 assert!(WamiAction::from_str("").is_err());
1011 }
1012
1013 #[test]
1014 fn wildcard_matching() {
1015 let all = WamiAction::All;
1016 let iam_all = WamiAction::ServiceAll(WamiServicePrefix::Iam);
1017 let create_user = WamiAction::IamCreateUser;
1018 let db_query = WamiAction::DbQuery;
1019
1020 assert!(all.matches(&create_user));
1022 assert!(all.matches(&db_query));
1023 assert!(all.matches(&iam_all));
1024
1025 assert!(iam_all.matches(&create_user));
1027 assert!(!iam_all.matches(&db_query));
1028
1029 assert!(create_user.matches(&create_user));
1031 assert!(!create_user.matches(&db_query));
1032 }
1033
1034 #[test]
1035 fn matches_str_backward_compat() {
1036 assert!(WamiAction::matches_str("*", "db:Query"));
1037 assert!(WamiAction::matches_str("db:*", "db:Query"));
1038 assert!(WamiAction::matches_str("db:Query", "db:Query"));
1039 assert!(!WamiAction::matches_str("db:*", "iam:CreateUser"));
1040 assert!(!WamiAction::matches_str("db:Query", "db:Write"));
1041 }
1042
1043 #[test]
1044 fn prefix_extraction() {
1045 assert_eq!(WamiAction::All.prefix(), None);
1046 assert_eq!(
1047 WamiAction::ServiceAll(WamiServicePrefix::Db).prefix(),
1048 Some(WamiServicePrefix::Db)
1049 );
1050 assert_eq!(WamiAction::DbQuery.prefix(), Some(WamiServicePrefix::Db));
1051 assert_eq!(
1052 WamiAction::IamCreateUser.prefix(),
1053 Some(WamiServicePrefix::Iam)
1054 );
1055 }
1056
1057 #[test]
1058 fn registry() {
1059 let all = ActionRegistry::list_actions();
1060 assert!(
1062 all.len() >= 70,
1063 "Expected at least 70 actions, got {}",
1064 all.len()
1065 );
1066
1067 let db_actions = ActionRegistry::list_by_prefix(WamiServicePrefix::Db);
1068 assert_eq!(db_actions.len(), 9);
1069 assert!(db_actions.iter().all(|a| a.category == "db"));
1070
1071 let iam_actions = ActionRegistry::list_by_prefix(WamiServicePrefix::Iam);
1072 assert_eq!(iam_actions.len(), 19);
1073 }
1074
1075 fn all_concrete_actions() -> Vec<WamiAction> {
1080 vec![
1081 WamiAction::PlatformAdmin,
1083 WamiAction::PlatformViewSettings,
1084 WamiAction::PlatformUpdateSettings,
1085 WamiAction::PlatformViewAuditLog,
1086 WamiAction::TenantCreate,
1087 WamiAction::TenantConfigure,
1088 WamiAction::TenantManageMembers,
1089 WamiAction::TenantSuspend,
1090 WamiAction::TenantResume,
1091 WamiAction::TenantList,
1092 WamiAction::TenantRead,
1094 WamiAction::TenantUpdate,
1095 WamiAction::TenantDelete,
1096 WamiAction::TenantCreateSubTenant,
1097 WamiAction::TenantManageUsers,
1098 WamiAction::TenantManageRoles,
1099 WamiAction::TenantManagePolicies,
1100 WamiAction::IamCreateUser,
1102 WamiAction::IamDeleteUser,
1103 WamiAction::IamReadUser,
1104 WamiAction::IamUpdateUser,
1105 WamiAction::IamListUsers,
1106 WamiAction::IamCreateGroup,
1107 WamiAction::IamDeleteGroup,
1108 WamiAction::IamManageGroupMembers,
1109 WamiAction::IamCreateRole,
1110 WamiAction::IamDeleteRole,
1111 WamiAction::IamReadRole,
1112 WamiAction::IamAssumeRole,
1113 WamiAction::IamCreatePolicy,
1114 WamiAction::IamDeletePolicy,
1115 WamiAction::IamReadPolicy,
1116 WamiAction::IamAttachPolicy,
1117 WamiAction::IamDetachPolicy,
1118 WamiAction::IamSetBoundary,
1119 WamiAction::IamManageCredentials,
1120 WamiAction::DbQuery,
1122 WamiAction::DbWrite,
1123 WamiAction::DbDelete,
1124 WamiAction::DbCreate,
1125 WamiAction::DbDrop,
1126 WamiAction::DbList,
1127 WamiAction::DbConfigureAccess,
1128 WamiAction::DbImport,
1129 WamiAction::DbExport,
1130 WamiAction::ChatSend,
1132 WamiAction::ChatReadHistory,
1133 WamiAction::ChatDeleteConversation,
1134 WamiAction::ChatStream,
1135 WamiAction::PersonaCreate,
1137 WamiAction::PersonaDelete,
1138 WamiAction::PersonaRead,
1139 WamiAction::PersonaUpdate,
1140 WamiAction::PersonaList,
1141 WamiAction::PersonaInvoke,
1142 WamiAction::RoomCreate,
1144 WamiAction::RoomDelete,
1145 WamiAction::RoomJoin,
1146 WamiAction::RoomRead,
1147 WamiAction::RoomSend,
1148 WamiAction::RoomManage,
1149 WamiAction::InferenceListModels,
1151 WamiAction::InferenceInvoke,
1152 WamiAction::InferenceConfigureRouter,
1153 WamiAction::InferenceViewUsage,
1154 WamiAction::AnalyticsViewUsage,
1156 WamiAction::AnalyticsViewConversations,
1157 WamiAction::AnalyticsExport,
1158 WamiAction::AnalyticsViewActivity,
1159 WamiAction::IntegrationCreate,
1161 WamiAction::IntegrationDelete,
1162 WamiAction::IntegrationConfigure,
1163 WamiAction::IntegrationList,
1164 WamiAction::IntegrationReceive,
1165 WamiAction::IntegrationSend,
1166 WamiAction::CognitiveRead,
1168 WamiAction::CognitiveWrite,
1169 WamiAction::CognitiveReset,
1170 WamiAction::GdprGrantConsent,
1172 WamiAction::GdprRevokeConsent,
1173 WamiAction::GdprExportData,
1174 WamiAction::GdprEraseData,
1175 WamiAction::GdprViewAudit,
1176 ]
1177 }
1178
1179 #[test]
1184 fn roundtrip_every_concrete_action() {
1185 for action in all_concrete_actions() {
1186 let s = action.as_str();
1187 let parsed = WamiAction::from_str(s)
1188 .unwrap_or_else(|e| panic!("from_str({:?}) failed: {}", s, e));
1189 assert_eq!(parsed, action, "roundtrip failed for {:?}", s);
1190 }
1191 }
1192
1193 #[test]
1194 fn roundtrip_all_service_wildcards() {
1195 for prefix in WamiServicePrefix::all() {
1196 let action = WamiAction::ServiceAll(*prefix);
1197 let s = action.as_str();
1198 assert!(s.ends_with(":*"), "ServiceAll as_str should end with :*");
1199 let parsed = WamiAction::from_str(s).unwrap();
1200 assert_eq!(parsed, action);
1201 }
1202 }
1203
1204 #[test]
1205 fn roundtrip_global_wildcard() {
1206 assert_eq!(WamiAction::from_str("*").unwrap(), WamiAction::All);
1207 assert_eq!(WamiAction::All.as_str(), "*");
1208 }
1209
1210 #[test]
1215 fn display_fromstr_every_action() {
1216 for action in all_concrete_actions() {
1217 let display = action.to_string();
1218 let parsed: WamiAction = display.parse().unwrap();
1219 assert_eq!(parsed, action);
1220 }
1221 let all_display = WamiAction::All.to_string();
1223 assert_eq!(all_display, "*");
1224 assert_eq!(all_display.parse::<WamiAction>().unwrap(), WamiAction::All);
1225
1226 for prefix in WamiServicePrefix::all() {
1227 let svc = WamiAction::ServiceAll(*prefix);
1228 let display = svc.to_string();
1229 assert_eq!(display.parse::<WamiAction>().unwrap(), svc);
1230 }
1231 }
1232
1233 #[test]
1238 fn prefix_every_concrete_action() {
1239 let expected: Vec<(WamiAction, WamiServicePrefix)> = vec![
1240 (WamiAction::PlatformAdmin, WamiServicePrefix::Platform),
1241 (
1242 WamiAction::PlatformViewSettings,
1243 WamiServicePrefix::Platform,
1244 ),
1245 (
1246 WamiAction::PlatformUpdateSettings,
1247 WamiServicePrefix::Platform,
1248 ),
1249 (
1250 WamiAction::PlatformViewAuditLog,
1251 WamiServicePrefix::Platform,
1252 ),
1253 (WamiAction::TenantCreate, WamiServicePrefix::Tenant),
1254 (WamiAction::TenantConfigure, WamiServicePrefix::Tenant),
1255 (WamiAction::TenantManageMembers, WamiServicePrefix::Tenant),
1256 (WamiAction::TenantSuspend, WamiServicePrefix::Tenant),
1257 (WamiAction::TenantResume, WamiServicePrefix::Tenant),
1258 (WamiAction::TenantList, WamiServicePrefix::Tenant),
1259 (WamiAction::TenantRead, WamiServicePrefix::Tenant),
1260 (WamiAction::TenantUpdate, WamiServicePrefix::Tenant),
1261 (WamiAction::TenantDelete, WamiServicePrefix::Tenant),
1262 (WamiAction::TenantCreateSubTenant, WamiServicePrefix::Tenant),
1263 (WamiAction::TenantManageUsers, WamiServicePrefix::Tenant),
1264 (WamiAction::TenantManageRoles, WamiServicePrefix::Tenant),
1265 (WamiAction::TenantManagePolicies, WamiServicePrefix::Tenant),
1266 (WamiAction::IamCreateUser, WamiServicePrefix::Iam),
1267 (WamiAction::IamDeleteUser, WamiServicePrefix::Iam),
1268 (WamiAction::IamReadUser, WamiServicePrefix::Iam),
1269 (WamiAction::IamUpdateUser, WamiServicePrefix::Iam),
1270 (WamiAction::IamListUsers, WamiServicePrefix::Iam),
1271 (WamiAction::IamCreateGroup, WamiServicePrefix::Iam),
1272 (WamiAction::IamDeleteGroup, WamiServicePrefix::Iam),
1273 (WamiAction::IamManageGroupMembers, WamiServicePrefix::Iam),
1274 (WamiAction::IamCreateRole, WamiServicePrefix::Iam),
1275 (WamiAction::IamDeleteRole, WamiServicePrefix::Iam),
1276 (WamiAction::IamReadRole, WamiServicePrefix::Iam),
1277 (WamiAction::IamAssumeRole, WamiServicePrefix::Iam),
1278 (WamiAction::IamCreatePolicy, WamiServicePrefix::Iam),
1279 (WamiAction::IamDeletePolicy, WamiServicePrefix::Iam),
1280 (WamiAction::IamReadPolicy, WamiServicePrefix::Iam),
1281 (WamiAction::IamAttachPolicy, WamiServicePrefix::Iam),
1282 (WamiAction::IamDetachPolicy, WamiServicePrefix::Iam),
1283 (WamiAction::IamSetBoundary, WamiServicePrefix::Iam),
1284 (WamiAction::IamManageCredentials, WamiServicePrefix::Iam),
1285 (WamiAction::DbQuery, WamiServicePrefix::Db),
1286 (WamiAction::DbWrite, WamiServicePrefix::Db),
1287 (WamiAction::DbDelete, WamiServicePrefix::Db),
1288 (WamiAction::DbCreate, WamiServicePrefix::Db),
1289 (WamiAction::DbDrop, WamiServicePrefix::Db),
1290 (WamiAction::DbList, WamiServicePrefix::Db),
1291 (WamiAction::DbConfigureAccess, WamiServicePrefix::Db),
1292 (WamiAction::DbImport, WamiServicePrefix::Db),
1293 (WamiAction::DbExport, WamiServicePrefix::Db),
1294 (WamiAction::ChatSend, WamiServicePrefix::Chat),
1295 (WamiAction::ChatReadHistory, WamiServicePrefix::Chat),
1296 (WamiAction::ChatDeleteConversation, WamiServicePrefix::Chat),
1297 (WamiAction::ChatStream, WamiServicePrefix::Chat),
1298 (WamiAction::PersonaCreate, WamiServicePrefix::Persona),
1299 (WamiAction::PersonaDelete, WamiServicePrefix::Persona),
1300 (WamiAction::PersonaRead, WamiServicePrefix::Persona),
1301 (WamiAction::PersonaUpdate, WamiServicePrefix::Persona),
1302 (WamiAction::PersonaList, WamiServicePrefix::Persona),
1303 (WamiAction::PersonaInvoke, WamiServicePrefix::Persona),
1304 (WamiAction::RoomCreate, WamiServicePrefix::Room),
1305 (WamiAction::RoomDelete, WamiServicePrefix::Room),
1306 (WamiAction::RoomJoin, WamiServicePrefix::Room),
1307 (WamiAction::RoomRead, WamiServicePrefix::Room),
1308 (WamiAction::RoomSend, WamiServicePrefix::Room),
1309 (WamiAction::RoomManage, WamiServicePrefix::Room),
1310 (
1311 WamiAction::InferenceListModels,
1312 WamiServicePrefix::Inference,
1313 ),
1314 (WamiAction::InferenceInvoke, WamiServicePrefix::Inference),
1315 (
1316 WamiAction::InferenceConfigureRouter,
1317 WamiServicePrefix::Inference,
1318 ),
1319 (WamiAction::InferenceViewUsage, WamiServicePrefix::Inference),
1320 (WamiAction::AnalyticsViewUsage, WamiServicePrefix::Analytics),
1321 (
1322 WamiAction::AnalyticsViewConversations,
1323 WamiServicePrefix::Analytics,
1324 ),
1325 (WamiAction::AnalyticsExport, WamiServicePrefix::Analytics),
1326 (
1327 WamiAction::AnalyticsViewActivity,
1328 WamiServicePrefix::Analytics,
1329 ),
1330 (
1331 WamiAction::IntegrationCreate,
1332 WamiServicePrefix::Integration,
1333 ),
1334 (
1335 WamiAction::IntegrationDelete,
1336 WamiServicePrefix::Integration,
1337 ),
1338 (
1339 WamiAction::IntegrationConfigure,
1340 WamiServicePrefix::Integration,
1341 ),
1342 (WamiAction::IntegrationList, WamiServicePrefix::Integration),
1343 (
1344 WamiAction::IntegrationReceive,
1345 WamiServicePrefix::Integration,
1346 ),
1347 (WamiAction::IntegrationSend, WamiServicePrefix::Integration),
1348 (WamiAction::CognitiveRead, WamiServicePrefix::Cognitive),
1349 (WamiAction::CognitiveWrite, WamiServicePrefix::Cognitive),
1350 (WamiAction::CognitiveReset, WamiServicePrefix::Cognitive),
1351 (WamiAction::GdprGrantConsent, WamiServicePrefix::Gdpr),
1352 (WamiAction::GdprRevokeConsent, WamiServicePrefix::Gdpr),
1353 (WamiAction::GdprExportData, WamiServicePrefix::Gdpr),
1354 (WamiAction::GdprEraseData, WamiServicePrefix::Gdpr),
1355 (WamiAction::GdprViewAudit, WamiServicePrefix::Gdpr),
1356 ];
1357
1358 for (action, expected_prefix) in &expected {
1359 assert_eq!(
1360 action.prefix(),
1361 Some(*expected_prefix),
1362 "prefix() wrong for {:?}",
1363 action
1364 );
1365 }
1366 }
1367
1368 #[test]
1369 fn prefix_wildcards() {
1370 assert_eq!(WamiAction::All.prefix(), None);
1371 for prefix in WamiServicePrefix::all() {
1372 assert_eq!(WamiAction::ServiceAll(*prefix).prefix(), Some(*prefix),);
1373 }
1374 }
1375
1376 #[test]
1381 fn as_str_platform() {
1382 assert_eq!(WamiAction::PlatformAdmin.as_str(), "platform:Admin");
1383 assert_eq!(
1384 WamiAction::PlatformViewSettings.as_str(),
1385 "platform:ViewSettings"
1386 );
1387 assert_eq!(
1388 WamiAction::PlatformUpdateSettings.as_str(),
1389 "platform:UpdateSettings"
1390 );
1391 assert_eq!(
1392 WamiAction::PlatformViewAuditLog.as_str(),
1393 "platform:ViewAuditLog"
1394 );
1395 }
1396
1397 #[test]
1398 fn as_str_tenant_lifecycle() {
1399 assert_eq!(WamiAction::TenantCreate.as_str(), "tenant:Create");
1400 assert_eq!(WamiAction::TenantConfigure.as_str(), "tenant:Configure");
1401 assert_eq!(
1402 WamiAction::TenantManageMembers.as_str(),
1403 "tenant:ManageMembers"
1404 );
1405 assert_eq!(WamiAction::TenantSuspend.as_str(), "tenant:Suspend");
1406 assert_eq!(WamiAction::TenantResume.as_str(), "tenant:Resume");
1407 assert_eq!(WamiAction::TenantList.as_str(), "tenant:List");
1408 }
1409
1410 #[test]
1411 fn as_str_tenant() {
1412 assert_eq!(WamiAction::TenantRead.as_str(), "tenant:Read");
1413 assert_eq!(WamiAction::TenantUpdate.as_str(), "tenant:Update");
1414 assert_eq!(WamiAction::TenantDelete.as_str(), "tenant:Delete");
1415 assert_eq!(
1416 WamiAction::TenantCreateSubTenant.as_str(),
1417 "tenant:CreateSubTenant"
1418 );
1419 assert_eq!(WamiAction::TenantManageUsers.as_str(), "tenant:ManageUsers");
1420 assert_eq!(WamiAction::TenantManageRoles.as_str(), "tenant:ManageRoles");
1421 assert_eq!(
1422 WamiAction::TenantManagePolicies.as_str(),
1423 "tenant:ManagePolicies"
1424 );
1425 }
1426
1427 #[test]
1428 fn as_str_iam() {
1429 assert_eq!(WamiAction::IamCreateUser.as_str(), "iam:CreateUser");
1430 assert_eq!(WamiAction::IamDeleteUser.as_str(), "iam:DeleteUser");
1431 assert_eq!(WamiAction::IamReadUser.as_str(), "iam:ReadUser");
1432 assert_eq!(WamiAction::IamUpdateUser.as_str(), "iam:UpdateUser");
1433 assert_eq!(WamiAction::IamListUsers.as_str(), "iam:ListUsers");
1434 assert_eq!(WamiAction::IamCreateGroup.as_str(), "iam:CreateGroup");
1435 assert_eq!(WamiAction::IamDeleteGroup.as_str(), "iam:DeleteGroup");
1436 assert_eq!(
1437 WamiAction::IamManageGroupMembers.as_str(),
1438 "iam:ManageGroupMembers"
1439 );
1440 assert_eq!(WamiAction::IamCreateRole.as_str(), "iam:CreateRole");
1441 assert_eq!(WamiAction::IamDeleteRole.as_str(), "iam:DeleteRole");
1442 assert_eq!(WamiAction::IamReadRole.as_str(), "iam:ReadRole");
1443 assert_eq!(WamiAction::IamAssumeRole.as_str(), "iam:AssumeRole");
1444 assert_eq!(WamiAction::IamCreatePolicy.as_str(), "iam:CreatePolicy");
1445 assert_eq!(WamiAction::IamDeletePolicy.as_str(), "iam:DeletePolicy");
1446 assert_eq!(WamiAction::IamReadPolicy.as_str(), "iam:ReadPolicy");
1447 assert_eq!(WamiAction::IamAttachPolicy.as_str(), "iam:AttachPolicy");
1448 assert_eq!(WamiAction::IamDetachPolicy.as_str(), "iam:DetachPolicy");
1449 assert_eq!(WamiAction::IamSetBoundary.as_str(), "iam:SetBoundary");
1450 assert_eq!(
1451 WamiAction::IamManageCredentials.as_str(),
1452 "iam:ManageCredentials"
1453 );
1454 }
1455
1456 #[test]
1457 fn as_str_db() {
1458 assert_eq!(WamiAction::DbQuery.as_str(), "db:Query");
1459 assert_eq!(WamiAction::DbWrite.as_str(), "db:Write");
1460 assert_eq!(WamiAction::DbDelete.as_str(), "db:Delete");
1461 assert_eq!(WamiAction::DbCreate.as_str(), "db:Create");
1462 assert_eq!(WamiAction::DbDrop.as_str(), "db:Drop");
1463 assert_eq!(WamiAction::DbList.as_str(), "db:List");
1464 assert_eq!(WamiAction::DbConfigureAccess.as_str(), "db:ConfigureAccess");
1465 assert_eq!(WamiAction::DbImport.as_str(), "db:Import");
1466 assert_eq!(WamiAction::DbExport.as_str(), "db:Export");
1467 }
1468
1469 #[test]
1470 fn as_str_chat() {
1471 assert_eq!(WamiAction::ChatSend.as_str(), "chat:Send");
1472 assert_eq!(WamiAction::ChatReadHistory.as_str(), "chat:ReadHistory");
1473 assert_eq!(
1474 WamiAction::ChatDeleteConversation.as_str(),
1475 "chat:DeleteConversation"
1476 );
1477 assert_eq!(WamiAction::ChatStream.as_str(), "chat:Stream");
1478 }
1479
1480 #[test]
1481 fn as_str_persona() {
1482 assert_eq!(WamiAction::PersonaCreate.as_str(), "persona:Create");
1483 assert_eq!(WamiAction::PersonaDelete.as_str(), "persona:Delete");
1484 assert_eq!(WamiAction::PersonaRead.as_str(), "persona:Read");
1485 assert_eq!(WamiAction::PersonaUpdate.as_str(), "persona:Update");
1486 assert_eq!(WamiAction::PersonaList.as_str(), "persona:List");
1487 assert_eq!(WamiAction::PersonaInvoke.as_str(), "persona:Invoke");
1488 }
1489
1490 #[test]
1491 fn as_str_room() {
1492 assert_eq!(WamiAction::RoomCreate.as_str(), "room:Create");
1493 assert_eq!(WamiAction::RoomDelete.as_str(), "room:Delete");
1494 assert_eq!(WamiAction::RoomJoin.as_str(), "room:Join");
1495 assert_eq!(WamiAction::RoomRead.as_str(), "room:Read");
1496 assert_eq!(WamiAction::RoomSend.as_str(), "room:Send");
1497 assert_eq!(WamiAction::RoomManage.as_str(), "room:Manage");
1498 }
1499
1500 #[test]
1501 fn as_str_inference() {
1502 assert_eq!(
1503 WamiAction::InferenceListModels.as_str(),
1504 "inference:ListModels"
1505 );
1506 assert_eq!(WamiAction::InferenceInvoke.as_str(), "inference:Invoke");
1507 assert_eq!(
1508 WamiAction::InferenceConfigureRouter.as_str(),
1509 "inference:ConfigureRouter"
1510 );
1511 assert_eq!(
1512 WamiAction::InferenceViewUsage.as_str(),
1513 "inference:ViewUsage"
1514 );
1515 }
1516
1517 #[test]
1518 fn as_str_analytics() {
1519 assert_eq!(
1520 WamiAction::AnalyticsViewUsage.as_str(),
1521 "analytics:ViewUsage"
1522 );
1523 assert_eq!(
1524 WamiAction::AnalyticsViewConversations.as_str(),
1525 "analytics:ViewConversations"
1526 );
1527 assert_eq!(WamiAction::AnalyticsExport.as_str(), "analytics:Export");
1528 assert_eq!(
1529 WamiAction::AnalyticsViewActivity.as_str(),
1530 "analytics:ViewActivity"
1531 );
1532 }
1533
1534 #[test]
1535 fn as_str_integration() {
1536 assert_eq!(WamiAction::IntegrationCreate.as_str(), "integration:Create");
1537 assert_eq!(WamiAction::IntegrationDelete.as_str(), "integration:Delete");
1538 assert_eq!(
1539 WamiAction::IntegrationConfigure.as_str(),
1540 "integration:Configure"
1541 );
1542 assert_eq!(WamiAction::IntegrationList.as_str(), "integration:List");
1543 assert_eq!(
1544 WamiAction::IntegrationReceive.as_str(),
1545 "integration:Receive"
1546 );
1547 assert_eq!(WamiAction::IntegrationSend.as_str(), "integration:Send");
1548 }
1549
1550 #[test]
1551 fn as_str_cognitive() {
1552 assert_eq!(WamiAction::CognitiveRead.as_str(), "cognitive:Read");
1553 assert_eq!(WamiAction::CognitiveWrite.as_str(), "cognitive:Write");
1554 assert_eq!(WamiAction::CognitiveReset.as_str(), "cognitive:Reset");
1555 }
1556
1557 #[test]
1558 fn as_str_gdpr() {
1559 assert_eq!(WamiAction::GdprGrantConsent.as_str(), "gdpr:GrantConsent");
1560 assert_eq!(WamiAction::GdprRevokeConsent.as_str(), "gdpr:RevokeConsent");
1561 assert_eq!(WamiAction::GdprExportData.as_str(), "gdpr:ExportData");
1562 assert_eq!(WamiAction::GdprEraseData.as_str(), "gdpr:EraseData");
1563 assert_eq!(WamiAction::GdprViewAudit.as_str(), "gdpr:ViewAudit");
1564 }
1565
1566 #[test]
1571 fn as_str_service_all_every_prefix() {
1572 assert_eq!(
1573 WamiAction::ServiceAll(WamiServicePrefix::Platform).as_str(),
1574 "platform:*"
1575 );
1576 assert_eq!(
1577 WamiAction::ServiceAll(WamiServicePrefix::Tenant).as_str(),
1578 "tenant:*"
1579 );
1580 assert_eq!(
1581 WamiAction::ServiceAll(WamiServicePrefix::Tenant).as_str(),
1582 "tenant:*"
1583 );
1584 assert_eq!(
1585 WamiAction::ServiceAll(WamiServicePrefix::Iam).as_str(),
1586 "iam:*"
1587 );
1588 assert_eq!(
1589 WamiAction::ServiceAll(WamiServicePrefix::Db).as_str(),
1590 "db:*"
1591 );
1592 assert_eq!(
1593 WamiAction::ServiceAll(WamiServicePrefix::Chat).as_str(),
1594 "chat:*"
1595 );
1596 assert_eq!(
1597 WamiAction::ServiceAll(WamiServicePrefix::Persona).as_str(),
1598 "persona:*"
1599 );
1600 assert_eq!(
1601 WamiAction::ServiceAll(WamiServicePrefix::Room).as_str(),
1602 "room:*"
1603 );
1604 assert_eq!(
1605 WamiAction::ServiceAll(WamiServicePrefix::Inference).as_str(),
1606 "inference:*"
1607 );
1608 assert_eq!(
1609 WamiAction::ServiceAll(WamiServicePrefix::Analytics).as_str(),
1610 "analytics:*"
1611 );
1612 assert_eq!(
1613 WamiAction::ServiceAll(WamiServicePrefix::Integration).as_str(),
1614 "integration:*"
1615 );
1616 assert_eq!(
1617 WamiAction::ServiceAll(WamiServicePrefix::Cognitive).as_str(),
1618 "cognitive:*"
1619 );
1620 assert_eq!(
1621 WamiAction::ServiceAll(WamiServicePrefix::Gdpr).as_str(),
1622 "gdpr:*"
1623 );
1624 }
1625
1626 #[test]
1631 fn matches_all_matches_everything() {
1632 let all = WamiAction::All;
1633 for action in all_concrete_actions() {
1634 assert!(all.matches(&action), "All should match {:?}", action);
1635 }
1636 for prefix in WamiServicePrefix::all() {
1638 assert!(all.matches(&WamiAction::ServiceAll(*prefix)));
1639 }
1640 assert!(all.matches(&WamiAction::All));
1641 }
1642
1643 #[test]
1644 fn service_all_matches_only_own_prefix() {
1645 let actions = all_concrete_actions();
1648 for prefix in WamiServicePrefix::all() {
1649 let svc_all = WamiAction::ServiceAll(*prefix);
1650 for action in &actions {
1651 if action.prefix() == Some(*prefix) {
1652 assert!(
1653 svc_all.matches(action),
1654 "{:?} should match {:?}",
1655 svc_all,
1656 action
1657 );
1658 } else {
1659 assert!(
1660 !svc_all.matches(action),
1661 "{:?} should NOT match {:?}",
1662 svc_all,
1663 action
1664 );
1665 }
1666 }
1667 }
1668 }
1669
1670 #[test]
1671 fn exact_match_only_matches_self() {
1672 let actions = all_concrete_actions();
1673 for a in &actions {
1674 assert!(a.matches(a), "{:?} should match itself", a);
1675 for b in &actions {
1676 if a != b {
1677 assert!(!a.matches(b), "{:?} should not match {:?}", a, b);
1678 }
1679 }
1680 }
1681 }
1682
1683 #[test]
1684 fn concrete_does_not_match_service_all() {
1685 let iam_create = WamiAction::IamCreateUser;
1687 let iam_all = WamiAction::ServiceAll(WamiServicePrefix::Iam);
1688 assert!(!iam_create.matches(&iam_all));
1689 }
1690
1691 #[test]
1696 fn matches_str_star_matches_everything() {
1697 for action in all_concrete_actions() {
1698 assert!(WamiAction::matches_str("*", action.as_str()));
1699 }
1700 }
1701
1702 #[test]
1703 fn matches_str_service_wildcard_every_prefix() {
1704 let prefixes_and_actions: Vec<(&str, &str, &str)> = vec![
1705 ("platform:*", "platform:Admin", "tenant:Create"),
1706 ("platform:*", "platform:Admin", "tenant:Read"),
1707 ("tenant:*", "tenant:Read", "iam:CreateUser"),
1708 ("iam:*", "iam:CreateUser", "db:Query"),
1709 ("db:*", "db:Query", "chat:Send"),
1710 ("chat:*", "chat:Send", "persona:Create"),
1711 ("persona:*", "persona:Create", "room:Create"),
1712 ("room:*", "room:Create", "inference:Invoke"),
1713 ("inference:*", "inference:Invoke", "analytics:Export"),
1714 ("analytics:*", "analytics:Export", "integration:Create"),
1715 ("integration:*", "integration:Create", "cognitive:Read"),
1716 ("cognitive:*", "cognitive:Read", "gdpr:GrantConsent"),
1717 ("gdpr:*", "gdpr:GrantConsent", "platform:Admin"),
1718 ];
1719 for (pattern, should_match, should_not_match) in prefixes_and_actions {
1720 assert!(
1721 WamiAction::matches_str(pattern, should_match),
1722 "{} should match {}",
1723 pattern,
1724 should_match
1725 );
1726 assert!(
1727 !WamiAction::matches_str(pattern, should_not_match),
1728 "{} should NOT match {}",
1729 pattern,
1730 should_not_match
1731 );
1732 }
1733 }
1734
1735 #[test]
1736 fn matches_str_exact() {
1737 assert!(WamiAction::matches_str("iam:CreateUser", "iam:CreateUser"));
1738 assert!(!WamiAction::matches_str("iam:CreateUser", "iam:DeleteUser"));
1739 }
1740
1741 #[test]
1742 fn matches_str_no_colon_in_requested() {
1743 assert!(!WamiAction::matches_str("iam:*", "noColonHere"));
1745 }
1746
1747 #[test]
1748 fn matches_str_non_matching_patterns() {
1749 assert!(!WamiAction::matches_str("db:Query", "db:Write"));
1750 assert!(!WamiAction::matches_str("iam:*", "db:Query"));
1751 assert!(!WamiAction::matches_str("platform:Admin", "*"));
1752 }
1753
1754 #[test]
1759 fn from_str_empty_string() {
1760 let err = WamiAction::from_str("").unwrap_err();
1761 assert!(matches!(err, ActionParseError::InvalidFormat(_)));
1762 }
1763
1764 #[test]
1765 fn from_str_no_colon() {
1766 let err = WamiAction::from_str("noColon").unwrap_err();
1767 assert!(matches!(err, ActionParseError::InvalidFormat(_)));
1768 }
1769
1770 #[test]
1771 fn from_str_unknown_prefix() {
1772 let err = WamiAction::from_str("fake:*").unwrap_err();
1773 assert!(matches!(err, ActionParseError::UnknownPrefix(_)));
1774 }
1775
1776 #[test]
1777 fn from_str_unknown_operation() {
1778 let err = WamiAction::from_str("iam:FakeOp").unwrap_err();
1779 assert!(matches!(err, ActionParseError::Unknown(_)));
1780 }
1781
1782 #[test]
1783 fn from_str_case_sensitive() {
1784 assert!(WamiAction::from_str("IAM:CreateUser").is_err());
1786 assert!(WamiAction::from_str("iam:createuser").is_err());
1787 assert!(WamiAction::from_str("Iam:CreateUser").is_err());
1788 assert!(WamiAction::from_str("iam:createUser").is_err());
1789 }
1790
1791 #[test]
1792 fn from_str_valid_prefix_wrong_operation() {
1793 assert!(WamiAction::from_str("db:NotAnAction").is_err());
1794 assert!(WamiAction::from_str("chat:NotAnAction").is_err());
1795 assert!(WamiAction::from_str("room:NotAnAction").is_err());
1796 }
1797
1798 #[test]
1803 fn service_prefix_as_str_all() {
1804 assert_eq!(WamiServicePrefix::Platform.as_str(), "platform");
1805 assert_eq!(WamiServicePrefix::Tenant.as_str(), "tenant");
1806 assert_eq!(WamiServicePrefix::Iam.as_str(), "iam");
1807 assert_eq!(WamiServicePrefix::Db.as_str(), "db");
1808 assert_eq!(WamiServicePrefix::Chat.as_str(), "chat");
1809 assert_eq!(WamiServicePrefix::Persona.as_str(), "persona");
1810 assert_eq!(WamiServicePrefix::Room.as_str(), "room");
1811 assert_eq!(WamiServicePrefix::Inference.as_str(), "inference");
1812 assert_eq!(WamiServicePrefix::Analytics.as_str(), "analytics");
1813 assert_eq!(WamiServicePrefix::Integration.as_str(), "integration");
1814 assert_eq!(WamiServicePrefix::Cognitive.as_str(), "cognitive");
1815 assert_eq!(WamiServicePrefix::Gdpr.as_str(), "gdpr");
1816 }
1817
1818 #[test]
1819 fn service_prefix_from_str_all() {
1820 for prefix in WamiServicePrefix::all() {
1821 let s = prefix.as_str();
1822 let parsed = WamiServicePrefix::from_str(s).unwrap();
1823 assert_eq!(parsed, *prefix);
1824 }
1825 }
1826
1827 #[test]
1828 fn service_prefix_from_str_error() {
1829 let err = WamiServicePrefix::from_str("nonexistent").unwrap_err();
1830 assert!(matches!(err, ActionParseError::UnknownPrefix(_)));
1831 }
1832
1833 #[test]
1834 fn service_prefix_display() {
1835 for prefix in WamiServicePrefix::all() {
1836 assert_eq!(prefix.to_string(), prefix.as_str());
1837 }
1838 }
1839
1840 #[test]
1841 fn service_prefix_all_returns_all_12() {
1842 assert_eq!(WamiServicePrefix::all().len(), 12);
1843 }
1844
1845 #[test]
1850 fn serde_roundtrip_every_action() {
1851 for action in all_concrete_actions() {
1852 let json = serde_json::to_string(&action).unwrap();
1853 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
1854 assert_eq!(parsed, action);
1855 }
1856 }
1857
1858 #[test]
1859 fn serde_roundtrip_every_service_wildcard() {
1860 for prefix in WamiServicePrefix::all() {
1861 let action = WamiAction::ServiceAll(*prefix);
1862 let json = serde_json::to_string(&action).unwrap();
1863 let parsed: WamiAction = serde_json::from_str(&json).unwrap();
1864 assert_eq!(parsed, action);
1865 }
1866 }
1867
1868 #[test]
1869 fn serde_deserialize_invalid() {
1870 let result: Result<WamiAction, _> = serde_json::from_str(r#""not:valid:action""#);
1871 assert!(result.is_err());
1872
1873 let result: Result<WamiAction, _> = serde_json::from_str(r#""garbage""#);
1874 assert!(result.is_err());
1875 }
1876
1877 #[test]
1882 fn action_parse_error_display() {
1883 let e1 = ActionParseError::Unknown("foo:bar".into());
1884 assert!(e1.to_string().contains("foo:bar"));
1885
1886 let e2 = ActionParseError::UnknownPrefix("xyz".into());
1887 assert!(e2.to_string().contains("xyz"));
1888
1889 let e3 = ActionParseError::InvalidFormat("nocolon".into());
1890 assert!(e3.to_string().contains("nocolon"));
1891 }
1892
1893 #[test]
1898 fn registry_list_by_every_prefix() {
1899 let expected_counts: Vec<(WamiServicePrefix, usize)> = vec![
1900 (WamiServicePrefix::Platform, 4),
1901 (WamiServicePrefix::Tenant, 13),
1904 (WamiServicePrefix::Iam, 19),
1905 (WamiServicePrefix::Db, 9),
1906 (WamiServicePrefix::Chat, 4),
1907 (WamiServicePrefix::Persona, 6),
1908 (WamiServicePrefix::Room, 6),
1909 (WamiServicePrefix::Inference, 4),
1910 (WamiServicePrefix::Analytics, 4),
1911 (WamiServicePrefix::Integration, 6),
1912 (WamiServicePrefix::Cognitive, 3),
1913 (WamiServicePrefix::Gdpr, 5),
1914 ];
1915 for (prefix, expected) in expected_counts {
1916 let actions = ActionRegistry::list_by_prefix(prefix);
1917 assert_eq!(
1918 actions.len(),
1919 expected,
1920 "Wrong count for {:?}: got {}, expected {}",
1921 prefix,
1922 actions.len(),
1923 expected
1924 );
1925 for a in &actions {
1926 assert_eq!(a.category, prefix.as_str());
1927 }
1928 }
1929 }
1930
1931 #[test]
1932 fn registry_list_prefixes() {
1933 let prefixes = ActionRegistry::list_prefixes();
1934 assert_eq!(prefixes.len(), 12);
1935 }
1936
1937 #[test]
1938 fn registry_total_action_count() {
1939 let all = ActionRegistry::list_actions();
1940 assert_eq!(all.len(), 83);
1942 }
1943}