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}