diff --git a/.deny.toml b/.deny.toml new file mode 100644 index 0000000..342c752 --- /dev/null +++ b/.deny.toml @@ -0,0 +1,42 @@ +# https://embarkstudios.github.io/cargo-deny/checks/cfg.html + +[graph] +all-features = true +exclude = [ + # dev only dependency + "criterion" +] + +[advisories] +version = 2 + +[licenses] +version = 2 +allow = [ + "Apache-2.0", + "BSD-3-Clause", + "MIT", + "MPL-2.0", + "Unicode-3.0", + "Zlib", +] +private = { ignore = true } + +[bans] +multiple-versions = "warn" +wildcards = "deny" + +[[bans.features]] +name = "serde_json" +# These features all don't make sense to activate from a library as they apply +# globally to all users of serde_json. Make sure we don't enable them somehow. +deny = [ + "arbitrary_precision", + "float_roundtrip", + "preserve_order", + "unbounded_depth", +] + +[sources] +unknown-registry = "deny" +unknown-git = "deny" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..76fa246 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,118 @@ +name: CI + +env: + CARGO_TERM_COLOR: always + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + NIGHTLY: nightly-2025-03-03 + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + style: + name: Style + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install rust nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.NIGHTLY }} + + - uses: Swatinem/rust-cache@v2 + + - name: Check spelling + uses: crate-ci/typos@v1.30.0 + + - name: Install cargo-sort + uses: taiki-e/cache-cargo-install-action@v2 + with: + tool: cargo-sort + + - name: Run cargo-sort + run: | + cargo sort --workspace --grouped --check \ + --order package,lib,features,dependencies,target,dev-dependencies,build-dependencies + + msrv: + name: Minimum Supported Rust Version / Check All Features + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install MSRV toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: "1.81" + + - uses: Swatinem/rust-cache@v2 + with: + # A stable compiler update should automatically not reuse old caches. + # Add the MSRV as a stable cache key too so bumping it also gets us a + # fresh cache. + shared-key: msrv1.81 + + - name: Run checks + run: cargo check --all-features + + stable: + name: Rust Stable / ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: Check All Features + cmd: check --all-features + + - name: Run Tests + cmd: test --all-features + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install rust stable toolchain + uses: dtolnay/rust-toolchain@stable + + - uses: Swatinem/rust-cache@v2 + + - name: Run checks + run: cargo ${{ matrix.cmd }} + + nightly: + name: Rust Nightly / ${{ matrix.name }} + runs-on: ubuntu-latest + strategy: + matrix: + include: + - name: Check Formatting + cmd: fmt --check + components: rustfmt + + - name: All Features + cmd: check --all-features + + - name: Clippy Default Features + cmd: clippy --all-features --all-targets -- -D warnings + components: clippy + + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Install rust nightly toolchain + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ env.NIGHTLY }} + components: ${{ matrix.components }} + + - uses: Swatinem/rust-cache@v2 + + - name: Run checks + run: cargo ${{ matrix.cmd }} diff --git a/.github/workflows/deps.yml b/.github/workflows/deps.yml new file mode 100644 index 0000000..a98b7aa --- /dev/null +++ b/.github/workflows/deps.yml @@ -0,0 +1,36 @@ +name: Dependencies + +env: + CARGO_TERM_COLOR: always + CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse + +on: + schedule: + # every monday at 4AM (UTC?) + - cron: '0 4 * * 1' + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + bans-licenses-sources: + name: Bans, Licenses, Sources + runs-on: ubuntu-latest + if: github.event.name != 'schedule' + + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check bans licenses sources + + advisories: + name: Advisories + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check advisories diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index 0877f76..0000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Rust Nightly - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - check: - name: Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install rust nightly toolchain - uses: dtolnay/rust-toolchain@nightly - with: - components: rustfmt, clippy - - name: Check formatting - run: cargo fmt -- --check - - name: Check all features - run: cargo check --all-features - - name: Catch common mistakes - run: cargo clippy --all-features --all-targets -- -D warnings diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml deleted file mode 100644 index 09bfeef..0000000 --- a/.github/workflows/stable.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Rust Stable - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - check: - name: Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Install rust nightly toolchain - uses: dtolnay/rust-toolchain@stable - - name: Check all features - run: cargo check --all-features - - name: Run tests - run: cargo test diff --git a/.rustfmt.toml b/.rustfmt.toml index d7d9e80..ed1837c 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,5 +1,8 @@ comment_width = 100 +format_code_in_doc_comments = true imports_granularity = "Crate" +group_imports = "StdExternalCrate" newline_style = "Unix" +use_field_init_shorthand = true use_small_heuristics = "Max" wrap_comments = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 5219a16..b344e7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ Breaking changes: * The list_room response changes the fields `version`, `join_rules`, `guest_access` and `history_visibility` to be an option * The list_room response changes the `join_rules` field to be `Option` +* `background_update::run::v1::JobName` is now non-exhaustive. +* `RoomSortOrder` and `RoomDirection` in `rooms::list_rooms::v1` are now non- + exhaustive. Their `PartialOrd` and `Ord` implementations now use their string + representation instead of the order in which they are defined in the enum. +* `UserDetails`, `ExternalId`, `CurrentUpdate`, `ExperimentalFeatures`, + `RoomDetails` and `UserMinorDetails` are now non-exhaustive. To keep using + them as if they were exhaustive, use the `ruma_unstable_exhaustive_types` + compile-time `cfg` setting. Improvement: diff --git a/Cargo.toml b/Cargo.toml index e91902b..45696d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ keywords = ["matrix", "chat", "messaging", "ruma"] license = "MIT" repository = "https://github.com/ruma/synapse-admin-api" edition = "2018" +rust-version = "1.81" [features] client = [] @@ -26,6 +27,40 @@ sha1 = { version = "0.10.1", optional = true } serde_json = "1.0.61" [lints.rust] +rust_2018_idioms = { level = "warn", priority = -1 } +semicolon_in_expressions_from_macros = "warn" unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(ruma_unstable_exhaustive_types)', # set all types as exhaustive ] } +unreachable_pub = "warn" +unused_import_braces = "warn" +unused_qualifications = "warn" + +[lints.clippy] +branches_sharing_code = "warn" +cloned_instead_of_copied = "warn" +dbg_macro = "warn" +disallowed_types = "warn" +empty_line_after_outer_attr = "warn" +exhaustive_enums = "warn" +exhaustive_structs = "warn" +inefficient_to_string = "warn" +macro_use_imports = "warn" +map_flatten = "warn" +missing_enforced_import_renames = "warn" +mod_module_files = "warn" +mut_mut = "warn" +nonstandard_macro_braces = "warn" +semicolon_if_nothing_returned = "warn" +str_to_string = "warn" +todo = "warn" +unreadable_literal = "warn" +unseparated_literal_suffix = "warn" +wildcard_imports = "warn" + +# Not that good of a lint +new_without_default = "allow" +# Disabled temporarily because it triggers false positives for types with generics. +arc_with_non_send_sync = "allow" +# Currently buggy +literal_string_with_formatting_args = "allow" diff --git a/src/background_updates/run/v1.rs b/src/background_updates/run/v1.rs index d2350d9..cdeb9c7 100644 --- a/src/background_updates/run/v1.rs +++ b/src/background_updates/run/v1.rs @@ -3,7 +3,7 @@ use ruma::{ api::{request, response, Metadata}, metadata, - serde::StringEnum, + serde::{PartialEqAsRefStr, StringEnum}, }; const METADATA: Metadata = metadata! { @@ -39,8 +39,9 @@ impl Response { } } -#[derive(Clone, PartialEq, StringEnum)] +#[derive(Clone, PartialEqAsRefStr, Eq, StringEnum)] #[ruma_enum(rename_all = "snake_case")] +#[non_exhaustive] pub enum JobName { /// Recalculate the stats for all rooms. PopulateStatsProcessRooms, diff --git a/src/background_updates/status/v1.rs b/src/background_updates/status/v1.rs index da43610..d4cc1d5 100644 --- a/src/background_updates/status/v1.rs +++ b/src/background_updates/status/v1.rs @@ -1,8 +1,9 @@ //! [GET /_synapse/admin/v1/background_updates/status](https://github.com/element-hq/synapse/blob/master/docs/usage/administration/admin_api/background_updates.md#status) +use std::collections::HashMap; + use ruma::api::{metadata, request, response, Metadata}; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; const METADATA: Metadata = metadata! { method: GET, @@ -27,7 +28,12 @@ pub struct Response { pub current_updates: HashMap, } +/// Information about a current update. +/// +/// To create an instance of this type, first create a `CurrentUpdateInit` and convert it via +/// `CurrentUpdate::from` / `.into()`. #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct CurrentUpdate { /// Name of the update. pub name: String, @@ -42,6 +48,33 @@ pub struct CurrentUpdate { pub average_items_per_ms: f64, } +/// Initial set of fields of [`CurrentUpdate`]. +/// +/// This struct will not be updated even if additional fields are added to `CurrentUpdate`. +#[derive(Debug)] +#[allow(clippy::exhaustive_structs)] +pub struct CurrentUpdateInit { + /// Name of the update. + pub name: String, + + /// Total number of processed "items". + pub total_item_count: u64, + + /// Runtime of background process, not including sleeping time. + pub total_duration_ms: f64, + + /// Items processed per millisecond based on an exponential average. + pub average_items_per_ms: f64, +} + +impl From for CurrentUpdate { + fn from(value: CurrentUpdateInit) -> Self { + let CurrentUpdateInit { name, total_item_count, total_duration_ms, average_items_per_ms } = + value; + Self { name, total_item_count, total_duration_ms, average_items_per_ms } + } +} + impl Request { /// Creates an empty `Request`. pub fn new() -> Self { @@ -58,14 +91,14 @@ impl Response { #[test] fn test_status_background_updates() { - let name = "current update 1".to_string(); - let total_item_count = 123456789; - let total_duration_ms = 2134567.12345; + let name = "current update 1"; + let total_item_count = 123_456_789; + let total_duration_ms = 2_134_567.123_45; let average_items_per_ms = 2.5; // Create the current update let update = CurrentUpdate { - name: name.clone(), + name: name.to_owned(), total_item_count, total_duration_ms, average_items_per_ms, @@ -77,7 +110,7 @@ fn test_status_background_updates() { // Create the hashmap let mut current_updates = HashMap::new(); - current_updates.insert("master".to_string(), update); + current_updates.insert("master".to_owned(), update); let enabled = true; let response = Response::new(enabled, current_updates); diff --git a/src/experimental_features.rs b/src/experimental_features.rs index 625dfd2..a8dc1f0 100644 --- a/src/experimental_features.rs +++ b/src/experimental_features.rs @@ -1,3 +1,3 @@ -/// Endpoints in /_synapse/admin/v/experimental_features/:user_id scope. +/// Endpoints in `/_synapse/admin/v/experimental_features/:user_id scope`. pub mod enable_features; pub mod list_features; diff --git a/src/experimental_features/enable_features/v1.rs b/src/experimental_features/enable_features/v1.rs index b27f757..96c331e 100644 --- a/src/experimental_features/enable_features/v1.rs +++ b/src/experimental_features/enable_features/v1.rs @@ -26,6 +26,7 @@ pub struct Request { } #[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Default)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct ExperimentalFeatures { /// Whether busy presence state is enabled. #[serde(skip_serializing_if = "Option::is_none")] @@ -38,6 +39,13 @@ pub struct ExperimentalFeatures { pub msc3967: Option, } +impl ExperimentalFeatures { + /// Construct an empty `ExperimentalFeatures`. + pub fn new() -> Self { + Self::default() + } +} + #[response] #[derive(Default)] pub struct Response {} @@ -59,9 +67,10 @@ impl Response { #[test] fn test_enable_features() { - use ruma::UserId; use std::convert::TryFrom; + use ruma::UserId; + let features = ExperimentalFeatures { msc3026: Option::from(true), msc3881: None, msc3967: None }; diff --git a/src/experimental_features/list_features/v1.rs b/src/experimental_features/list_features/v1.rs index a2ece52..e71ab0e 100644 --- a/src/experimental_features/list_features/v1.rs +++ b/src/experimental_features/list_features/v1.rs @@ -1,12 +1,13 @@ //! [GET /_synapse/admin/v1/experimental_features/:user_id](https://github.com/element-hq/synapse/blob/develop/docs/admin_api/experimental_features.md#listing-enabled-features) -use crate::experimental_features::enable_features::v1::ExperimentalFeatures; use ruma::{ api::{request, response, Metadata}, metadata, OwnedUserId, }; use serde::{Deserialize, Serialize}; +use crate::experimental_features::enable_features::v1::ExperimentalFeatures; + const METADATA: Metadata = metadata! { method: GET, rate_limited: false, diff --git a/src/lib.rs b/src/lib.rs index 1d59d21..5e4f046 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,20 @@ //! Serializable types for the requests and responses for each endpoint in the //! [synapse admin API][api]. //! +//! # Compile-time `cfg` settings +//! +//! These settings are accepted at compile time to configure the generated code. They can be set as +//! `--cfg={key}={value}` using `RUSTFLAGS` or `.cargo/config.toml` (under `[build]` -> `rustflags = +//! ["..."]`). +//! +//! * `ruma_identifiers_storage` -- Choose the inner representation of `Owned*` wrapper types for +//! identifiers. By default they use [`Box`], setting the value to `Arc` makes them use +//! [`Arc`](std::sync::Arc). +//! * `ruma_unstable_exhaustive_types` -- Most types in synapse-admin-api are marked as +//! non-exhaustive to avoid breaking changes when new fields are added in the API. This setting +//! compiles all types as exhaustive. By enabling this feature you opt out of all semver +//! guarantees synapse-admin-api otherwise provides. +//! //! [api]: https://github.com/matrix-org/synapse/tree/master/docs/admin_api // FIXME: don't allow dead code, warn on missing docs diff --git a/src/rooms/list_rooms/v1.rs b/src/rooms/list_rooms/v1.rs index 3ab7af0..5ccc3f0 100644 --- a/src/rooms/list_rooms/v1.rs +++ b/src/rooms/list_rooms/v1.rs @@ -3,9 +3,9 @@ use ruma::{ api::{metadata, request, response, Metadata}, events::room::{guest_access::GuestAccess, history_visibility::HistoryVisibility}, room::RoomType, - serde::StringEnum, + serde::{OrdAsRefStr, PartialEqAsRefStr, PartialOrdAsRefStr, StringEnum}, space::SpaceRoomJoinRule, - OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, + uint, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, }; use serde::{Deserialize, Serialize}; @@ -82,8 +82,9 @@ impl Response { } /// Enum to define the sorting method of rooms. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)] +#[derive(Clone, PartialEqAsRefStr, Eq, PartialOrdAsRefStr, OrdAsRefStr, StringEnum)] #[ruma_enum(rename_all = "snake_case")] +#[non_exhaustive] pub enum RoomSortOrder { /// Sort by name alphabetical Name, @@ -129,7 +130,8 @@ pub enum RoomSortOrder { } /// Enum to define the sort order direction. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)] +#[derive(Clone, PartialEqAsRefStr, Eq, PartialOrdAsRefStr, OrdAsRefStr, StringEnum)] +#[non_exhaustive] pub enum SortDirection { /// Sort direction backward. #[ruma_enum(rename = "b")] @@ -145,6 +147,7 @@ pub enum SortDirection { /// Structure for all the room details. #[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct RoomDetails { /// Room ID pub room_id: OwnedRoomId, @@ -194,3 +197,27 @@ pub struct RoomDetails { /// Room type of the room. pub room_type: Option, } + +impl RoomDetails { + /// Construct `RoomDetails` with the given room ID and all the other fields at their default + /// value. + pub fn new(room_id: OwnedRoomId) -> Self { + Self { + room_id, + name: None, + canonical_alias: None, + joined_members: uint!(0), + joined_local_members: uint!(0), + version: None, + creator: None, + encryption: None, + federatable: false, + public: false, + join_rules: None, + guest_access: None, + history_visibility: None, + state_events: uint!(0), + room_type: None, + } + } +} diff --git a/src/rooms/room_details/v1.rs b/src/rooms/room_details/v1.rs index 0ff1e0f..946de82 100644 --- a/src/rooms/room_details/v1.rs +++ b/src/rooms/room_details/v1.rs @@ -4,7 +4,7 @@ use ruma::{ events::room::{guest_access::GuestAccess, history_visibility::HistoryVisibility}, room::RoomType, space::SpaceRoomJoinRule, - OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, + uint, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UInt, }; const METADATA: Metadata = metadata! { @@ -103,9 +103,9 @@ impl Response { topic: None, avatar: None, canonical_alias: None, - joined_members: 0u32.into(), - joined_local_members: 0u32.into(), - joined_local_devices: 0u32.into(), + joined_members: uint!(0), + joined_local_members: uint!(0), + joined_local_devices: uint!(0), version: None, creator: None, encryption: None, @@ -114,7 +114,7 @@ impl Response { join_rules: None, guest_access: None, history_visibility: None, - state_events: 0u32.into(), + state_events: uint!(0), room_type: None, forgotten: false, } diff --git a/src/serde.rs b/src/serde.rs index ee360c0..dcd49da 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -11,7 +11,7 @@ where impl Visitor<'_> for BoolOrUIntVisitor { type Value = bool; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter.write_str("a boolean, or integer that's 0 or 1") } diff --git a/src/users.rs b/src/users.rs index 5d9ba41..d075c4e 100644 --- a/src/users.rs +++ b/src/users.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; /// User details #[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct UserDetails { /// The user's name. pub name: String, @@ -71,8 +72,33 @@ pub struct UserDetails { pub locked: bool, } +impl UserDetails { + /// Construct a `UserDetails` with the given user name and all the other fields set to their + /// default value. + pub fn new(name: String) -> Self { + Self { + name, + password_hash: None, + is_guest: false, + admin: false, + consent_version: None, + consent_server_notice_sent: None, + appservice_id: None, + creation_ts: None, + user_type: None, + deactivated: false, + displayname: String::new(), + avatar_url: None, + threepids: Vec::new(), + external_ids: Vec::new(), + locked: false, + } + } +} + /// An external ID associated with a user #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct ExternalId { /// The authentication provider to which the user is associated. pub auth_provider: String, @@ -80,3 +106,10 @@ pub struct ExternalId { /// The ID known to the auth provider associated with this user. pub external_id: String, } + +impl ExternalId { + /// Construct an `ExternalId` with the given authentication provider and ID. + pub fn new(auth_provider: String, external_id: String) -> Self { + Self { auth_provider, external_id } + } +} diff --git a/src/users/create_or_modify/v2.rs b/src/users/create_or_modify/v2.rs index 2d2d2a1..863ebad 100644 --- a/src/users/create_or_modify/v2.rs +++ b/src/users/create_or_modify/v2.rs @@ -80,7 +80,7 @@ pub struct Response { // The error is necessary at least at all endpoints which need auth, because a invalid login // response such an error // TODO: Should this be the real error like at ruma client api error, is Void-Default enough? -// TODO: ruma api serialisis Ok if status code < 400, alse error. That should be diskussed. +// TODO: ruma api serialize is Ok if status code < 400, else error. That should be discussed. // The redirect 300 area is Ok too. impl Request { diff --git a/src/users/deactivate_account/v1.rs b/src/users/deactivate_account/v1.rs index 3430ee5..31bae2a 100644 --- a/src/users/deactivate_account/v1.rs +++ b/src/users/deactivate_account/v1.rs @@ -20,7 +20,7 @@ pub struct Request { #[ruma_api(path)] pub user_id: OwnedUserId, - /// Flag wether to erase the account. + /// Flag whether to erase the account. #[serde(default = "ruma::serde::default_false", skip_serializing_if = "ruma::serde::is_false")] pub erase: bool, } diff --git a/src/users/get_details/v2.rs b/src/users/get_details/v2.rs index 7e4fe83..0d42777 100644 --- a/src/users/get_details/v2.rs +++ b/src/users/get_details/v2.rs @@ -1,11 +1,12 @@ //! [GET /_synapse/admin/v2/users/:user_id](https://github.com/matrix-org/synapse/blob/master/docs/admin_api/user_admin_api.rst#query-user-account) -pub use crate::users::UserDetails; use ruma::{ api::{metadata, request, response, Metadata}, OwnedUserId, }; +pub use crate::users::UserDetails; + const METADATA: Metadata = metadata! { method: GET, rate_limited: false, diff --git a/src/users/list_users/v2.rs b/src/users/list_users/v2.rs index 2866524..a615772 100644 --- a/src/users/list_users/v2.rs +++ b/src/users/list_users/v2.rs @@ -97,6 +97,7 @@ impl Response { /// A minor set of user details. #[derive(Serialize, Deserialize, Clone, Debug)] +#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)] pub struct UserMinorDetails { /// The user's name. pub name: String, @@ -128,3 +129,20 @@ pub struct UserMinorDetails { #[serde(default, deserialize_with = "crate::serde::bool_or_uint")] pub locked: bool, } + +impl UserMinorDetails { + /// Construct a `UserMinorDetails` with the given user name and all the other fields set to + /// their default value. + pub fn new(name: String) -> Self { + Self { + name, + is_guest: false, + admin: false, + user_type: None, + deactivated: false, + displayname: String::new(), + avatar_url: None, + locked: false, + } + } +} diff --git a/src/version/get_server_version/v1.rs b/src/version/get_server_version/v1.rs index e454d1f..084522f 100644 --- a/src/version/get_server_version/v1.rs +++ b/src/version/get_server_version/v1.rs @@ -47,10 +47,10 @@ impl Response { fn test_response_with_python_version() { use serde_json; - let server_version = "1.2.3".to_string(); + let server_version = "1.2.3"; // Check create response case - let response = Response::new(server_version.clone()); + let response = Response::new(server_version.to_owned()); assert_eq!(response.server_version, server_version); assert_eq!(response.python_version, None); @@ -64,8 +64,8 @@ fn test_response_with_python_version() { assert_eq!(deserialized.python_version, None); // Check backwards compatibility - let old_serialized = "{\"server_version\":\"1.2.3\",\"python_version\":\"4.5.6\"}".to_string(); - let old_deserialized: Response = serde_json::from_str(&old_serialized).unwrap(); + let old_serialized = "{\"server_version\":\"1.2.3\",\"python_version\":\"4.5.6\"}"; + let old_deserialized: Response = serde_json::from_str(old_serialized).unwrap(); assert_eq!(old_deserialized.server_version, "1.2.3"); - assert_eq!(old_deserialized.python_version, Some("4.5.6".to_string())); + assert_eq!(old_deserialized.python_version.as_deref(), Some("4.5.6")); }