1use std::{
6 borrow::Cow,
7 collections::{HashMap, HashSet},
8};
9
10use rbx_dom_weak::{
11 types::{Ref, Variant, VariantType},
12 Ustr, UstrMap,
13};
14use serde::{Deserialize, Serialize};
15
16use crate::{
17 session_id::SessionId,
18 snapshot::{
19 AppliedPatchSet, InstanceMetadata as RojoInstanceMetadata, InstanceWithMeta, RojoTree,
20 },
21};
22
23pub(crate) const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26pub const PROTOCOL_VERSION: u64 = 4;
28
29#[derive(Debug, Serialize, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct SubscribeMessage<'a> {
33 pub removed: Vec<Ref>,
34 pub added: HashMap<Ref, Instance<'a>>,
35 pub updated: Vec<InstanceUpdate>,
36}
37
38impl<'a> SubscribeMessage<'a> {
39 pub(crate) fn from_patch_update(tree: &'a RojoTree, patch: AppliedPatchSet) -> Self {
40 let removed = patch.removed;
41
42 let mut added = HashMap::new();
43 for id in patch.added {
44 let instance = tree.get_instance(id).unwrap();
45 added.insert(id, Instance::from_rojo_instance(instance));
46
47 for instance in tree.descendants(id) {
48 added.insert(instance.id(), Instance::from_rojo_instance(instance));
49 }
50 }
51
52 let updated = patch
53 .updated
54 .into_iter()
55 .map(|update| {
56 let changed_metadata = update
57 .changed_metadata
58 .as_ref()
59 .map(InstanceMetadata::from_rojo_metadata);
60
61 let changed_properties = update
62 .changed_properties
63 .into_iter()
64 .filter(|(_key, value)| property_filter(value.as_ref()))
65 .collect();
66
67 InstanceUpdate {
68 id: update.id,
69 changed_name: update.changed_name,
70 changed_class_name: update.changed_class_name,
71 changed_properties,
72 changed_metadata,
73 }
74 })
75 .collect();
76
77 Self {
78 removed,
79 added,
80 updated,
81 }
82 }
83}
84
85#[derive(Debug, Serialize, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct InstanceUpdate {
88 pub id: Ref,
89 pub changed_name: Option<String>,
90 pub changed_class_name: Option<Ustr>,
91
92 #[serde(default)]
95 pub changed_properties: UstrMap<Option<Variant>>,
96 pub changed_metadata: Option<InstanceMetadata>,
97}
98
99#[derive(Debug, Serialize, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct InstanceMetadata {
102 pub ignore_unknown_instances: bool,
103}
104
105impl InstanceMetadata {
106 pub(crate) fn from_rojo_metadata(meta: &RojoInstanceMetadata) -> Self {
107 Self {
108 ignore_unknown_instances: meta.ignore_unknown_instances,
109 }
110 }
111}
112
113#[derive(Debug, Serialize, Deserialize)]
114#[serde(rename_all = "PascalCase")]
115pub struct Instance<'a> {
116 pub id: Ref,
117 pub parent: Ref,
118 pub name: Cow<'a, str>,
119 pub class_name: Ustr,
120 pub properties: UstrMap<Cow<'a, Variant>>,
121 pub children: Cow<'a, [Ref]>,
122 pub metadata: Option<InstanceMetadata>,
123}
124
125impl Instance<'_> {
126 pub(crate) fn from_rojo_instance(source: InstanceWithMeta<'_>) -> Instance<'_> {
127 let properties = source
128 .properties()
129 .iter()
130 .filter(|(_key, value)| property_filter(Some(value)))
131 .map(|(key, value)| (*key, Cow::Borrowed(value)))
132 .collect();
133
134 Instance {
135 id: source.id(),
136 parent: source.parent(),
137 name: Cow::Borrowed(source.name()),
138 class_name: source.class_name(),
139 properties,
140 children: Cow::Borrowed(source.children()),
141 metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
142 }
143 }
144}
145
146fn property_filter(value: Option<&Variant>) -> bool {
147 let ty = value.map(|value| value.ty());
148
149 ty != Some(VariantType::SharedString)
152}
153
154#[derive(Debug, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct ServerInfoResponse {
158 pub session_id: SessionId,
159 pub server_version: String,
160 pub protocol_version: u64,
161 pub project_name: String,
162 pub expected_place_ids: Option<HashSet<u64>>,
163 pub unexpected_place_ids: Option<HashSet<u64>>,
164 pub game_id: Option<u64>,
165 pub place_id: Option<u64>,
166 pub root_instance_id: Ref,
167}
168
169#[derive(Debug, Serialize, Deserialize)]
171#[serde(rename_all = "camelCase")]
172pub struct ReadResponse<'a> {
173 pub session_id: SessionId,
174 pub message_cursor: u32,
175 pub instances: HashMap<Ref, Instance<'a>>,
176}
177
178#[derive(Debug, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct WriteRequest {
181 pub session_id: SessionId,
182 pub removed: Vec<Ref>,
183
184 #[serde(default)]
185 pub added: HashMap<Ref, ()>,
186 pub updated: Vec<InstanceUpdate>,
187}
188
189#[derive(Debug, Serialize, Deserialize)]
190#[serde(rename_all = "camelCase")]
191pub struct WriteResponse {
192 pub session_id: SessionId,
193}
194
195#[derive(Debug, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct SubscribeResponse<'a> {
199 pub session_id: SessionId,
200 pub message_cursor: u32,
201 pub messages: Vec<SubscribeMessage<'a>>,
202}
203
204#[derive(Debug, Serialize, Deserialize)]
206#[serde(rename_all = "camelCase")]
207pub struct OpenResponse {
208 pub session_id: SessionId,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
212#[serde(rename_all = "camelCase")]
213pub struct SerializeResponse {
214 pub session_id: SessionId,
215 pub model_contents: BufferEncode,
216}
217
218#[derive(Debug, Serialize, Deserialize)]
221pub struct BufferEncode {
222 m: (),
223 t: Cow<'static, str>,
224 base64: String,
225}
226
227impl BufferEncode {
228 pub fn new(content: Vec<u8>) -> Self {
229 let base64 = data_encoding::BASE64.encode(&content);
230 Self {
231 m: (),
232 t: Cow::Borrowed("buffer"),
233 base64,
234 }
235 }
236
237 pub fn model(&self) -> &str {
238 &self.base64
239 }
240}
241
242#[derive(Debug, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase")]
244pub struct RefPatchResponse<'a> {
245 pub session_id: SessionId,
246 pub patch: SubscribeMessage<'a>,
247}
248
249#[derive(Debug, Serialize, Deserialize)]
251#[serde(rename_all = "camelCase")]
252pub struct ErrorResponse {
253 kind: ErrorResponseKind,
254 details: String,
255}
256
257impl ErrorResponse {
258 pub fn not_found<S: Into<String>>(details: S) -> Self {
259 Self {
260 kind: ErrorResponseKind::NotFound,
261 details: details.into(),
262 }
263 }
264
265 pub fn bad_request<S: Into<String>>(details: S) -> Self {
266 Self {
267 kind: ErrorResponseKind::BadRequest,
268 details: details.into(),
269 }
270 }
271
272 pub fn internal_error<S: Into<String>>(details: S) -> Self {
273 Self {
274 kind: ErrorResponseKind::InternalError,
275 details: details.into(),
276 }
277 }
278}
279
280#[derive(Debug, Serialize, Deserialize)]
281pub enum ErrorResponseKind {
282 NotFound,
283 BadRequest,
284 InternalError,
285}