use std::str::FromStr as _;
use crate::server::error::{AppResult, Error};
use super::Mode;
use anyhow::Context;
use serde::{Deserialize, Serialize};
use sqlx::types::JsonValue;
use tracing::debug;
#[derive(Deserialize)]
pub(super) struct UseragentJson {
pub mode: String,
pub value: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Useragent {
pub mode: Mode,
pub value: Option<String>,
}
impl TryFrom<UseragentJson> for Useragent {
type Error = crate::server::error::Error;
fn try_from(useragent: UseragentJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&useragent.mode)?;
Ok(Self {
mode,
value: useragent.value,
})
}
}
#[derive(Deserialize)]
pub(super) struct ScreenJson {
pub resolution: Option<String>,
pub mode: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Screen {
pub width: Option<i32>,
pub height: Option<i32>,
pub mode: Mode,
}
impl TryFrom<ScreenJson> for Screen {
type Error = crate::server::error::Error;
fn try_from(screen: ScreenJson) -> Result<Self, Self::Error> {
let mode = match screen.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
if screen.resolution.is_none() && mode == Mode::Manual {
return Err(Error::BadRequest("Resolution is required".to_string()));
}
let (width, height) = match screen.resolution {
None => (None, None),
Some(resolution) => {
let split = resolution.split('x').collect::<Vec<&str>>();
let width = split
.first()
.map(|s| s.parse().context("Failed to parse width"));
let height = split
.get(1)
.map(|s| s.parse().context("Failed to parse height"));
(width.transpose()?, height.transpose()?)
}
};
Ok(Self {
width,
height,
mode,
})
}
}
#[derive(Deserialize)]
pub(super) struct ClientRectJson {
pub mode: Option<String>,
pub noise: Option<Vec<f64>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ClientRect {
pub mode: Mode,
pub noise: Option<Vec<f64>>,
}
impl TryFrom<ClientRectJson> for ClientRect {
type Error = crate::server::error::Error;
fn try_from(client_rect: ClientRectJson) -> Result<Self, Self::Error> {
let mode = match client_rect.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
if mode == Mode::Noise && client_rect.noise.is_none() {
return Err(Error::BadRequest("Noise is required".to_string()));
}
Ok(Self {
mode,
noise: client_rect.noise,
})
}
}
#[derive(Deserialize)]
pub(super) struct WebrtcJson {
pub mode: Option<String>,
#[serde(rename = "ipAddress")]
pub ip_address: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Webrtc {
pub mode: Mode,
pub ip_address: Option<String>,
}
impl TryFrom<WebrtcJson> for Webrtc {
type Error = crate::server::error::Error;
fn try_from(webrtc: WebrtcJson) -> Result<Self, Self::Error> {
let mode = match webrtc.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
let ip_address = match mode {
Mode::Manual => webrtc.ip_address,
Mode::Altered => None,
Mode::Off => None,
_ => None,
};
Ok(Self { mode, ip_address })
}
}
#[derive(Deserialize)]
pub(super) struct CanvasJson {
pub mode: Option<String>,
pub noise: Option<Vec<f32>>,
}
#[derive(Debug, Clone)]
pub struct Canvas {
pub mode: Mode,
pub noise: Option<Vec<f32>>,
}
impl TryFrom<CanvasJson> for Canvas {
type Error = crate::server::error::Error;
fn try_from(canvas: CanvasJson) -> Result<Self, Self::Error> {
let mode = match canvas.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
if mode == Mode::Noise && canvas.noise.is_none() {
return Err(Error::BadRequest("Noise is required".to_string()));
}
Ok(Self {
mode,
noise: canvas.noise,
})
}
}
#[derive(Deserialize)]
pub(super) struct WebglJson {
pub mode: Option<String>,
pub noise: Option<Vec<f32>>,
}
#[derive(Debug, Clone)]
pub struct Webgl {
pub mode: Mode,
pub noise: Option<Vec<f32>>,
}
impl TryFrom<WebglJson> for Webgl {
type Error = crate::server::error::Error;
fn try_from(webgl: WebglJson) -> Result<Self, Self::Error> {
let mode = match webgl.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
if mode == Mode::Noise && webgl.noise.is_none() {
return Err(Error::BadRequest("Noise is required".to_string()));
}
Ok(Self {
mode,
noise: webgl.noise,
})
}
}
#[derive(Deserialize)]
pub(super) struct WebglInfoJson {
pub mode: Option<String>,
pub vendor: Option<String>,
pub renderer: Option<String>,
pub webgl2_maximum: Option<String>,
}
#[derive(Debug, Clone)]
pub struct WebglInfo {
pub mode: Mode,
pub vendor: Option<String>,
pub renderer: Option<String>,
}
impl TryFrom<WebglInfoJson> for WebglInfo {
type Error = crate::server::error::Error;
fn try_from(webgl_info: WebglInfoJson) -> Result<Self, Self::Error> {
let mode = match webgl_info.mode {
Some(mode) => Mode::from_str(&mode)?,
None => {
return Err(Error::DatabaseParsing("Mode is required".to_string()))
}
};
Ok(Self {
mode,
vendor: webgl_info.vendor,
renderer: webgl_info.renderer,
})
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub struct WebGL2Maximum {
pub uniform_buffer_offset_alignment: i64,
pub max_texture_size: i64,
pub max_viewport_dims: [i64; 2],
pub max_vertex_attribs: i64,
pub max_vertex_uniform_vectors: i64,
pub max_varying_vectors: i64,
pub max_combined_texture_image_units: i64,
pub max_vertex_texture_image_units: i64,
pub max_texture_image_units: i64,
pub max_fragment_uniform_vectors: i64,
pub max_cube_map_texture_size: i64,
pub max_renderbuffer_size: i64,
pub max_3d_texture_size: i64,
pub max_elements_vertices: i64,
pub max_elements_indices: i64,
pub max_texture_lod_bias: f64,
pub max_draw_buffers: i64,
pub max_fragment_uniform_components: i64,
pub max_vertex_uniform_components: i64,
pub max_array_texture_layers: i64,
pub min_program_texel_offset: i64,
pub max_program_texel_offset: i64,
pub max_varying_components: i64,
pub max_transform_feedback_separate_components: i64,
pub max_transform_feedback_interleaved_components: i64,
pub max_transform_feedback_separate_attribs: i64,
pub max_color_attachments: i64,
pub max_samples: i64,
pub max_vertex_uniform_blocks: i64,
pub max_fragment_uniform_blocks: i64,
pub max_combined_uniform_blocks: i64,
pub max_uniform_buffer_bindings: i64,
pub max_uniform_block_size: i64,
pub max_combined_vertex_uniform_components: i64,
pub max_combined_fragment_uniform_components: i64,
pub max_vertex_output_components: i64,
pub max_fragment_input_components: i64,
pub max_element_index: i64,
}
#[derive(Debug, Clone)]
pub struct MediaDevices {
pub mode: Mode,
pub audio_inputs: u8,
pub video_inputs: u8,
pub audio_outputs: u8,
}
impl TryFrom<JsonValue> for MediaDevices {
type Error = crate::server::error::Error;
fn try_from(media_devices: JsonValue) -> Result<Self, Self::Error> {
let mut mode = Mode::Real;
let mut audio_inputs = 0;
let mut video_inputs = 0;
let mut audio_outputs = 0;
if let Some(media_devices) = media_devices.as_object() {
if let Some(mode_value) = media_devices.get("mode") {
if let Some(mode_value) = mode_value.as_str() {
let new_mode = Mode::from_str(mode_value)?;
if new_mode != Mode::Unknown {
mode = new_mode;
}
}
}
}
if let Some(audio_inputs_value) = media_devices.get("audio_inputs") {
if let Some(audio_inputs_value) = audio_inputs_value.as_u64() {
audio_inputs = audio_inputs_value as u8;
}
}
if let Some(video_inputs_value) = media_devices.get("video_inputs") {
if let Some(video_inputs_value) = video_inputs_value.as_u64() {
video_inputs = video_inputs_value as u8;
}
}
if let Some(audio_outputs_value) = media_devices.get("audio_outputs") {
if let Some(audio_outputs_value) = audio_outputs_value.as_u64() {
audio_outputs = audio_outputs_value as u8;
}
}
Ok(Self {
mode,
audio_inputs,
video_inputs,
audio_outputs,
})
}
}
#[derive(Deserialize, PartialEq, Eq)]
pub(super) struct PortsJson {
pub mode: Option<String>,
pub blacklist: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ports {
pub mode: Mode,
pub blacklist: Vec<u16>,
}
impl TryFrom<PortsJson> for Ports {
type Error = crate::server::error::Error;
fn try_from(ports: PortsJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&ports.mode.unwrap_or("protect".to_string()))?;
let blacklist = ports
.blacklist
.unwrap_or_default()
.split(',')
.filter_map(|s| s.trim().parse().ok())
.collect();
Ok(Self { mode, blacklist })
}
}
#[derive(Debug, Deserialize)]
pub(super) struct CpuJson {
pub mode: Option<String>,
pub value: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct Cpu {
pub mode: Mode,
pub value: u8,
}
impl TryFrom<CpuJson> for Cpu {
type Error = crate::server::error::Error;
fn try_from(cpu: CpuJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&cpu.mode.unwrap_or("real".to_string()))?;
let value = cpu.value.unwrap_or(0);
Ok(Self { mode, value })
}
}
#[derive(Debug, Deserialize)]
pub(super) struct MemoryJson {
pub mode: Option<String>,
pub value: Option<u8>,
}
#[derive(Debug, Clone)]
pub struct Memory {
pub mode: Mode,
pub value: u8,
}
impl TryFrom<MemoryJson> for Memory {
type Error = crate::server::error::Error;
fn try_from(memory: MemoryJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&memory.mode.unwrap_or("real".to_string()))?;
let value = memory.value.unwrap_or(0);
Ok(Self { mode, value })
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct Args(pub Option<Vec<String>>);
impl Args {
pub fn join(&self) -> String {
self
.0
.clone()
.unwrap_or_default()
.join(" ")
.trim()
.to_string()
}
pub fn splitted(&self) -> Vec<String> {
self.0.clone().unwrap_or_default()
}
pub fn from_json_str(json: &str) -> AppResult<Self> {
if json.is_empty() {
return Ok(Self(None));
}
let value: Option<Vec<String>> = serde_json::from_str(json)
.context("Failed to parse args from JSON string")?;
let value = value
.unwrap_or_default()
.iter()
.map(|arg| {
if !arg.starts_with("--") {
"--".to_string() + arg
} else {
arg.clone()
}
})
.collect();
Ok(Self(Some(value)))
}
}
#[derive(Debug, Deserialize)]
pub(super) struct GeolocationJson {
pub mode: Option<String>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub accuracy: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct Geolocation {
pub mode: Mode,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub accuracy: Option<f64>,
}
impl TryFrom<GeolocationJson> for Geolocation {
type Error = crate::server::error::Error;
fn try_from(geolocation: GeolocationJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&geolocation.mode.unwrap_or("auto".to_string()))?;
let latitude = geolocation.latitude;
let longitude = geolocation.longitude;
let accuracy = geolocation.accuracy;
Ok(Self {
mode,
latitude,
longitude,
accuracy,
})
}
}
#[derive(Debug, Deserialize)]
pub(super) struct LocaleJson {
pub mode: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Locale {
pub mode: Mode,
pub value: Option<String>,
}
impl TryFrom<LocaleJson> for Locale {
type Error = crate::server::error::Error;
fn try_from(locale: LocaleJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&locale.mode.unwrap_or("auto".to_string()))?;
let value = locale.value;
Ok(Self { mode, value })
}
}
#[derive(Debug, Deserialize)]
pub(super) struct TimezoneJson {
pub mode: Option<String>,
pub value: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Timezone {
pub mode: Mode,
pub value: Option<String>,
}
impl TryFrom<TimezoneJson> for Timezone {
type Error = crate::server::error::Error;
fn try_from(timezone: TimezoneJson) -> Result<Self, Self::Error> {
let mode = Mode::from_str(&timezone.mode.unwrap_or("auto".to_string()))?;
let value = timezone.value;
Ok(Self { mode, value })
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct Tabs(pub Vec<String>);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WebGPUInfo {
pub mode: Mode,
pub limits: Option<Limits>,
pub get_preferred_canvas_format: Option<String>,
pub info: Option<Info>,
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Limits {
pub max_bind_groups: u32,
pub max_bindings_per_bind_group: u32,
pub max_buffer_size: u64,
pub max_color_attachment_bytes_per_sample: u32,
pub max_color_attachments: u32,
pub max_compute_invocations_per_workgroup: u32,
pub max_compute_workgroup_size_x: u32,
pub max_compute_workgroup_size_y: u32,
pub max_compute_workgroup_size_z: u32,
pub max_compute_workgroup_storage_size: u32,
pub max_compute_workgroups_per_dimension: u32,
pub max_dynamic_storage_buffers_per_pipeline_layout: u32,
pub max_dynamic_uniform_buffers_per_pipeline_layout: u32,
pub max_inter_stage_shader_components: u32,
pub max_inter_stage_shader_variables: u32,
pub max_sampled_textures_per_shader_stage: u32,
pub max_samplers_per_shader_stage: u32,
pub max_storage_buffer_binding_size: u64,
pub max_storage_buffers_per_shader_stage: u32,
pub max_storage_textures_per_shader_stage: u32,
pub max_texture_array_layers: u32,
#[serde(rename = "maxTextureDimension1D")]
pub max_texture_dimension_1d: u32,
#[serde(rename = "maxTextureDimension2D")]
pub max_texture_dimension_2d: u32,
#[serde(rename = "maxTextureDimension3D")]
pub max_texture_dimension_3d: u32,
pub max_uniform_buffer_binding_size: u64,
pub max_uniform_buffers_per_shader_stage: u32,
pub max_vertex_attributes: u32,
pub max_vertex_buffer_array_stride: u32,
pub max_vertex_buffers: u32,
pub min_storage_buffer_offset_alignment: u32,
pub min_uniform_buffer_offset_alignment: u32,
}
impl Limits {
pub fn get_mock() -> Self {
Self {
max_bind_groups: 1,
max_bindings_per_bind_group: 1,
max_buffer_size: 1,
max_color_attachment_bytes_per_sample: 1,
max_color_attachments: 1,
max_compute_invocations_per_workgroup: 1,
max_compute_workgroup_size_x: 1,
max_compute_workgroup_size_y: 1,
max_compute_workgroup_size_z: 1,
max_compute_workgroup_storage_size: 1,
max_compute_workgroups_per_dimension: 1,
max_dynamic_storage_buffers_per_pipeline_layout: 1,
max_dynamic_uniform_buffers_per_pipeline_layout: 1,
max_inter_stage_shader_components: 1,
max_inter_stage_shader_variables: 1,
max_sampled_textures_per_shader_stage: 1,
max_samplers_per_shader_stage: 1,
max_storage_buffer_binding_size: 1,
max_storage_buffers_per_shader_stage: 1,
max_storage_textures_per_shader_stage: 1,
max_texture_array_layers: 1,
max_texture_dimension_1d: 1,
max_texture_dimension_2d: 1,
max_texture_dimension_3d: 1,
max_uniform_buffer_binding_size: 1,
max_uniform_buffers_per_shader_stage: 1,
max_vertex_attributes: 1,
max_vertex_buffer_array_stride: 1,
max_vertex_buffers: 1,
min_storage_buffer_offset_alignment: 1,
min_uniform_buffer_offset_alignment: 1,
}
}
}
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub struct Info {
pub architecture: String,
pub description: String,
pub device: String,
pub vendor: String,
}
impl TryFrom<Option<JsonValue>> for WebGPUInfo {
type Error = crate::server::error::Error;
fn try_from(webgpu: Option<JsonValue>) -> Result<Self, Self::Error> {
debug!("got webgpu to parse: {:?}", webgpu);
let webgpu = webgpu.unwrap_or_else(|| {
debug!("webgpu is none, setting it to Real with no params");
serde_json::json!({})
});
let mode = Mode::from_str(
webgpu
.get("mode")
.unwrap_or(&serde_json::json!("real"))
.as_str()
.unwrap_or("real"),
)?;
#[derive(Deserialize)]
struct TempWebgpu {
limits: Option<Limits>,
#[serde(rename = "getPreferredCanvasFormat")]
get_preferred_canvas_format: Option<String>,
info: Option<Info>,
}
let value_str = webgpu.get("value").and_then(|v| v.as_str()).unwrap_or("");
let unescaped = if !value_str.is_empty() {
serde_json::from_str::<String>(value_str)
.context("Failed to parse first level of webgpu JSON string")?
} else {
String::new()
};
let stringed: TempWebgpu = if !unescaped.is_empty() {
serde_json::from_str(&unescaped).context("Failed to parse webgpu data")?
} else {
TempWebgpu {
limits: None,
get_preferred_canvas_format: None,
info: None,
}
};
Ok(Self {
mode,
limits: stringed.limits,
get_preferred_canvas_format: stringed.get_preferred_canvas_format,
info: stringed.info,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_webgpu_info_try_from_empty() {
let webgpu = serde_json::json!({});
let webgpu_info = WebGPUInfo::try_from(Some(webgpu)).unwrap();
assert_eq!(webgpu_info.mode, Mode::Real);
}
#[test]
fn test_webgpu_info_try_from_only_mode() {
let webgpu = serde_json::json!({"mode": "manual"});
let webgpu_info = WebGPUInfo::try_from(Some(webgpu)).unwrap();
assert_eq!(
webgpu_info,
WebGPUInfo {
mode: Mode::Manual,
limits: None,
get_preferred_canvas_format: None,
info: None,
}
);
}
#[test]
fn test_webgpu_info_try_from_real_database_value() {
let webgpu = serde_json::json!({"mode": "manual", "value": "\"{\\\"limits\\\":{\\\"maxBindGroups\\\":4,\\\"maxBindingsPerBindGroup\\\":640,\\\"maxBufferSize\\\":2147483648,\\\"maxColorAttachmentBytesPerSample\\\":32,\\\"maxColorAttachments\\\":8,\\\"maxComputeInvocationsPerWorkgroup\\\":1024,\\\"maxComputeWorkgroupSizeX\\\":1024,\\\"maxComputeWorkgroupSizeY\\\":1024,\\\"maxComputeWorkgroupSizeZ\\\":64,\\\"maxComputeWorkgroupStorageSize\\\":32768,\\\"maxComputeWorkgroupsPerDimension\\\":65535,\\\"maxDynamicStorageBuffersPerPipelineLayout\\\":8,\\\"maxDynamicUniformBuffersPerPipelineLayout\\\":10,\\\"maxInterStageShaderComponents\\\":60,\\\"maxInterStageShaderVariables\\\":16,\\\"maxSampledTexturesPerShaderStage\\\":16,\\\"maxSamplersPerShaderStage\\\":16,\\\"maxStorageBufferBindingSize\\\":2147483647,\\\"maxStorageBuffersPerShaderStage\\\":8,\\\"maxStorageTexturesPerShaderStage\\\":8,\\\"maxTextureArrayLayers\\\":256,\\\"maxTextureDimension1D\\\":8192,\\\"maxTextureDimension2D\\\":8192,\\\"maxTextureDimension3D\\\":2048,\\\"maxUniformBufferBindingSize\\\":65536,\\\"maxUniformBuffersPerShaderStage\\\":12,\\\"maxVertexAttributes\\\":16,\\\"maxVertexBufferArrayStride\\\":2048,\\\"maxVertexBuffers\\\":8,\\\"minStorageBufferOffsetAlignment\\\":256,\\\"minUniformBufferOffsetAlignment\\\":256},\\\"getPreferredCanvasFormat\\\":\\\"bgra8unorm\\\",\\\"info\\\":{\\\"architecture\\\":\\\"gcn-2\\\",\\\"description\\\":\\\"\\\",\\\"device\\\":\\\"\\\",\\\"vendor\\\":\\\"amd\\\"}}\""});
let webgpu_info = WebGPUInfo::try_from(Some(webgpu)).unwrap();
assert_eq!(
webgpu_info,
WebGPUInfo {
mode: Mode::Manual,
limits: Some(Limits {
max_bind_groups: 4,
max_bindings_per_bind_group: 640,
max_buffer_size: 2147483648,
max_color_attachment_bytes_per_sample: 32,
max_color_attachments: 8,
max_compute_invocations_per_workgroup: 1024,
max_compute_workgroup_size_x: 1024,
max_compute_workgroup_size_y: 1024,
max_compute_workgroup_size_z: 64,
max_compute_workgroup_storage_size: 32768,
max_compute_workgroups_per_dimension: 65535,
max_dynamic_storage_buffers_per_pipeline_layout: 8,
max_dynamic_uniform_buffers_per_pipeline_layout: 10,
max_inter_stage_shader_components: 60,
max_inter_stage_shader_variables: 16,
max_sampled_textures_per_shader_stage: 16,
max_samplers_per_shader_stage: 16,
max_storage_buffer_binding_size: 2147483647,
max_storage_buffers_per_shader_stage: 8,
max_storage_textures_per_shader_stage: 8,
max_texture_array_layers: 256,
max_texture_dimension_1d: 8192,
max_texture_dimension_2d: 8192,
max_texture_dimension_3d: 2048,
max_uniform_buffer_binding_size: 65536,
max_uniform_buffers_per_shader_stage: 12,
max_vertex_attributes: 16,
max_vertex_buffer_array_stride: 2048,
max_vertex_buffers: 8,
min_storage_buffer_offset_alignment: 256,
min_uniform_buffer_offset_alignment: 256,
}),
get_preferred_canvas_format: Some("bgra8unorm".to_string()),
info: Some(Info {
architecture: "gcn-2".to_string(),
description: "".to_string(),
device: "".to_string(),
vendor: "amd".to_string(),
}),
}
);
}
#[test]
fn test_webgl_noise_parse_json() {
let webgl = serde_json::json!({"mode": "noise", "noise": [1.0, 2.0]});
let webgl: WebglJson = serde_json::from_value(webgl).unwrap();
assert_eq!(webgl.noise, Some(vec![1., 2.]));
let webgl = serde_json::json!({"mode": "noise", "noise": []});
let webgl: WebglJson = serde_json::from_value(webgl).unwrap();
assert_eq!(webgl.noise, Some(vec![]));
let webgl = serde_json::json!({"mode": "noise", "noise": [1, 2, 3]});
let webgl: WebglJson = serde_json::from_value(webgl).unwrap();
assert_eq!(webgl.noise, Some(vec![1., 2., 3.]));
let webgl = String::from("{\"mode\":\"noise\",\"noise\":[1, 2.5 ,3]}");
let webgl: WebglJson = serde_json::from_str(&webgl).unwrap();
assert_eq!(webgl.noise, Some(vec![1., 2.5, 3.]));
}
#[test]
fn test_canvas_noise_parse_json() {
let canvas = String::from("{\"mode\":\"noise\",\"noise\":[1, 2.5, 3.5]}");
let canvas: CanvasJson = serde_json::from_str(&canvas).unwrap();
assert_eq!(canvas.noise, Some(vec![1., 2.5, 3.5]));
}
#[test]
fn test_ports_from_json() {
let ports =
String::from("{\"mode\":\"protect\",\"blacklist\":\"1, 2, 3\"}");
let ports: PortsJson = serde_json::from_str(&ports).unwrap();
assert_eq!(ports.mode, Some("protect".to_string()));
assert_eq!(ports.blacklist, Some("1, 2, 3".to_string()));
let ports: Ports = ports.try_into().unwrap();
assert_eq!(ports.mode, Mode::Protect);
assert_eq!(ports.blacklist, vec![1, 2, 3]);
let ports = String::from("{\"mode\":\"real\"}");
let ports: PortsJson = serde_json::from_str(&ports).unwrap();
assert_eq!(ports.mode, Some("real".to_string()));
assert_eq!(ports.blacklist, None);
let ports: Ports = ports.try_into().unwrap();
assert_eq!(ports.mode, Mode::Real);
assert_eq!(ports.blacklist, Vec::<u16>::new());
let ports = String::from("{\"mode\":\"protect\",\"blacklist\":\"\"}");
let ports: PortsJson = serde_json::from_str(&ports).unwrap();
assert_eq!(ports.mode, Some("protect".to_string()));
assert_eq!(ports.blacklist, Some("".to_string()));
let ports: Ports = ports.try_into().unwrap();
assert_eq!(ports.mode, Mode::Protect);
assert_eq!(ports.blacklist, Vec::<u16>::new());
}
#[test]
fn test_args_from_json() {
let args = String::from("[\"some-arg=value\",\"another-arg\"]");
let args = Args::from_json_str(&args).unwrap();
assert_eq!(
args.0,
Some(vec![
"some-arg=value".to_string(),
"another-arg".to_string()
])
);
let args = String::from("[]");
let args = Args::from_json_str(&args).unwrap();
assert_eq!(args.0, Some(vec![]));
let args = String::from("");
let args = Args::from_json_str(&args).unwrap();
assert_eq!(args.0, None);
}
}