role_system/
permission.rs

1//! Permission definitions and validation with enhanced three-part format support.
2
3use crate::error::{Error, Result};
4use std::collections::HashMap;
5use std::sync::Arc;
6
7/// A permission represents an action that can be performed on a resource type.
8///
9/// Permissions follow the format "action:resource" or "action:resource:instance"
10/// Examples:
11/// - "read:documents" - Read any document
12/// - "read:documents:doc123" - Read specific document doc123
13/// - "admin:users" - Admin access to users
14/// - "admin:users:user456" - Admin access to specific user
15///
16/// Special permissions:
17/// - "*:*" grants all permissions (super admin)
18/// - "action:*" grants all actions on any resource
19/// - "*:resource" grants any action on a specific resource
20/// - "action:resource:*" grants action on any instance of resource
21#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
22pub struct Permission {
23    /// The action being performed (e.g., "read", "write", "delete").
24    action: String,
25    /// The resource type this permission applies to (e.g., "documents", "users").
26    resource_type: String,
27    /// Optional specific instance of the resource (e.g., "doc123", "user456").
28    instance: Option<String>,
29    /// Optional conditional validator function.
30    #[cfg_attr(feature = "persistence", serde(skip))]
31    condition: Option<PermissionCondition>,
32}
33
34/// A condition that must be met for a permission to be granted.
35pub type PermissionCondition = Arc<dyn Fn(&HashMap<String, String>) -> bool + Send + Sync>;
36
37impl Permission {
38    /// Create a new permission for an action on a resource type.
39    pub fn new(action: impl Into<String>, resource_type: impl Into<String>) -> Self {
40        let action = action.into();
41        let resource_type = resource_type.into();
42
43        // Use specialized permission validation that allows wildcards
44        Self::validate_permission_field(&action, "action").expect("Invalid action in permission");
45        Self::validate_permission_field(&resource_type, "resource_type")
46            .expect("Invalid resource_type in permission");
47
48        Self {
49            action,
50            resource_type,
51            instance: None,
52            condition: None,
53        }
54    }
55
56    /// Try to create a new permission, returning an error if validation fails.
57    pub fn try_new(action: impl Into<String>, resource_type: impl Into<String>) -> Result<Self> {
58        let action = action.into();
59        let resource_type = resource_type.into();
60
61        // Use specialized permission validation that allows wildcards
62        Self::validate_permission_field(&action, "action")?;
63        Self::validate_permission_field(&resource_type, "resource_type")?;
64
65        Ok(Self {
66            action,
67            resource_type,
68            instance: None,
69            condition: None,
70        })
71    }
72
73    /// Create a new permission for an action on a specific resource instance.
74    pub fn with_instance(
75        action: impl Into<String>,
76        resource_type: impl Into<String>,
77        instance: impl Into<String>,
78    ) -> Self {
79        let mut permission = Self::new(action, resource_type);
80        let instance = instance.into();
81
82        Self::validate_permission_field(&instance, "instance")
83            .expect("Invalid instance in permission");
84
85        permission.instance = Some(instance.to_owned());
86        permission
87    }
88
89    /// Create a permission with a conditional validator.
90    pub fn with_condition<F>(
91        action: impl Into<String>,
92        resource_type: impl Into<String>,
93        condition: F,
94    ) -> Self
95    where
96        F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
97    {
98        let mut permission = Self::new(action, resource_type);
99        permission.condition = Some(Arc::new(condition));
100        permission
101    }
102
103    /// Create a permission with both instance and condition.
104    pub fn with_instance_and_condition<F>(
105        action: impl Into<String>,
106        resource_type: impl Into<String>,
107        instance: impl Into<String>,
108        condition: F,
109    ) -> Self
110    where
111        F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
112    {
113        let mut permission = Self::with_instance(action, resource_type, instance);
114        permission.condition = Some(Arc::new(condition));
115        permission
116    }
117
118    /// Create a wildcard permission that grants access to all actions on a resource type.
119    pub fn wildcard(resource_type: impl Into<String>) -> Self {
120        Self::new("*", resource_type)
121    }
122
123    /// Create a super-admin permission that grants access to everything.
124    pub fn super_admin() -> Self {
125        Self::new("*", "*")
126    }
127
128    /// Create a permission with enhanced context awareness.
129    ///
130    /// # Example
131    /// ```rust
132    /// use role_system::permission::Permission;
133    /// let perm = Permission::with_context("users", "read", Some("own_data"));
134    /// ```
135    pub fn with_context(
136        resource_type: impl Into<String>,
137        action: impl Into<String>,
138        context: Option<impl Into<String>>,
139    ) -> Self {
140        let mut permission = Self::new(action, resource_type);
141        if let Some(ctx) = context {
142            permission.instance = Some(ctx.into());
143        }
144        permission
145    }
146
147    /// Create a permission with multiple scopes/actions.
148    ///
149    /// # Example
150    /// ```rust
151    /// use role_system::permission::Permission;
152    /// let perms = Permission::with_scope("users", "read", vec!["profile", "preferences"]);
153    /// ```
154    pub fn with_scope(
155        resource_type: impl Into<String>,
156        action: impl Into<String>,
157        scopes: Vec<impl Into<String>>,
158    ) -> Vec<Self> {
159        let resource_type = resource_type.into();
160        let action = action.into();
161
162        scopes
163            .into_iter()
164            .map(|scope| Self::with_instance(action.clone(), resource_type.clone(), scope.into()))
165            .collect()
166    }
167
168    /// Create a conditional permission that depends on context.
169    ///
170    /// # Example
171    /// ```rust
172    /// use role_system::permission::Permission;
173    /// let perm = Permission::conditional("users", "update")
174    ///     .when(|context| context.get("user_id") == context.get("target_id"));
175    /// ```
176    pub fn conditional(
177        resource_type: impl Into<String>,
178        action: impl Into<String>,
179    ) -> ConditionalPermissionBuilder {
180        ConditionalPermissionBuilder::new(resource_type, action)
181    }
182
183    /// Get the action this permission grants.
184    pub fn action(&self) -> &str {
185        &self.action
186    }
187
188    /// Get the resource type this permission applies to.
189    pub fn resource_type(&self) -> &str {
190        &self.resource_type
191    }
192
193    /// Get the specific instance this permission applies to, if any.
194    pub fn instance(&self) -> Option<&str> {
195        self.instance.as_deref()
196    }
197
198    /// Check if this permission matches the given action and resource type.
199    /// For backward compatibility, this doesn't consider instances.
200    pub fn matches(&self, action: &str, resource_type: &str) -> bool {
201        let action_match = self.action == "*" || self.action == action;
202        let resource_match = self.resource_type == "*" || self.resource_type == resource_type;
203        action_match && resource_match
204    }
205
206    /// Check if this permission matches the given action, resource type, and optional instance.
207    pub fn matches_with_instance(
208        &self,
209        action: &str,
210        resource_type: &str,
211        instance: Option<&str>,
212    ) -> bool {
213        let action_match = self.action == "*" || self.action == action;
214        let resource_match = self.resource_type == "*" || self.resource_type == resource_type;
215
216        let instance_match = match (&self.instance, instance) {
217            (None, _) => true, // Permission without instance matches any instance
218            (Some(perm_inst), Some(req_inst)) => perm_inst == "*" || perm_inst == req_inst,
219            (Some(_), None) => false, // Permission with instance doesn't match request without instance
220        };
221
222        action_match && resource_match && instance_match
223    }
224
225    /// Check if this permission implies another permission.
226    /// A permission implies another if it grants equal or greater access.
227    ///
228    /// Examples:
229    /// - "read:documents" implies "read:documents:doc123"
230    /// - "admin:*" implies "admin:users"
231    /// - "*:*" implies any permission
232    /// - "read:documents:*" implies "read:documents:doc123"
233    pub fn implies(&self, other: &Permission) -> bool {
234        // Check action implication
235        let action_implies = self.action == "*" || self.action == other.action;
236
237        // Check resource implication
238        let resource_implies =
239            self.resource_type == "*" || self.resource_type == other.resource_type;
240
241        // Check instance implication
242        let instance_implies = match (&self.instance, &other.instance) {
243            (None, _) => true,        // No instance restriction implies any instance
244            (Some(_), None) => false, // Instance-specific doesn't imply general
245            (Some(self_inst), Some(other_inst)) => self_inst == "*" || self_inst == other_inst,
246        };
247
248        action_implies && resource_implies && instance_implies
249    }
250
251    /// Check if this permission is granted given the context.
252    pub fn is_granted(
253        &self,
254        action: &str,
255        resource_type: &str,
256        context: &HashMap<String, String>,
257    ) -> bool {
258        if !self.matches(action, resource_type) {
259            return false;
260        }
261
262        // Check conditional validator if present
263        if let Some(condition) = &self.condition {
264            condition(context)
265        } else {
266            true
267        }
268    }
269
270    /// Parse a permission from a string format like "action:resource_type" or "action:resource_type:instance".
271    pub fn parse(permission_str: &str) -> Result<Self> {
272        let parts: Vec<&str> = permission_str.split(':').collect();
273
274        match parts.len() {
275            2 => {
276                let action = parts[0].trim();
277                let resource_type = parts[1].trim();
278
279                Self::validate_permission_field(action, "action")?;
280                Self::validate_permission_field(resource_type, "resource_type")?;
281
282                Ok(Self::new(action, resource_type))
283            }
284            3 => {
285                let action = parts[0].trim();
286                let resource_type = parts[1].trim();
287                let instance = parts[2].trim();
288
289                Self::validate_permission_field(action, "action")?;
290                Self::validate_permission_field(resource_type, "resource_type")?;
291                Self::validate_permission_field(instance, "instance")?;
292
293                Ok(Self::with_instance(action, resource_type, instance))
294            }
295            _ => Err(Error::InvalidPermission(format!(
296                "Permission must be in format 'action:resource_type' or 'action:resource_type:instance', got: '{permission_str}'"
297            ))),
298        }
299    }
300
301    /// Validate permission field allowing wildcards and other permission-specific characters.
302    fn validate_permission_field(value: &str, field_name: &str) -> Result<()> {
303        if value.trim().is_empty() {
304            return Err(Error::ValidationError {
305                field: field_name.to_string(),
306                reason: "cannot be empty".to_string(),
307                invalid_value: Some(value.to_string()),
308            });
309        }
310
311        if value.len() > 255 {
312            return Err(Error::ValidationError {
313                field: field_name.to_string(),
314                reason: "exceeds maximum length of 255 characters".to_string(),
315                invalid_value: Some(format!("{}...", &value[..50])),
316            });
317        }
318
319        // Allow wildcards and alphanumeric characters, hyphens, underscores
320        // But reject dangerous control characters and certain symbols
321        if value
322            .chars()
323            .any(|c| c.is_control() || "'\";{}[]\\<>".contains(c))
324        {
325            return Err(Error::ValidationError {
326                field: field_name.to_string(),
327                reason: "contains invalid characters".to_string(),
328                invalid_value: Some(value.to_string()),
329            });
330        }
331
332        // Still check for path traversal attempts
333        if value.contains("..") || value.contains('\0') {
334            return Err(Error::ValidationError {
335                field: field_name.to_string(),
336                reason: "contains path traversal sequences".to_string(),
337                invalid_value: Some(value.to_string()),
338            });
339        }
340
341        Ok(())
342    }
343}
344
345/// Builder for creating conditional permissions with fluent API.
346pub struct ConditionalPermissionBuilder {
347    resource_type: String,
348    action: String,
349    conditions: Vec<PermissionCondition>,
350}
351
352impl ConditionalPermissionBuilder {
353    /// Create a new conditional permission builder.
354    pub fn new(resource_type: impl Into<String>, action: impl Into<String>) -> Self {
355        Self {
356            resource_type: resource_type.into(),
357            action: action.into(),
358            conditions: Vec::new(),
359        }
360    }
361
362    /// Add a condition that must be true for the permission to be granted.
363    pub fn when<F>(mut self, condition: F) -> Self
364    where
365        F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
366    {
367        self.conditions.push(Arc::new(condition));
368        self
369    }
370
371    /// Add an alternative condition (OR logic).
372    pub fn or_when<F>(mut self, condition: F) -> Self
373    where
374        F: Fn(&HashMap<String, String>) -> bool + Send + Sync + 'static,
375    {
376        self.conditions.push(Arc::new(condition));
377        self
378    }
379
380    /// Build the final permission with all conditions combined.
381    pub fn build(self) -> Permission {
382        let combined_condition = if self.conditions.is_empty() {
383            None
384        } else {
385            Some(Arc::new(move |context: &HashMap<String, String>| {
386                // OR logic: any condition can be true
387                self.conditions.iter().any(|condition| condition(context))
388            }) as PermissionCondition)
389        };
390
391        Permission {
392            action: self.action,
393            resource_type: self.resource_type,
394            instance: None,
395            condition: combined_condition,
396        }
397    }
398}
399
400impl std::fmt::Debug for Permission {
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        f.debug_struct("Permission")
403            .field("action", &self.action)
404            .field("resource_type", &self.resource_type)
405            .field("instance", &self.instance)
406            .field("has_condition", &self.condition.is_some())
407            .finish()
408    }
409}
410
411impl Clone for Permission {
412    fn clone(&self) -> Self {
413        Self {
414            action: self.action.clone(),
415            resource_type: self.resource_type.clone(),
416            instance: self.instance.clone(),
417            condition: self.condition.clone(), // Now we can clone Arc
418        }
419    }
420}
421
422impl PartialEq for Permission {
423    fn eq(&self, other: &Self) -> bool {
424        self.action == other.action
425            && self.resource_type == other.resource_type
426            && self.instance == other.instance
427        // Note: We don't compare conditions as they're function pointers
428    }
429}
430
431impl Eq for Permission {}
432
433impl std::hash::Hash for Permission {
434    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
435        self.action.hash(state);
436        self.resource_type.hash(state);
437        self.instance.hash(state);
438        // Note: We don't hash conditions as they're function pointers
439    }
440}
441
442impl std::fmt::Display for Permission {
443    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444        match &self.instance {
445            Some(instance) => write!(f, "{}:{}:{}", self.action, self.resource_type, instance),
446            None => write!(f, "{}:{}", self.action, self.resource_type),
447        }
448    }
449}
450
451impl std::str::FromStr for Permission {
452    type Err = Error;
453
454    fn from_str(s: &str) -> Result<Self> {
455        Self::parse(s)
456    }
457}
458
459/// A collection of permissions with utility methods.
460#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
461pub struct PermissionSet {
462    permissions: Vec<Permission>,
463}
464
465impl std::fmt::Debug for PermissionSet {
466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467        f.debug_struct("PermissionSet")
468            .field("permissions", &self.permissions)
469            .field("count", &self.permissions.len())
470            .finish()
471    }
472}
473
474impl Clone for PermissionSet {
475    fn clone(&self) -> Self {
476        Self {
477            permissions: self.permissions.clone(),
478        }
479    }
480}
481
482impl Default for PermissionSet {
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488impl PermissionSet {
489    /// Create a new empty permission set.
490    pub fn new() -> Self {
491        Self {
492            permissions: Vec::new(),
493        }
494    }
495
496    /// Add a permission to the set.
497    pub fn add(&mut self, permission: Permission) {
498        self.permissions.push(permission);
499    }
500
501    /// Remove a permission from the set.
502    pub fn remove(&mut self, permission: &Permission) {
503        self.permissions.retain(|p| p != permission);
504    }
505
506    /// Check if the set contains a specific permission.
507    pub fn contains(&self, permission: &Permission) -> bool {
508        self.permissions.contains(permission)
509    }
510
511    /// Check if any permission in the set grants the given action on the resource type.
512    pub fn grants(
513        &self,
514        action: &str,
515        resource_type: &str,
516        context: &HashMap<String, String>,
517    ) -> bool {
518        self.permissions
519            .iter()
520            .any(|p| p.is_granted(action, resource_type, context))
521    }
522
523    /// Check if any permission in the set grants the given action on the resource type and instance.
524    pub fn grants_with_instance(
525        &self,
526        action: &str,
527        resource_type: &str,
528        instance: Option<&str>,
529        context: &HashMap<String, String>,
530    ) -> bool {
531        self.permissions.iter().any(|p| {
532            p.matches_with_instance(action, resource_type, instance)
533                && (p.condition.is_none() || p.condition.as_ref().unwrap()(context))
534        })
535    }
536
537    /// Check if any permission in the set implies the given permission.
538    pub fn implies(&self, permission: &Permission) -> bool {
539        self.permissions.iter().any(|p| p.implies(permission))
540    }
541
542    /// Get all permissions in the set.
543    pub fn permissions(&self) -> &[Permission] {
544        &self.permissions
545    }
546
547    /// Get the number of permissions in the set.
548    pub fn len(&self) -> usize {
549        self.permissions.len()
550    }
551
552    /// Check if the permission set is empty.
553    pub fn is_empty(&self) -> bool {
554        self.permissions.is_empty()
555    }
556
557    /// Merge another permission set into this one.
558    pub fn merge(&mut self, other: PermissionSet) {
559        for permission in other.permissions {
560            if !self.contains(&permission) {
561                self.add(permission);
562            }
563        }
564    }
565}
566
567impl From<Vec<Permission>> for PermissionSet {
568    fn from(permissions: Vec<Permission>) -> Self {
569        Self { permissions }
570    }
571}
572
573impl From<Permission> for PermissionSet {
574    fn from(permission: Permission) -> Self {
575        Self {
576            permissions: vec![permission],
577        }
578    }
579}
580
581impl IntoIterator for PermissionSet {
582    type Item = Permission;
583    type IntoIter = std::vec::IntoIter<Permission>;
584
585    fn into_iter(self) -> Self::IntoIter {
586        self.permissions.into_iter()
587    }
588}
589
590impl<'a> IntoIterator for &'a PermissionSet {
591    type Item = &'a Permission;
592    type IntoIter = std::slice::Iter<'a, Permission>;
593
594    fn into_iter(self) -> Self::IntoIter {
595        self.permissions.iter()
596    }
597}
598
599#[cfg(test)]
600mod tests {
601    use super::*;
602
603    #[test]
604    fn test_permission_creation() {
605        let permission = Permission::new("read", "documents");
606        assert_eq!(permission.action(), "read");
607        assert_eq!(permission.resource_type(), "documents");
608        assert_eq!(permission.instance(), None);
609    }
610
611    #[test]
612    fn test_permission_with_instance() {
613        let permission = Permission::with_instance("read", "documents", "doc123");
614        assert_eq!(permission.action(), "read");
615        assert_eq!(permission.resource_type(), "documents");
616        assert_eq!(permission.instance(), Some("doc123"));
617    }
618
619    #[test]
620    fn test_permission_matching() {
621        let permission = Permission::new("read", "documents");
622        assert!(permission.matches("read", "documents"));
623        assert!(!permission.matches("write", "documents"));
624        assert!(!permission.matches("read", "users"));
625    }
626
627    #[test]
628    fn test_permission_matching_with_instance() {
629        let permission = Permission::with_instance("read", "documents", "doc123");
630
631        // Should match exact instance
632        assert!(permission.matches_with_instance("read", "documents", Some("doc123")));
633
634        // Should not match different instance
635        assert!(!permission.matches_with_instance("read", "documents", Some("doc456")));
636
637        // Should not match when no instance provided
638        assert!(!permission.matches_with_instance("read", "documents", None));
639
640        // General permission should match any instance
641        let general_permission = Permission::new("read", "documents");
642        assert!(general_permission.matches_with_instance("read", "documents", Some("doc123")));
643        assert!(general_permission.matches_with_instance("read", "documents", None));
644    }
645
646    #[test]
647    fn test_permission_implication() {
648        let general = Permission::new("read", "documents");
649        let specific = Permission::with_instance("read", "documents", "doc123");
650
651        // General should imply specific
652        assert!(general.implies(&specific));
653
654        // Specific should not imply general
655        assert!(!specific.implies(&general));
656
657        // Same permission should imply itself
658        assert!(general.implies(&general));
659        assert!(specific.implies(&specific));
660
661        // Wildcard tests
662        let wildcard_action = Permission::new("*", "documents");
663        let wildcard_resource = Permission::new("read", "*");
664        let super_admin = Permission::super_admin();
665
666        assert!(wildcard_action.implies(&general));
667        assert!(wildcard_resource.implies(&general));
668        assert!(super_admin.implies(&general));
669        assert!(super_admin.implies(&specific));
670    }
671
672    #[test]
673    fn test_wildcard_permission() {
674        let permission = Permission::wildcard("documents");
675        assert!(permission.matches("read", "documents"));
676        assert!(permission.matches("write", "documents"));
677        assert!(!permission.matches("read", "users"));
678    }
679
680    #[test]
681    fn test_super_admin_permission() {
682        let permission = Permission::super_admin();
683        assert!(permission.matches("read", "documents"));
684        assert!(permission.matches("write", "users"));
685        assert!(permission.matches("delete", "anything"));
686    }
687
688    #[test]
689    fn test_permission_parsing() {
690        // Two-part format
691        let permission = Permission::parse("read:documents").unwrap();
692        assert_eq!(permission.action(), "read");
693        assert_eq!(permission.resource_type(), "documents");
694        assert_eq!(permission.instance(), None);
695
696        // Three-part format
697        let permission = Permission::parse("read:documents:doc123").unwrap();
698        assert_eq!(permission.action(), "read");
699        assert_eq!(permission.resource_type(), "documents");
700        assert_eq!(permission.instance(), Some("doc123"));
701
702        // Error cases
703        assert!(Permission::parse("invalid").is_err());
704        assert!(Permission::parse("read:").is_err());
705        assert!(Permission::parse(":documents").is_err());
706        assert!(Permission::parse("read:documents:").is_err());
707        assert!(Permission::parse("read:documents:instance:extra").is_err());
708    }
709
710    #[test]
711    fn test_permission_display() {
712        let permission = Permission::new("read", "documents");
713        assert_eq!(permission.to_string(), "read:documents");
714
715        let permission_with_instance = Permission::with_instance("read", "documents", "doc123");
716        assert_eq!(
717            permission_with_instance.to_string(),
718            "read:documents:doc123"
719        );
720    }
721
722    #[test]
723    fn test_permission_set() {
724        let mut set = PermissionSet::new();
725        let perm1 = Permission::new("read", "documents");
726        let perm2 = Permission::new("write", "documents");
727
728        set.add(perm1.clone());
729        set.add(perm2.clone());
730
731        assert_eq!(set.len(), 2);
732        assert!(set.contains(&perm1));
733        assert!(set.contains(&perm2));
734
735        let context = HashMap::new();
736        assert!(set.grants("read", "documents", &context));
737        assert!(set.grants("write", "documents", &context));
738        assert!(!set.grants("delete", "documents", &context));
739    }
740
741    #[test]
742    fn test_permission_set_with_instances() {
743        let mut set = PermissionSet::new();
744        let general_perm = Permission::new("read", "documents");
745        let specific_perm = Permission::with_instance("write", "documents", "doc123");
746
747        set.add(general_perm);
748        set.add(specific_perm);
749
750        let context = HashMap::new();
751
752        // General permission should work for any instance
753        assert!(set.grants_with_instance("read", "documents", Some("doc123"), &context));
754        assert!(set.grants_with_instance("read", "documents", Some("doc456"), &context));
755        assert!(set.grants_with_instance("read", "documents", None, &context));
756
757        // Specific permission should only work for that instance
758        assert!(set.grants_with_instance("write", "documents", Some("doc123"), &context));
759        assert!(!set.grants_with_instance("write", "documents", Some("doc456"), &context));
760        assert!(!set.grants_with_instance("write", "documents", None, &context));
761    }
762
763    #[test]
764    fn test_permission_set_implication() {
765        let mut set = PermissionSet::new();
766        let general_perm = Permission::new("read", "documents");
767        let admin_perm = Permission::new("admin", "*");
768
769        set.add(general_perm);
770        set.add(admin_perm);
771
772        let specific_perm = Permission::with_instance("read", "documents", "doc123");
773        let admin_users_perm = Permission::new("admin", "users");
774
775        assert!(set.implies(&specific_perm));
776        assert!(set.implies(&admin_users_perm));
777    }
778}