veloren_server/settings/
admin.rs

1//! Versioned admins settings files.
2
3// NOTE: Needed to allow the second-to-last migration to call try_into().
4
5use super::{ADMINS_FILENAME as FILENAME, MIGRATION_UPGRADE_GUARANTEE};
6use crate::settings::editable::{EditableSetting, Version};
7use core::convert::{Infallible, TryFrom, TryInto};
8use serde::{Deserialize, Serialize};
9
10/// NOTE: Always replace this with the latest admins version. Then update the
11/// `AdminsRaw`, the `TryFrom<AdminsRaw>` for `Admins`, the previously most
12/// recent module, and add a new module for the latest version!  Please respect
13/// the migration upgrade guarantee found in the parent module with any upgrade.
14pub use self::v1::*;
15
16/// Versioned settings files, one per version (v0 is only here as an example; we
17/// do not expect to see any actual v0 settings files).
18#[derive(Deserialize, Serialize)]
19pub enum AdminsRaw {
20    V0(v0::Admins),
21    V1(Admins),
22}
23
24impl From<Admins> for AdminsRaw {
25    fn from(value: Admins) -> Self {
26        // Replace variant with that of current latest version.
27        Self::V1(value)
28    }
29}
30
31#[expect(clippy::infallible_try_from)] // TODO: evaluate
32impl TryFrom<AdminsRaw> for (Version, Admins) {
33    type Error = <Admins as EditableSetting>::Error;
34
35    fn try_from(value: AdminsRaw) -> Result<Self, <Admins as EditableSetting>::Error> {
36        use AdminsRaw::*;
37        Ok(match value {
38            // Old versions
39            V0(value) => (Version::Old, value.try_into()?),
40            // Latest version (move to old section using the pattern of other old version when it
41            // is no longer latest).
42            V1(mut value) => (value.validate()?, value),
43        })
44    }
45}
46
47type Final = Admins;
48
49impl EditableSetting for Admins {
50    type Error = Infallible;
51    type Legacy = legacy::Admins;
52    type Setting = AdminsRaw;
53
54    const FILENAME: &'static str = FILENAME;
55}
56
57mod legacy {
58    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, v0 as next};
59    use authc::Uuid;
60    use core::convert::TryInto;
61    use hashbrown::HashSet;
62    use serde::{Deserialize, Serialize};
63
64    #[derive(Deserialize, Serialize, Default)]
65    #[serde(transparent)]
66    pub struct Admins(pub(super) HashSet<Uuid>);
67
68    impl From<Admins> for Final {
69        /// Legacy migrations can be migrated to the latest version through the
70        /// process of "chaining" migrations, starting from
71        /// `next::Admins`.
72        ///
73        /// Note that legacy files are always valid, which is why we implement
74        /// `From` rather than `TryFrom`.
75        fn from(value: Admins) -> Self {
76            next::Admins::migrate(value)
77                .try_into()
78                .expect(MIGRATION_UPGRADE_GUARANTEE)
79        }
80    }
81}
82
83/// This module represents a admins version that isn't actually used.  It is
84/// here and part of the migration process to provide an example for how to
85/// perform a migration for an old version; please use this as a reference when
86/// constructing new migrations.
87mod v0 {
88    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, legacy as prev, v1 as next};
89    use crate::settings::editable::{EditableSetting, Version};
90    use authc::Uuid;
91    use core::convert::{TryFrom, TryInto};
92    use hashbrown::HashSet;
93    use serde::{Deserialize, Serialize};
94
95    #[derive(Clone, Deserialize, Serialize, Default)]
96    #[serde(transparent)]
97    pub struct Admins(pub(super) HashSet<Uuid>);
98
99    impl Admins {
100        /// One-off migration from the previous version.  This must be
101        /// guaranteed to produce a valid settings file as long as it is
102        /// called with a valid settings file from the previous version.
103        pub(super) fn migrate(prev: prev::Admins) -> Self { Admins(prev.0) }
104
105        /// Perform any needed validation on this admins that can't be done
106        /// using parsing.
107        ///
108        /// The returned version being "Old" indicates the loaded setting has
109        /// been modified during validation (this is why validate takes
110        /// `&mut self`).
111        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
112            Ok(Version::Latest)
113        }
114    }
115
116    /// Pretty much every TryFrom implementation except that of the very last
117    /// version should look exactly like this.
118    #[expect(clippy::infallible_try_from)] // TODO: evaluate
119    impl TryFrom<Admins> for Final {
120        type Error = <Final as EditableSetting>::Error;
121
122        #[expect(clippy::useless_conversion)]
123        fn try_from(mut value: Admins) -> Result<Final, Self::Error> {
124            value.validate()?;
125            Ok(next::Admins::migrate(value)
126                .try_into()
127                .expect(MIGRATION_UPGRADE_GUARANTEE))
128        }
129    }
130}
131
132mod v1 {
133    use super::{Final, v0 as prev};
134    use crate::settings::editable::{EditableSetting, Version};
135    use authc::Uuid;
136    use chrono::{Utc, prelude::*};
137    use common::comp::AdminRole;
138    use core::ops::{Deref, DerefMut};
139    use hashbrown::HashMap;
140    use serde::{Deserialize, Serialize};
141    /* use super::v2 as next; */
142
143    /// Important: even if the role we are storing here appears to be identical
144    /// to one used in another versioned store (like banlist::Role), we
145    /// *must* have our own versioned copy! This ensures that if there's an
146    /// update to the role somewhere else, the conversion function between
147    /// them will break, letting people make an intelligent decision.
148    ///
149    /// In particular, *never remove variants from this enum* (or any other enum
150    /// in a versioned settings file) without bumping the version and
151    /// writing a migration that understands how to properly deal with
152    /// existing instances of the old variant (you can delete From instances
153    /// for the old variants at this point).  Otherwise, we will lose
154    /// compatibility with old settings files, since we won't be able to
155    /// deserialize them!
156    #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialOrd, PartialEq, Serialize)]
157    pub enum Role {
158        Moderator = 0,
159        Admin = 1,
160    }
161
162    impl From<AdminRole> for Role {
163        fn from(value: AdminRole) -> Self {
164            match value {
165                AdminRole::Moderator => Self::Moderator,
166                AdminRole::Admin => Self::Admin,
167            }
168        }
169    }
170
171    impl From<Role> for AdminRole {
172        fn from(value: Role) -> Self {
173            match value {
174                Role::Moderator => Self::Moderator,
175                Role::Admin => Self::Admin,
176            }
177        }
178    }
179
180    #[derive(Clone, Deserialize, Serialize)]
181    /// NOTE: This does not include info structs like other settings, because we
182    /// (deliberately) provide no interface for creating new mods or admins
183    /// except through the command line, ensuring that the host of the
184    /// server has total control over these things and avoiding the creation
185    /// of code paths to alter the admin list that are accessible during normal
186    /// gameplay.
187    pub struct AdminRecord {
188        /// NOTE: Should only be None for migrations from legacy data.
189        pub username_when_admined: Option<String>,
190        /// Date that the user was given this role.
191        pub date: DateTime<Utc>,
192        pub role: Role,
193    }
194
195    #[derive(Clone, Deserialize, Serialize, Default)]
196    #[serde(transparent)]
197    /// NOTE: Records should only be unavailable for cases where we are
198    /// migration from a legacy version.
199    pub struct Admins(pub(super) HashMap<Uuid, AdminRecord>);
200
201    impl Deref for Admins {
202        type Target = HashMap<Uuid, AdminRecord>;
203
204        fn deref(&self) -> &Self::Target { &self.0 }
205    }
206
207    impl DerefMut for Admins {
208        fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
209    }
210
211    impl Admins {
212        /// One-off migration from the previous version.  This must be
213        /// guaranteed to produce a valid settings file as long as it is
214        /// called with a valid settings file from the previous version.
215        pub(super) fn migrate(prev: prev::Admins) -> Self {
216            // The role assignment date for migrations from legacy is the current one; we
217            // could record that they actually have an unknown start date, but
218            // this would just complicate the format.
219            let date = Utc::now();
220            Admins(
221                prev.0
222                    .into_iter()
223                    .map(|uid| {
224                        (uid, AdminRecord {
225                            date,
226                            // We don't have username information for old admin records.
227                            username_when_admined: None,
228                            // All legacy roles are Admin, because we didn't have any other roles at
229                            // the time.
230                            role: Role::Admin,
231                        })
232                    })
233                    .collect(),
234            )
235        }
236
237        /// Perform any needed validation on this admins that can't be done
238        /// using parsing.
239        ///
240        /// The returned version being "Old" indicates the loaded setting has
241        /// been modified during validation (this is why validate takes
242        /// `&mut self`).
243        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
244            Ok(Version::Latest)
245        }
246    }
247
248    // NOTE: Whenever there is a version upgrade, copy this note as well as the
249    // commented-out code below to the next version, then uncomment the code
250    // for this version.
251    /* impl TryFrom<Admins> for Final {
252        type Error = <Final as EditableSetting>::Error;
253
254        fn try_from(mut value: Admins) -> Result<Final, Self::Error> {
255            value.validate()?;
256            Ok(next::Admins::migrate(value).try_into().expect(MIGRATION_UPGRADE_GUARANTEE))
257        }
258    } */
259}