Skip to main content

zino_model/policy/
mod.rs

1//! The `policy` model and related services.
2
3use crate::group::Group;
4use serde::{Deserialize, Serialize};
5use zino_core::{
6    Map, Uuid,
7    datetime::DateTime,
8    error::Error,
9    extension::JsonObjectExt,
10    model::{Model, ModelHooks},
11    validation::Validation,
12};
13use zino_derive::{DecodeRow, Entity, ModelAccessor, Schema};
14
15#[cfg(feature = "tags")]
16use crate::tag::Tag;
17
18#[cfg(any(feature = "owner-id", feature = "maintainer-id"))]
19use crate::user::User;
20
21#[cfg(feature = "maintainer-id")]
22use zino_auth::UserSession;
23
24/// The `policy` model.
25#[derive(
26    Debug, Clone, Default, Serialize, Deserialize, DecodeRow, Entity, Schema, ModelAccessor,
27)]
28#[serde(default)]
29#[schema(auto_rename)]
30pub struct Policy {
31    // Basic fields.
32    #[schema(read_only)]
33    id: Uuid,
34    #[schema(not_null)]
35    name: String,
36    #[cfg(feature = "namespace")]
37    #[schema(default_value = "Policy::model_namespace", index_type = "hash")]
38    namespace: String,
39    #[cfg(feature = "visibility")]
40    #[schema(default_value = "Internal")]
41    visibility: String,
42    #[schema(default_value = "Active", index_type = "hash")]
43    status: String,
44    description: String,
45
46    // Info fields.
47    #[schema(reference = "Group")]
48    tenant_id: Uuid, // group.id, group.namespace = "*:policy"
49    #[schema(not_null)]
50    resource: String,
51    actions: Vec<String>,
52    effect: String,
53    valid_from: DateTime,
54    expires_at: DateTime,
55    #[cfg(feature = "tags")]
56    #[schema(reference = "Tag", index_type = "gin")]
57    tags: Vec<Uuid>, // tag.id, tag.namespace = "*:policy"
58
59    // Extensions.
60    extra: Map,
61
62    // Revisions.
63    #[cfg(feature = "owner-id")]
64    #[schema(reference = "User")]
65    owner_id: Option<Uuid>, // user.id
66    #[cfg(feature = "maintainer-id")]
67    #[schema(reference = "User")]
68    maintainer_id: Option<Uuid>, // user.id
69    #[schema(read_only, default_value = "now", index_type = "btree")]
70    created_at: DateTime,
71    #[schema(default_value = "now", index_type = "btree")]
72    updated_at: DateTime,
73    version: u64,
74    #[cfg(feature = "edition")]
75    edition: u32,
76}
77
78impl Model for Policy {
79    const MODEL_NAME: &'static str = "policy";
80
81    #[inline]
82    fn new() -> Self {
83        Self {
84            id: Uuid::now_v7(),
85            ..Self::default()
86        }
87    }
88
89    fn read_map(&mut self, data: &Map) -> Validation {
90        let mut validation = Validation::new();
91        if let Some(result) = data.parse_uuid("id") {
92            match result {
93                Ok(id) => self.id = id,
94                Err(err) => validation.record_fail("id", err),
95            }
96        }
97        if let Some(name) = data.parse_string("name") {
98            self.name = name.into_owned();
99        }
100        if let Some(description) = data.parse_string("description") {
101            self.description = description.into_owned();
102        }
103        #[cfg(feature = "tags")]
104        if let Some(result) = data.parse_array("tags") {
105            match result {
106                Ok(tags) => self.tags = tags,
107                Err(err) => validation.record_fail("tags", err),
108            }
109        }
110        #[cfg(feature = "owner-id")]
111        if let Some(result) = data.parse_uuid("owner_id") {
112            match result {
113                Ok(owner_id) => self.owner_id = Some(owner_id),
114                Err(err) => validation.record_fail("owner_id", err),
115            }
116        }
117        #[cfg(feature = "maintainer-id")]
118        if let Some(result) = data.parse_uuid("maintainer_id") {
119            match result {
120                Ok(maintainer_id) => self.maintainer_id = Some(maintainer_id),
121                Err(err) => validation.record_fail("maintainer_id", err),
122            }
123        }
124        validation
125    }
126}
127
128impl ModelHooks for Policy {
129    type Data = ();
130    #[cfg(feature = "maintainer-id")]
131    type Extension = UserSession<Uuid, String>;
132    #[cfg(not(feature = "maintainer-id"))]
133    type Extension = ();
134
135    #[cfg(feature = "maintainer-id")]
136    #[inline]
137    async fn after_extract(&mut self, session: Self::Extension) -> Result<(), Error> {
138        self.maintainer_id = Some(*session.user_id());
139        Ok(())
140    }
141
142    #[cfg(feature = "maintainer-id")]
143    #[inline]
144    async fn before_validation(
145        data: &mut Map,
146        extension: Option<&Self::Extension>,
147    ) -> Result<(), Error> {
148        if let Some(session) = extension {
149            data.upsert("maintainer_id", session.user_id().to_string());
150        }
151        Ok(())
152    }
153}