veloren_server/settings/
server_description.rs

1//! Versioned server description settings files.
2
3// NOTE: Needed to allow the second-to-last migration to call try_into().
4
5use super::{MIGRATION_UPGRADE_GUARANTEE, SERVER_DESCRIPTION_FILENAME as FILENAME};
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 server description version. Then
11/// update the ServerDescriptionRaw, the TryFrom<ServerDescriptionRaw> for
12/// ServerDescription, the previously most recent module, and add a new module
13/// for the latest version!  Please respect the migration upgrade guarantee
14/// found in the parent module with any upgrade.
15pub use self::v2::*;
16
17/// Versioned settings files, one per version (v0 is only here as an example; we
18/// do not expect to see any actual v0 settings files).
19#[derive(Deserialize, Serialize)]
20pub enum ServerDescriptionRaw {
21    V0(v0::ServerDescription),
22    V1(v1::ServerDescription),
23    V2(ServerDescriptions),
24}
25
26impl From<ServerDescriptions> for ServerDescriptionRaw {
27    fn from(value: ServerDescriptions) -> Self {
28        // Replace variant with that of current latest version.
29        Self::V2(value)
30    }
31}
32
33impl TryFrom<ServerDescriptionRaw> for (Version, ServerDescriptions) {
34    type Error = <ServerDescriptions as EditableSetting>::Error;
35
36    fn try_from(
37        value: ServerDescriptionRaw,
38    ) -> Result<Self, <ServerDescriptions as EditableSetting>::Error> {
39        use ServerDescriptionRaw::*;
40        Ok(match value {
41            // Old versions
42            V0(value) => (Version::Old, value.try_into()?),
43            V1(value) => (Version::Old, value.try_into()?),
44            // Latest version (move to old section using the pattern of other old version when it
45            // is no longer latest).
46            V2(mut value) => (value.validate()?, value),
47        })
48    }
49}
50
51type Final = ServerDescriptions;
52
53impl EditableSetting for ServerDescriptions {
54    type Error = Infallible;
55    type Legacy = legacy::ServerDescription;
56    type Setting = ServerDescriptionRaw;
57
58    const FILENAME: &'static str = FILENAME;
59}
60
61mod legacy {
62    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, v0 as next};
63    use core::convert::TryInto;
64    use serde::{Deserialize, Serialize};
65
66    #[derive(Deserialize, Serialize)]
67    #[serde(transparent)]
68    pub struct ServerDescription(pub(super) String);
69
70    impl From<ServerDescription> for Final {
71        /// Legacy migrations can be migrated to the latest version through the
72        /// process of "chaining" migrations, starting from
73        /// `next::ServerDescription`.
74        ///
75        /// Note that legacy files are always valid, which is why we implement
76        /// From rather than TryFrom.
77        fn from(value: ServerDescription) -> Self {
78            next::ServerDescription::migrate(value)
79                .try_into()
80                .expect(MIGRATION_UPGRADE_GUARANTEE)
81        }
82    }
83}
84
85/// This module represents a server description version that isn't actually
86/// used.  It is here and part of the migration process to provide an example
87/// for how to perform a migration for an old version; please use this as a
88/// reference when constructing new migrations.
89mod v0 {
90    use super::{Final, MIGRATION_UPGRADE_GUARANTEE, legacy as prev, v1 as next};
91    use crate::settings::editable::{EditableSetting, Version};
92    use core::convert::{TryFrom, TryInto};
93    use serde::{Deserialize, Serialize};
94
95    #[derive(Clone, Deserialize, Serialize)]
96    #[serde(transparent)]
97    pub struct ServerDescription(pub(super) String);
98
99    impl ServerDescription {
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::ServerDescription) -> Self { ServerDescription(prev.0) }
104
105        /// Perform any needed validation on this server description that can't
106        /// be done 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    impl TryFrom<ServerDescription> for Final {
119        type Error = <Final as EditableSetting>::Error;
120
121        fn try_from(mut value: ServerDescription) -> Result<Final, Self::Error> {
122            value.validate()?;
123            Ok(next::ServerDescription::migrate(value)
124                .try_into()
125                .expect(MIGRATION_UPGRADE_GUARANTEE))
126        }
127    }
128}
129
130mod v1 {
131    use super::{Final, v0 as prev};
132    use crate::settings::editable::{EditableSetting, Version};
133    use core::ops::{Deref, DerefMut};
134    use serde::{Deserialize, Serialize};
135
136    #[derive(Clone, Deserialize, Serialize)]
137    #[serde(transparent)]
138    pub struct ServerDescription(pub(super) String);
139
140    impl Default for ServerDescription {
141        fn default() -> Self { Self("This is the best Veloren server".into()) }
142    }
143
144    impl Deref for ServerDescription {
145        type Target = String;
146
147        fn deref(&self) -> &Self::Target { &self.0 }
148    }
149
150    impl DerefMut for ServerDescription {
151        fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
152    }
153
154    impl ServerDescription {
155        /// One-off migration from the previous version.  This must be
156        /// guaranteed to produce a valid settings file as long as it is
157        /// called with a valid settings file from the previous version.
158        pub(super) fn migrate(prev: prev::ServerDescription) -> Self { ServerDescription(prev.0) }
159
160        /// Perform any needed validation on this server description that can't
161        /// be done using parsing.
162        ///
163        /// The returned version being "Old" indicates the loaded setting has
164        /// been modified during validation (this is why validate takes
165        /// `&mut self`).
166        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
167            Ok(Version::Latest)
168        }
169    }
170
171    use super::v2 as next;
172    impl TryFrom<ServerDescription> for Final {
173        type Error = <Final as EditableSetting>::Error;
174
175        fn try_from(mut value: ServerDescription) -> Result<Final, Self::Error> {
176            value.validate()?;
177            Ok(next::ServerDescriptions::migrate(value))
178        }
179    }
180}
181
182mod v2 {
183    use std::collections::HashMap;
184
185    use super::{Final, v1 as prev};
186    use crate::settings::editable::{EditableSetting, Version};
187    use serde::{Deserialize, Serialize};
188
189    /// Map of all localized [`ServerDescription`]s
190    #[derive(Clone, Deserialize, Serialize)]
191    pub struct ServerDescriptions {
192        pub default_locale: String,
193        pub descriptions: HashMap<String, ServerDescription>,
194    }
195
196    #[derive(Clone, Deserialize, Serialize)]
197    pub struct ServerDescription {
198        pub motd: String,
199        pub rules: Option<String>,
200    }
201
202    impl Default for ServerDescriptions {
203        fn default() -> Self {
204            Self {
205                default_locale: "en".to_string(),
206                descriptions: HashMap::from([("en".to_string(), ServerDescription::default())]),
207            }
208        }
209    }
210
211    impl Default for ServerDescription {
212        fn default() -> Self {
213            Self {
214                motd: "This is the best Veloren server".into(),
215                rules: None,
216            }
217        }
218    }
219
220    impl ServerDescriptions {
221        fn unwrap_locale_or_default<'a, 'b: 'a>(&'b self, locale: Option<&'a str>) -> &'a str {
222            locale.map_or(&self.default_locale, |locale| {
223                if self.descriptions.contains_key(locale) {
224                    locale
225                } else {
226                    &self.default_locale
227                }
228            })
229        }
230
231        pub fn get(&self, locale: Option<&str>) -> Option<&ServerDescription> {
232            self.descriptions.get(self.unwrap_locale_or_default(locale))
233        }
234
235        /// Attempts to get the rules in the specified locale, falls back to
236        /// `default_locale` if no rules were specified in this locale
237        pub fn get_rules(&self, locale: Option<&str>) -> Option<&str> {
238            self.descriptions
239                .get(self.unwrap_locale_or_default(locale))
240                .and_then(|d| d.rules.as_deref())
241                .or_else(|| {
242                    self.descriptions
243                        .get(&self.default_locale)?
244                        .rules
245                        .as_deref()
246                })
247        }
248    }
249
250    impl ServerDescriptions {
251        /// One-off migration from the previous version.  This must be
252        /// guaranteed to produce a valid settings file as long as it is
253        /// called with a valid settings file from the previous version.
254        pub(super) fn migrate(prev: prev::ServerDescription) -> Self {
255            Self {
256                default_locale: "en".to_string(),
257                descriptions: HashMap::from([("en".to_string(), ServerDescription {
258                    motd: prev.0,
259                    rules: None,
260                })]),
261            }
262        }
263
264        /// Perform any needed validation on this server description that can't
265        /// be done using parsing.
266        ///
267        /// The returned version being "Old" indicates the loaded setting has
268        /// been modified during validation (this is why validate takes
269        /// `&mut self`).
270        pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
271            if self.descriptions.is_empty() {
272                *self = Self::default();
273                Ok(Version::Old)
274            } else if !self.descriptions.contains_key(&self.default_locale) {
275                // default locale not present, select the a random one (as ordering in hashmaps
276                // isn't predictable)
277                self.default_locale = self
278                    .descriptions
279                    .keys()
280                    .next()
281                    .expect("We know descriptions isn't empty")
282                    .to_string();
283                Ok(Version::Old)
284            } else {
285                Ok(Version::Latest)
286            }
287        }
288    }
289
290    // NOTE: Whenever there is a version upgrade, copy this note as well as the
291    // commented-out code below to the next version, then uncomment the code
292    // for this version.
293    /*
294    use super::{v3 as next, MIGRATION_UPGRADE_GUARANTEE};
295    impl TryFrom<ServerDescription> for Final {
296        type Error = <Final as EditableSetting>::Error;
297
298        fn try_from(mut value: ServerDescription) -> Result<Final, Self::Error> {
299            value.validate()?;
300            Ok(next::ServerDescription::migrate(value).try_into().expect(MIGRATION_UPGRADE_GUARANTEE))
301        }
302    } */
303}