use std::{
borrow::Cow,
collections::{HashMap, HashSet},
};
use rbx_dom_weak::types::{Ref, Variant};
use serde::{Deserialize, Serialize};
use crate::{
session_id::SessionId,
snapshot::{InstanceMetadata as RojoInstanceMetadata, InstanceWithMeta},
};
pub(crate) const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
pub const PROTOCOL_VERSION: u64 = 4;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeMessage<'a> {
pub removed: Vec<Ref>,
pub added: HashMap<Ref, Instance<'a>>,
pub updated: Vec<InstanceUpdate>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstanceUpdate {
pub id: Ref,
pub changed_name: Option<String>,
pub changed_class_name: Option<String>,
#[serde(default)]
pub changed_properties: HashMap<String, Option<Variant>>,
pub changed_metadata: Option<InstanceMetadata>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct InstanceMetadata {
pub ignore_unknown_instances: bool,
}
impl InstanceMetadata {
pub(crate) fn from_rojo_metadata(meta: &RojoInstanceMetadata) -> Self {
Self {
ignore_unknown_instances: meta.ignore_unknown_instances,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct Instance<'a> {
pub id: Ref,
pub parent: Ref,
pub name: Cow<'a, str>,
pub class_name: Cow<'a, str>,
pub properties: HashMap<String, Cow<'a, Variant>>,
pub children: Cow<'a, [Ref]>,
pub metadata: Option<InstanceMetadata>,
}
impl<'a> Instance<'a> {
pub(crate) fn from_rojo_instance(source: InstanceWithMeta<'_>) -> Instance<'_> {
let properties = source
.properties()
.iter()
.filter_map(|(key, value)| {
if matches!(value, Variant::SharedString(_)) {
return None;
}
Some((key.clone(), Cow::Borrowed(value)))
})
.collect();
Instance {
id: source.id(),
parent: source.parent(),
name: Cow::Borrowed(source.name()),
class_name: Cow::Borrowed(source.class_name()),
properties,
children: Cow::Borrowed(source.children()),
metadata: Some(InstanceMetadata::from_rojo_metadata(source.metadata())),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ServerInfoResponse {
pub session_id: SessionId,
pub server_version: String,
pub protocol_version: u64,
pub project_name: String,
pub expected_place_ids: Option<HashSet<u64>>,
pub root_instance_id: Ref,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ReadResponse<'a> {
pub session_id: SessionId,
pub message_cursor: u32,
pub instances: HashMap<Ref, Instance<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WriteRequest {
pub session_id: SessionId,
pub removed: Vec<Ref>,
#[serde(default)]
pub added: HashMap<Ref, ()>,
pub updated: Vec<InstanceUpdate>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WriteResponse {
pub session_id: SessionId,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SubscribeResponse<'a> {
pub session_id: SessionId,
pub message_cursor: u32,
pub messages: Vec<SubscribeMessage<'a>>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenResponse {
pub session_id: SessionId,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
kind: ErrorResponseKind,
details: String,
}
impl ErrorResponse {
pub fn not_found<S: Into<String>>(details: S) -> Self {
Self {
kind: ErrorResponseKind::NotFound,
details: details.into(),
}
}
pub fn bad_request<S: Into<String>>(details: S) -> Self {
Self {
kind: ErrorResponseKind::BadRequest,
details: details.into(),
}
}
pub fn internal_error<S: Into<String>>(details: S) -> Self {
Self {
kind: ErrorResponseKind::InternalError,
details: details.into(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ErrorResponseKind {
NotFound,
BadRequest,
InternalError,
}