1use js_int::UInt;
6use ruma_macros::{EventContent, StringEnum};
7use serde::{Deserialize, Serialize};
8
9mod zoomlevel_serde;
10
11use ruma_common::MilliSecondsSinceUnixEpoch;
12
13use super::{message::TextContentBlock, room::message::Relation};
14use crate::PrivOwnedStr;
15
16#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
24#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
25#[ruma_event(type = "m.location", kind = MessageLike, without_relation)]
26pub struct LocationEventContent {
27 #[serde(rename = "org.matrix.msc1767.text")]
29 pub text: TextContentBlock,
30
31 #[serde(rename = "m.location")]
33 pub location: LocationContent,
34
35 #[serde(default, rename = "m.asset", skip_serializing_if = "ruma_common::serde::is_default")]
37 pub asset: AssetContent,
38
39 #[serde(rename = "m.ts", skip_serializing_if = "Option::is_none")]
41 pub ts: Option<MilliSecondsSinceUnixEpoch>,
42
43 #[cfg(feature = "unstable-msc3955")]
45 #[serde(
46 default,
47 skip_serializing_if = "ruma_common::serde::is_default",
48 rename = "org.matrix.msc1767.automated"
49 )]
50 pub automated: bool,
51
52 #[serde(
54 flatten,
55 skip_serializing_if = "Option::is_none",
56 deserialize_with = "crate::room::message::relation_serde::deserialize_relation"
57 )]
58 pub relates_to: Option<Relation<LocationEventContentWithoutRelation>>,
59}
60
61impl LocationEventContent {
62 pub fn new(text: TextContentBlock, location: LocationContent) -> Self {
64 Self {
65 text,
66 location,
67 asset: Default::default(),
68 ts: None,
69 #[cfg(feature = "unstable-msc3955")]
70 automated: false,
71 relates_to: None,
72 }
73 }
74
75 pub fn with_plain_text(plain_text: impl Into<String>, location: LocationContent) -> Self {
78 Self {
79 text: TextContentBlock::plain(plain_text),
80 location,
81 asset: Default::default(),
82 ts: None,
83 #[cfg(feature = "unstable-msc3955")]
84 automated: false,
85 relates_to: None,
86 }
87 }
88}
89
90#[derive(Clone, Debug, Serialize, Deserialize)]
92#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
93pub struct LocationContent {
94 pub uri: String,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
103 pub description: Option<String>,
104
105 #[serde(skip_serializing_if = "Option::is_none")]
107 pub zoom_level: Option<ZoomLevel>,
108}
109
110impl LocationContent {
111 pub fn new(uri: String) -> Self {
113 Self { uri, description: None, zoom_level: None }
114 }
115}
116
117#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, thiserror::Error)]
119#[non_exhaustive]
120pub enum ZoomLevelError {
121 #[error("value too high")]
123 TooHigh,
124}
125
126#[derive(Clone, Debug, Serialize)]
132pub struct ZoomLevel(UInt);
133
134impl ZoomLevel {
135 pub const MIN: u8 = 0;
137
138 pub const MAX: u8 = 20;
140
141 pub fn new(value: u8) -> Option<Self> {
143 if value > Self::MAX {
144 None
145 } else {
146 Some(Self(value.into()))
147 }
148 }
149
150 pub fn get(&self) -> UInt {
152 self.0
153 }
154}
155
156impl TryFrom<u8> for ZoomLevel {
157 type Error = ZoomLevelError;
158
159 fn try_from(value: u8) -> Result<Self, Self::Error> {
160 Self::new(value).ok_or(ZoomLevelError::TooHigh)
161 }
162}
163
164#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
166#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
167pub struct AssetContent {
168 #[serde(rename = "type")]
170 pub type_: AssetType,
171}
172
173impl AssetContent {
174 pub fn new() -> Self {
176 Self::default()
177 }
178}
179
180#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
182#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
183#[ruma_enum(rename_all = "m.snake_case")]
184#[non_exhaustive]
185pub enum AssetType {
186 #[default]
188 #[ruma_enum(rename = "m.self")]
189 Self_,
190
191 Pin,
193
194 #[doc(hidden)]
195 _Custom(PrivOwnedStr),
196}