1use std::{borrow::Cow, collections::HashMap};
4
5use parse_display_derive::{Display, FromStr};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8#[cfg(feature = "slog")]
9use slog::{Record, Serializer, KV};
10use uuid::Uuid;
11
12use crate::{
13 id::ModelingCmdId,
14 ok_response::OkModelingCmdResponse,
15 shared::{EngineErrorCode, ExportFile},
16 ModelingCmd,
17};
18
19#[derive(Display, FromStr, Copy, Eq, PartialEq, Debug, JsonSchema, Deserialize, Serialize, Clone, Ord, PartialOrd)]
21#[serde(rename_all = "snake_case")]
22#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
23pub enum ErrorCode {
24 InternalEngine,
26 InternalApi,
28 BadRequest,
32 AuthTokenMissing,
34 AuthTokenInvalid,
36 InvalidJson,
38 InvalidBson,
40 WrongProtocol,
42 ConnectionProblem,
44 MessageTypeNotAccepted,
46 MessageTypeNotAcceptedForWebRTC,
49}
50
51impl From<EngineErrorCode> for ErrorCode {
54 fn from(value: EngineErrorCode) -> Self {
55 match value {
56 EngineErrorCode::InternalEngine => Self::InternalEngine,
57 EngineErrorCode::BadRequest => Self::BadRequest,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize)]
64#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
65#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
66pub struct ModelingCmdReq {
67 pub cmd: ModelingCmd,
69 pub cmd_id: ModelingCmdId,
71}
72
73#[allow(clippy::large_enum_variant)]
75#[derive(Serialize, Deserialize, Debug, Clone)]
76#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
77#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
78#[serde(tag = "type", rename_all = "snake_case")]
79#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
80pub enum WebSocketRequest {
81 TrickleIce {
84 candidate: Box<RtcIceCandidateInit>,
86 },
87 SdpOffer {
89 offer: Box<RtcSessionDescription>,
91 },
92 ModelingCmdReq(ModelingCmdReq),
94 ModelingCmdBatchReq(ModelingBatch),
96 Ping {},
98
99 MetricsResponse {
101 metrics: Box<ClientMetrics>,
103 },
104
105 Debug {},
107
108 Headers {
110 headers: HashMap<String, String>,
112 },
113
114 ExecKclProject {
116 request_id: Uuid,
118 project: crate::exec_kcl::KclProject,
120 },
121}
122
123#[derive(Serialize, Deserialize, Debug, Clone)]
125#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
126#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
127#[serde(rename_all = "snake_case")]
128pub struct ModelingBatch {
129 pub requests: Vec<ModelingCmdReq>,
131 pub batch_id: ModelingCmdId,
135 #[serde(default)]
138 pub responses: bool,
139}
140
141impl std::default::Default for ModelingBatch {
142 fn default() -> Self {
144 Self {
145 requests: Default::default(),
146 batch_id: Uuid::new_v4().into(),
147 responses: false,
148 }
149 }
150}
151
152impl ModelingBatch {
153 pub fn push(&mut self, req: ModelingCmdReq) {
155 self.requests.push(req);
156 }
157
158 pub fn is_empty(&self) -> bool {
160 self.requests.is_empty()
161 }
162}
163
164#[derive(serde::Serialize, serde::Deserialize, Debug, JsonSchema, Clone, PartialEq)]
168pub struct IceServer {
169 pub urls: Vec<String>,
173 pub credential: Option<String>,
175 pub username: Option<String>,
177}
178
179#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
181#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
182#[serde(tag = "type", content = "data", rename_all = "snake_case")]
183#[cfg_attr(not(feature = "unstable_exhaustive"), non_exhaustive)]
184pub enum OkWebSocketResponseData {
185 IceServerInfo {
187 ice_servers: Vec<IceServer>,
189 },
190 TrickleIce {
193 candidate: Box<RtcIceCandidateInit>,
195 },
196 SdpAnswer {
198 answer: Box<RtcSessionDescription>,
200 },
201 Modeling {
203 modeling_response: OkModelingCmdResponse,
205 },
206 ModelingBatch {
208 responses: HashMap<ModelingCmdId, BatchResponse>,
211 },
212 Export {
214 files: Vec<RawFile>,
216 },
217
218 MetricsRequest {},
220
221 ModelingSessionData {
223 session: ModelingSessionData,
225 },
226
227 Pong {},
229
230 Debug {
232 name: String,
234 },
235
236 ExecKclProject {
238 result: Result<crate::exec_kcl::ExecKclProjectOk, crate::exec_kcl::ExecKclProjectErr>,
240 },
241}
242
243#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
245#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
246#[serde(rename_all = "snake_case")]
247pub struct SuccessWebSocketResponse {
248 pub success: bool,
250 pub request_id: Option<Uuid>,
254 pub resp: OkWebSocketResponseData,
257}
258
259#[derive(JsonSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
261#[serde(rename_all = "snake_case")]
262pub struct FailureWebSocketResponse {
263 pub success: bool,
265 pub request_id: Option<Uuid>,
269 pub errors: Vec<ApiError>,
271}
272
273#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
276#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
277#[serde(rename_all = "snake_case", untagged)]
278pub enum WebSocketResponse {
279 Success(SuccessWebSocketResponse),
281 Failure(FailureWebSocketResponse),
283}
284
285#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
288#[cfg_attr(feature = "derive-jsonschema-on-enums", derive(schemars::JsonSchema))]
289#[serde(rename_all = "snake_case", untagged)]
290pub enum BatchResponse {
291 Success {
293 response: OkModelingCmdResponse,
295 },
296 Failure {
298 errors: Vec<ApiError>,
300 },
301}
302
303impl WebSocketResponse {
304 pub fn success(request_id: Option<Uuid>, resp: OkWebSocketResponseData) -> Self {
306 Self::Success(SuccessWebSocketResponse {
307 success: true,
308 request_id,
309 resp,
310 })
311 }
312
313 pub fn failure(request_id: Option<Uuid>, errors: Vec<ApiError>) -> Self {
315 Self::Failure(FailureWebSocketResponse {
316 success: false,
317 request_id,
318 errors,
319 })
320 }
321
322 pub fn is_success(&self) -> bool {
324 matches!(self, Self::Success(_))
325 }
326
327 pub fn is_failure(&self) -> bool {
329 matches!(self, Self::Failure(_))
330 }
331
332 pub fn request_id(&self) -> Option<Uuid> {
334 match self {
335 WebSocketResponse::Success(x) => x.request_id,
336 WebSocketResponse::Failure(x) => x.request_id,
337 }
338 }
339}
340
341#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone, PartialEq)]
344#[cfg_attr(
345 feature = "python",
346 pyo3::pyclass(from_py_object),
347 pyo3_stub_gen::derive::gen_stub_pyclass
348)]
349pub struct RawFile {
350 pub name: String,
352 #[serde(
354 serialize_with = "serde_bytes::serialize",
355 deserialize_with = "serde_bytes::deserialize"
356 )]
357 pub contents: Vec<u8>,
358}
359
360#[cfg(feature = "python")]
361#[pyo3_stub_gen::derive::gen_stub_pymethods]
362#[pyo3::pymethods]
363impl RawFile {
364 #[getter]
365 fn contents(&self) -> Vec<u8> {
366 self.contents.clone()
367 }
368
369 #[getter]
370 fn name(&self) -> String {
371 self.name.clone()
372 }
373}
374
375impl From<ExportFile> for RawFile {
376 fn from(f: ExportFile) -> Self {
377 Self {
378 name: f.name,
379 contents: f.contents.0,
380 }
381 }
382}
383
384#[derive(Debug, Serialize, Deserialize, JsonSchema)]
386pub struct LoggableApiError {
387 pub error: ApiError,
389 pub msg_internal: Option<Cow<'static, str>>,
391}
392
393#[cfg(feature = "slog")]
394impl KV for LoggableApiError {
395 fn serialize(&self, _rec: &Record, serializer: &mut dyn Serializer) -> slog::Result {
396 use slog::Key;
397 if let Some(ref msg_internal) = self.msg_internal {
398 serializer.emit_str(Key::from("msg_internal"), msg_internal)?;
399 }
400 serializer.emit_str(Key::from("msg_external"), &self.error.message)?;
401 serializer.emit_str(Key::from("error_code"), &self.error.error_code.to_string())
402 }
403}
404
405#[derive(Debug, Serialize, Deserialize, JsonSchema, Eq, PartialEq, Clone)]
407pub struct ApiError {
408 pub error_code: ErrorCode,
410 pub message: String,
412}
413
414impl ApiError {
415 pub fn no_internal_message(self) -> LoggableApiError {
417 LoggableApiError {
418 error: self,
419 msg_internal: None,
420 }
421 }
422 pub fn with_message(self, msg_internal: Cow<'static, str>) -> LoggableApiError {
424 LoggableApiError {
425 error: self,
426 msg_internal: Some(msg_internal),
427 }
428 }
429
430 pub fn should_log_internal_message(&self) -> bool {
432 use ErrorCode as Code;
433 match self.error_code {
434 Code::InternalEngine | Code::InternalApi => true,
436 Code::MessageTypeNotAcceptedForWebRTC
438 | Code::MessageTypeNotAccepted
439 | Code::BadRequest
440 | Code::WrongProtocol
441 | Code::AuthTokenMissing
442 | Code::AuthTokenInvalid
443 | Code::InvalidBson
444 | Code::InvalidJson => false,
445 Code::ConnectionProblem => cfg!(debug_assertions),
447 }
448 }
449}
450
451#[derive(Debug, Serialize, Deserialize, JsonSchema)]
454#[serde(rename_all = "snake_case", rename = "SnakeCaseResult")]
455pub enum SnakeCaseResult<T, E> {
456 Ok(T),
458 Err(E),
460}
461
462impl<T, E> From<SnakeCaseResult<T, E>> for Result<T, E> {
463 fn from(value: SnakeCaseResult<T, E>) -> Self {
464 match value {
465 SnakeCaseResult::Ok(x) => Self::Ok(x),
466 SnakeCaseResult::Err(x) => Self::Err(x),
467 }
468 }
469}
470
471impl<T, E> From<Result<T, E>> for SnakeCaseResult<T, E> {
472 fn from(value: Result<T, E>) -> Self {
473 match value {
474 Ok(x) => Self::Ok(x),
475 Err(x) => Self::Err(x),
476 }
477 }
478}
479
480#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
482#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
483pub struct ClientMetrics {
484 pub rtc_frames_dropped: Option<u32>,
489
490 pub rtc_frames_decoded: Option<u64>,
495
496 pub rtc_frames_received: Option<u64>,
501
502 pub rtc_frames_per_second: Option<u8>, pub rtc_freeze_count: Option<u32>,
514
515 pub rtc_jitter_sec: Option<f64>,
525
526 pub rtc_keyframes_decoded: Option<u32>,
536
537 pub rtc_total_freezes_duration_sec: Option<f32>,
541
542 pub rtc_frame_height: Option<u32>,
546
547 pub rtc_frame_width: Option<u32>,
551
552 pub rtc_packets_lost: Option<u32>,
556
557 pub rtc_pli_count: Option<u32>,
561
562 pub rtc_pause_count: Option<u32>,
566
567 pub rtc_total_pauses_duration_sec: Option<f32>,
571
572 pub rtc_stun_rtt_sec: Option<f32>,
580}
581
582#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
584pub struct RtcIceCandidate {
585 pub stats_id: String,
587 pub foundation: String,
589 pub priority: u32,
591 pub address: String,
593 pub protocol: RtcIceProtocol,
595 pub port: u16,
597 pub typ: RtcIceCandidateType,
599 pub component: u16,
601 pub related_address: String,
603 pub related_port: u16,
605 pub tcp_type: String,
607}
608
609#[cfg(feature = "webrtc")]
610impl From<webrtc::ice_transport::ice_candidate::RTCIceCandidate> for RtcIceCandidate {
611 fn from(candidate: webrtc::ice_transport::ice_candidate::RTCIceCandidate) -> Self {
612 Self {
613 stats_id: candidate.stats_id,
614 foundation: candidate.foundation,
615 priority: candidate.priority,
616 address: candidate.address,
617 protocol: candidate.protocol.into(),
618 port: candidate.port,
619 typ: candidate.typ.into(),
620 component: candidate.component,
621 related_address: candidate.related_address,
622 related_port: candidate.related_port,
623 tcp_type: candidate.tcp_type,
624 }
625 }
626}
627
628#[cfg(feature = "webrtc")]
629impl From<RtcIceCandidate> for webrtc::ice_transport::ice_candidate::RTCIceCandidate {
630 fn from(candidate: RtcIceCandidate) -> Self {
631 Self {
632 stats_id: candidate.stats_id,
633 foundation: candidate.foundation,
634 priority: candidate.priority,
635 address: candidate.address,
636 protocol: candidate.protocol.into(),
637 port: candidate.port,
638 typ: candidate.typ.into(),
639 component: candidate.component,
640 related_address: candidate.related_address,
641 related_port: candidate.related_port,
642 tcp_type: candidate.tcp_type,
643 }
644 }
645}
646
647#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
649#[serde(rename_all = "snake_case")]
650pub enum RtcIceCandidateType {
651 #[default]
653 Unspecified,
654
655 Host,
661
662 Srflx,
669
670 Prflx,
675
676 Relay,
680}
681
682#[cfg(feature = "webrtc")]
683impl From<webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType> for RtcIceCandidateType {
684 fn from(candidate_type: webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType) -> Self {
685 match candidate_type {
686 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Host => RtcIceCandidateType::Host,
687 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Srflx => RtcIceCandidateType::Srflx,
688 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Prflx => RtcIceCandidateType::Prflx,
689 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Relay => RtcIceCandidateType::Relay,
690 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Unspecified => {
691 RtcIceCandidateType::Unspecified
692 }
693 }
694 }
695}
696
697#[cfg(feature = "webrtc")]
698impl From<RtcIceCandidateType> for webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType {
699 fn from(candidate_type: RtcIceCandidateType) -> Self {
700 match candidate_type {
701 RtcIceCandidateType::Host => webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Host,
702 RtcIceCandidateType::Srflx => webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Srflx,
703 RtcIceCandidateType::Prflx => webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Prflx,
704 RtcIceCandidateType::Relay => webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Relay,
705 RtcIceCandidateType::Unspecified => {
706 webrtc::ice_transport::ice_candidate_type::RTCIceCandidateType::Unspecified
707 }
708 }
709 }
710}
711
712#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
715#[serde(rename_all = "snake_case")]
716pub enum RtcIceProtocol {
717 #[default]
719 Unspecified,
720
721 Udp,
723
724 Tcp,
726}
727
728#[cfg(feature = "webrtc")]
729impl From<webrtc::ice_transport::ice_protocol::RTCIceProtocol> for RtcIceProtocol {
730 fn from(protocol: webrtc::ice_transport::ice_protocol::RTCIceProtocol) -> Self {
731 match protocol {
732 webrtc::ice_transport::ice_protocol::RTCIceProtocol::Udp => RtcIceProtocol::Udp,
733 webrtc::ice_transport::ice_protocol::RTCIceProtocol::Tcp => RtcIceProtocol::Tcp,
734 webrtc::ice_transport::ice_protocol::RTCIceProtocol::Unspecified => RtcIceProtocol::Unspecified,
735 }
736 }
737}
738
739#[cfg(feature = "webrtc")]
740impl From<RtcIceProtocol> for webrtc::ice_transport::ice_protocol::RTCIceProtocol {
741 fn from(protocol: RtcIceProtocol) -> Self {
742 match protocol {
743 RtcIceProtocol::Udp => webrtc::ice_transport::ice_protocol::RTCIceProtocol::Udp,
744 RtcIceProtocol::Tcp => webrtc::ice_transport::ice_protocol::RTCIceProtocol::Tcp,
745 RtcIceProtocol::Unspecified => webrtc::ice_transport::ice_protocol::RTCIceProtocol::Unspecified,
746 }
747 }
748}
749
750#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
752#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
753#[serde(rename_all = "camelCase")]
754pub struct RtcIceCandidateInit {
756 pub candidate: String,
758 pub sdp_mid: Option<String>,
761 #[serde(rename = "sdpMLineIndex")]
764 pub sdp_mline_index: Option<u16>,
765 pub username_fragment: Option<String>,
768}
769
770#[cfg(feature = "webrtc")]
771impl From<webrtc::ice_transport::ice_candidate::RTCIceCandidateInit> for RtcIceCandidateInit {
772 fn from(candidate: webrtc::ice_transport::ice_candidate::RTCIceCandidateInit) -> Self {
773 Self {
774 candidate: candidate.candidate,
775 sdp_mid: candidate.sdp_mid,
776 sdp_mline_index: candidate.sdp_mline_index,
777 username_fragment: candidate.username_fragment,
778 }
779 }
780}
781
782#[cfg(feature = "webrtc")]
783impl From<RtcIceCandidateInit> for webrtc::ice_transport::ice_candidate::RTCIceCandidateInit {
784 fn from(candidate: RtcIceCandidateInit) -> Self {
785 Self {
786 candidate: candidate.candidate,
787 sdp_mid: candidate.sdp_mid,
788 sdp_mline_index: candidate.sdp_mline_index,
789 username_fragment: candidate.username_fragment,
790 }
791 }
792}
793
794#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
796#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
797pub struct RtcSessionDescription {
798 #[serde(rename = "type")]
800 pub sdp_type: RtcSdpType,
801
802 pub sdp: String,
804}
805
806#[cfg(feature = "webrtc")]
807impl From<webrtc::peer_connection::sdp::session_description::RTCSessionDescription> for RtcSessionDescription {
808 fn from(desc: webrtc::peer_connection::sdp::session_description::RTCSessionDescription) -> Self {
809 Self {
810 sdp_type: desc.sdp_type.into(),
811 sdp: desc.sdp,
812 }
813 }
814}
815
816#[cfg(feature = "webrtc")]
817impl TryFrom<RtcSessionDescription> for webrtc::peer_connection::sdp::session_description::RTCSessionDescription {
818 type Error = anyhow::Error;
819
820 fn try_from(desc: RtcSessionDescription) -> Result<Self, Self::Error> {
821 let result = match desc.sdp_type {
822 RtcSdpType::Offer => {
823 webrtc::peer_connection::sdp::session_description::RTCSessionDescription::offer(desc.sdp)?
824 }
825 RtcSdpType::Pranswer => {
826 webrtc::peer_connection::sdp::session_description::RTCSessionDescription::pranswer(desc.sdp)?
827 }
828 RtcSdpType::Answer => {
829 webrtc::peer_connection::sdp::session_description::RTCSessionDescription::answer(desc.sdp)?
830 }
831 RtcSdpType::Rollback => anyhow::bail!("Rollback is not supported"),
832 RtcSdpType::Unspecified => anyhow::bail!("Unspecified is not supported"),
833 };
834
835 Ok(result)
836 }
837}
838
839#[derive(Default, Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize, JsonSchema)]
841#[serde(rename_all = "snake_case")]
842#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
843pub enum RtcSdpType {
844 #[default]
846 Unspecified = 0,
847
848 Offer,
850
851 Pranswer,
856
857 Answer,
862
863 Rollback,
869}
870
871#[cfg(feature = "webrtc")]
872impl From<webrtc::peer_connection::sdp::sdp_type::RTCSdpType> for RtcSdpType {
873 fn from(sdp_type: webrtc::peer_connection::sdp::sdp_type::RTCSdpType) -> Self {
874 match sdp_type {
875 webrtc::peer_connection::sdp::sdp_type::RTCSdpType::Offer => Self::Offer,
876 webrtc::peer_connection::sdp::sdp_type::RTCSdpType::Pranswer => Self::Pranswer,
877 webrtc::peer_connection::sdp::sdp_type::RTCSdpType::Answer => Self::Answer,
878 webrtc::peer_connection::sdp::sdp_type::RTCSdpType::Rollback => Self::Rollback,
879 webrtc::peer_connection::sdp::sdp_type::RTCSdpType::Unspecified => Self::Unspecified,
880 }
881 }
882}
883
884#[cfg(feature = "webrtc")]
885impl From<RtcSdpType> for webrtc::peer_connection::sdp::sdp_type::RTCSdpType {
886 fn from(sdp_type: RtcSdpType) -> Self {
887 match sdp_type {
888 RtcSdpType::Offer => Self::Offer,
889 RtcSdpType::Pranswer => Self::Pranswer,
890 RtcSdpType::Answer => Self::Answer,
891 RtcSdpType::Rollback => Self::Rollback,
892 RtcSdpType::Unspecified => Self::Unspecified,
893 }
894 }
895}
896#[derive(JsonSchema, Debug, Serialize, Deserialize, Clone, PartialEq)]
898#[serde(rename_all = "snake_case")]
899pub struct ModelingSessionData {
900 pub api_call_id: String,
903}
904
905#[cfg(test)]
906mod tests {
907 use super::*;
908 use crate::output;
909
910 const REQ_ID: Uuid = uuid::uuid!("cc30d5e2-482b-4498-b5d2-6131c30a50a4");
911
912 #[test]
913 fn serialize_websocket_modeling_ok() {
914 let actual = WebSocketResponse::Success(SuccessWebSocketResponse {
915 success: true,
916 request_id: Some(REQ_ID),
917 resp: OkWebSocketResponseData::Modeling {
918 modeling_response: OkModelingCmdResponse::CurveGetControlPoints(output::CurveGetControlPoints {
919 control_points: vec![],
920 }),
921 },
922 });
923 let expected = serde_json::json!({
924 "success": true,
925 "request_id": "cc30d5e2-482b-4498-b5d2-6131c30a50a4",
926 "resp": {
927 "type": "modeling",
928 "data": {
929 "modeling_response": {
930 "type": "curve_get_control_points",
931 "data": { "control_points": [] }
932 }
933 }
934 }
935 });
936 assert_json_eq(actual, expected);
937 }
938
939 #[test]
940 fn serialize_websocket_webrtc_ok() {
941 let actual = WebSocketResponse::Success(SuccessWebSocketResponse {
942 success: true,
943 request_id: Some(REQ_ID),
944 resp: OkWebSocketResponseData::IceServerInfo { ice_servers: vec![] },
945 });
946 let expected = serde_json::json!({
947 "success": true,
948 "request_id": "cc30d5e2-482b-4498-b5d2-6131c30a50a4",
949 "resp": {
950 "type": "ice_server_info",
951 "data": {
952 "ice_servers": []
953 }
954 }
955 });
956 assert_json_eq(actual, expected);
957 }
958
959 #[test]
960 fn serialize_websocket_export_ok() {
961 let actual = WebSocketResponse::Success(SuccessWebSocketResponse {
962 success: true,
963 request_id: Some(REQ_ID),
964 resp: OkWebSocketResponseData::Export { files: vec![] },
965 });
966 let expected = serde_json::json!({
967 "success": true,
968 "request_id": "cc30d5e2-482b-4498-b5d2-6131c30a50a4",
969 "resp": {
970 "type": "export",
971 "data": {"files": [] }
972 }
973 });
974 assert_json_eq(actual, expected);
975 }
976
977 #[test]
978 fn serialize_websocket_err() {
979 let actual = WebSocketResponse::Failure(FailureWebSocketResponse {
980 success: false,
981 request_id: Some(REQ_ID),
982 errors: vec![ApiError {
983 error_code: ErrorCode::InternalApi,
984 message: "you fucked up!".to_owned(),
985 }],
986 });
987 let expected = serde_json::json!({
988 "success": false,
989 "request_id": "cc30d5e2-482b-4498-b5d2-6131c30a50a4",
990 "errors": [
991 {
992 "error_code": "internal_api",
993 "message": "you fucked up!"
994 }
995 ],
996 });
997 assert_json_eq(actual, expected);
998 }
999
1000 #[test]
1001 fn serialize_websocket_metrics() {
1002 let actual = WebSocketRequest::MetricsResponse {
1003 metrics: Box::new(ClientMetrics {
1004 rtc_frames_dropped: Some(1),
1005 rtc_frames_decoded: Some(2),
1006 rtc_frames_per_second: Some(3),
1007 rtc_frames_received: Some(4),
1008 rtc_freeze_count: Some(5),
1009 rtc_jitter_sec: Some(6.7),
1010 rtc_keyframes_decoded: Some(8),
1011 rtc_total_freezes_duration_sec: Some(9.1),
1012 rtc_frame_height: Some(100),
1013 rtc_frame_width: Some(100),
1014 rtc_packets_lost: Some(0),
1015 rtc_pli_count: Some(0),
1016 rtc_pause_count: Some(0),
1017 rtc_total_pauses_duration_sec: Some(0.0),
1018 rtc_stun_rtt_sec: Some(0.005),
1019 }),
1020 };
1021 let expected = serde_json::json!({
1022 "type": "metrics_response",
1023 "metrics": {
1024 "rtc_frames_dropped": 1,
1025 "rtc_frames_decoded": 2,
1026 "rtc_frames_per_second": 3,
1027 "rtc_frames_received": 4,
1028 "rtc_freeze_count": 5,
1029 "rtc_jitter_sec": 6.7,
1030 "rtc_keyframes_decoded": 8,
1031 "rtc_total_freezes_duration_sec": 9.1,
1032 "rtc_frame_height": 100,
1033 "rtc_frame_width": 100,
1034 "rtc_packets_lost": 0,
1035 "rtc_pli_count": 0,
1036 "rtc_pause_count": 0,
1037 "rtc_total_pauses_duration_sec": 0.0,
1038 "rtc_stun_rtt_sec": 0.005,
1039 },
1040 });
1041 assert_json_eq(actual, expected);
1042 }
1043
1044 fn assert_json_eq<T: Serialize>(actual: T, expected: serde_json::Value) {
1045 let json_str = serde_json::to_string(&actual).unwrap();
1046 let actual: serde_json::Value = serde_json::from_str(&json_str).unwrap();
1047 assert_eq!(actual, expected, "got\n{actual:#}\n, expected\n{expected:#}\n");
1048 }
1049}