First commit

This commit is contained in:
CyberRex
2026-07-31 14:39:33 +09:00
commit 38670efd46
103 changed files with 22514 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
[package]
name = "mirakurun-types"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,808 @@
// Copyright 2026 Mirakurun contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
pub type ProgramId = u64;
pub type EventId = u16;
pub type ServiceId = u16;
pub type NetworkId = u16;
pub type ServiceItemId = u64;
pub type UnixTimeMs = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ChannelType {
#[serde(rename = "GR")]
Gr,
#[serde(rename = "BS")]
Bs,
#[serde(rename = "CS")]
Cs,
#[serde(rename = "SKY")]
Sky,
}
impl ChannelType {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Gr => "GR",
Self::Bs => "BS",
Self::Cs => "CS",
Self::Sky => "SKY",
}
}
}
impl std::str::FromStr for ChannelType {
type Err = String;
fn from_str(value: &str) -> Result<Self, Self::Err> {
match value {
"GR" => Ok(Self::Gr),
"BS" => Ok(Self::Bs),
"CS" => Ok(Self::Cs),
"SKY" => Ok(Self::Sky),
_ => Err(format!("unsupported channel type: {value}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Channel {
#[serde(rename = "type")]
pub channel_type: ChannelType,
pub channel: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub services: Option<Vec<Service>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Service {
pub id: ServiceItemId,
pub service_id: ServiceId,
pub network_id: NetworkId,
pub name: String,
#[serde(rename = "type")]
pub service_type: u8,
#[serde(skip_serializing_if = "Option::is_none")]
pub logo_id: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_logo_data: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_control_key_id: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epg_ready: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epg_updated_at: Option<UnixTimeMs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channel: Option<Box<Channel>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Program {
pub id: ProgramId,
pub event_id: EventId,
pub service_id: ServiceId,
pub network_id: NetworkId,
pub start_at: UnixTimeMs,
pub duration: u64,
pub is_free: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub genres: Option<Vec<ProgramGenre>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub video: Option<ProgramVideo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audios: Option<Vec<ProgramAudio>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub series: Option<ProgramSeries>,
#[serde(skip_serializing_if = "Option::is_none")]
pub extended: Option<BTreeMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub related_items: Option<Vec<ProgramRelatedItem>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramGenre {
pub lv1: u8,
pub lv2: u8,
pub un1: u8,
pub un2: u8,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramVideo {
#[serde(rename = "type")]
pub video_type: ProgramVideoType,
pub resolution: ProgramVideoResolution,
pub stream_content: u8,
pub component_type: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProgramVideoType {
#[serde(rename = "mpeg2")]
Mpeg2,
#[serde(rename = "h.264")]
H264,
#[serde(rename = "h.265")]
H265,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProgramVideoResolution {
#[serde(rename = "240p")]
P240,
#[serde(rename = "480i")]
I480,
#[serde(rename = "480p")]
P480,
#[serde(rename = "720p")]
P720,
#[serde(rename = "1080i")]
I1080,
#[serde(rename = "1080p")]
P1080,
#[serde(rename = "2160p")]
P2160,
#[serde(rename = "4320p")]
P4320,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramAudio {
pub component_type: u8,
pub component_tag: u8,
pub is_main: bool,
pub sampling_rate: u32,
pub langs: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramSeries {
pub id: u16,
pub repeat: u8,
pub pattern: u8,
pub expires_at: i64,
pub episode: u16,
pub last_episode: u16,
pub name: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProgramRelatedItemType {
Shared,
Relay,
Movement,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProgramRelatedItem {
#[serde(rename = "type")]
pub item_type: ProgramRelatedItemType,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_id: Option<NetworkId>,
pub service_id: ServiceId,
pub event_id: EventId,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(clippy::struct_excessive_bools)]
pub struct TunerDevice {
pub index: usize,
pub name: String,
pub types: Vec<ChannelType>,
pub command: Option<String>,
pub pid: Option<u32>,
pub users: Vec<TunerUser>,
pub is_available: bool,
pub is_remote: bool,
pub is_free: bool,
pub is_using: bool,
pub is_fault: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TunerUser {
pub id: String,
pub priority: i32,
#[serde(skip_serializing_if = "Option::is_none")]
pub agent: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_decoder: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_setting: Option<StreamSetting>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stream_info: Option<BTreeMap<String, StreamPidInfo>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamSetting {
pub channel: ConfigChannel,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_id: Option<NetworkId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_id: Option<ServiceId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_id: Option<EventId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub no_provide: Option<bool>,
#[serde(rename = "parseNIT", skip_serializing_if = "Option::is_none")]
pub parse_nit: Option<bool>,
#[serde(rename = "parseSDT", skip_serializing_if = "Option::is_none")]
pub parse_sdt: Option<bool>,
#[serde(rename = "parseEIT", skip_serializing_if = "Option::is_none")]
pub parse_eit: Option<bool>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct StreamPidInfo {
pub packet: u64,
pub drop: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct TunerProcess {
pub pid: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JobScheduleItem {
pub key: String,
pub schedule: String,
pub job: JobReference,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JobReference {
pub key: String,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct JobItem {
pub key: String,
pub name: String,
pub id: String,
pub status: JobStatus,
pub retry_count: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_rerunnable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_on_abort: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_on_fail: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_max: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retry_delay: Option<u64>,
pub is_aborting: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_aborted: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_skipped: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_failed: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub created_at: UnixTimeMs,
pub updated_at: UnixTimeMs,
#[serde(skip_serializing_if = "Option::is_none")]
pub started_at: Option<UnixTimeMs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub finished_at: Option<UnixTimeMs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<u64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum JobStatus {
Queued,
Standby,
Running,
Finished,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Event {
pub resource: EventResource,
#[serde(rename = "type")]
pub event_type: EventType,
pub data: Value,
pub time: UnixTimeMs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EventResource {
Program,
Service,
Tuner,
Job,
JobSchedule,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum EventType {
Create,
Update,
Remove,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigServer {
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hostname: Option<String>,
#[serde(rename = "disableIPv6", skip_serializing_if = "Option::is_none")]
pub disable_ipv6: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub log_level: Option<i8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_log_history: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_max_running: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub job_max_standby: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_buffer_bytes_before_ready: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub event_end_timeout: Option<u64>,
#[serde(
rename = "programGCJobSchedule",
skip_serializing_if = "Option::is_none"
)]
pub program_gc_job_schedule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epg_gathering_job_schedule: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub epg_retrieval_time: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub logo_data_interval: Option<u64>,
#[serde(rename = "disableEITParsing", skip_serializing_if = "Option::is_none")]
pub disable_eit_parsing: Option<bool>,
#[serde(rename = "disableWebUI", skip_serializing_if = "Option::is_none")]
pub disable_web_ui: Option<bool>,
#[serde(rename = "allowIPv4CidrRanges", default = "default_ipv4_ranges")]
pub allow_ipv4_cidr_ranges: Vec<String>,
#[serde(rename = "allowIPv6CidrRanges", default = "default_ipv6_ranges")]
pub allow_ipv6_cidr_ranges: Vec<String>,
#[serde(default = "default_origins")]
pub allow_origins: Vec<String>,
#[serde(rename = "allowPNA", default = "default_allow_pna")]
pub allow_pna: bool,
#[serde(default = "default_tsplay_endpoint")]
pub tsplay_endpoint: String,
}
impl Default for ConfigServer {
fn default() -> Self {
Self {
path: Some("/var/run/mirakurun.sock".into()),
port: Some(40_772),
hostname: None,
disable_ipv6: None,
log_level: Some(2),
max_log_history: None,
job_max_running: None,
job_max_standby: None,
max_buffer_bytes_before_ready: None,
event_end_timeout: None,
program_gc_job_schedule: None,
epg_gathering_job_schedule: None,
epg_retrieval_time: None,
logo_data_interval: None,
disable_eit_parsing: None,
disable_web_ui: None,
allow_ipv4_cidr_ranges: default_ipv4_ranges(),
allow_ipv6_cidr_ranges: default_ipv6_ranges(),
allow_origins: default_origins(),
allow_pna: true,
tsplay_endpoint: default_tsplay_endpoint(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigTuner {
pub name: String,
pub types: Vec<ChannelType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dvb_device_path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_mirakurun_host: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_mirakurun_port: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_mirakurun_decoder: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub decoder: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_disabled: Option<bool>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ConfigChannel {
pub name: String,
#[serde(rename = "type")]
pub channel_type: ChannelType,
pub channel: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_id: Option<ServiceId>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tsmf_rel_ts: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub command_vars: Option<BTreeMap<String, CommandVariable>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub is_disabled: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub satelite: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub satellite: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub space: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub freq: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub polarity: Option<Polarity>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum CommandVariable {
String(String),
Number(i64),
}
impl std::fmt::Display for CommandVariable {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::String(value) => formatter.write_str(value),
Self::Number(value) => value.fmt(formatter),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum Polarity {
H,
V,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ChannelScanStatus {
pub is_scanning: bool,
pub status: ChannelScanPhase,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub channel_type: Option<ChannelType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dry_run: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub progress: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub current_channel: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scan_log: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub new_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub takeover_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Vec<ConfigChannel>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub start_time: Option<UnixTimeMs>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_time: Option<UnixTimeMs>,
}
impl Default for ChannelScanStatus {
fn default() -> Self {
Self {
is_scanning: false,
status: ChannelScanPhase::NotStarted,
channel_type: None,
dry_run: None,
progress: None,
current_channel: None,
scan_log: None,
new_count: None,
takeover_count: None,
result: None,
start_time: None,
update_time: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChannelScanPhase {
NotStarted,
Scanning,
Completed,
Cancelled,
Error,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Version {
pub current: String,
pub latest: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Status {
pub time: UnixTimeMs,
pub version: String,
pub process: ProcessStatus,
pub epg: EpgStatus,
pub rpc_count: u64,
pub stream_count: StreamCount,
pub error_count: ErrorCount,
pub timer_accuracy: TimerAccuracy,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProcessStatus {
pub arch: String,
pub platform: String,
pub versions: BTreeMap<String, String>,
pub env: BTreeMap<String, String>,
pub pid: u32,
pub memory_usage: MemoryUsage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MemoryUsage {
pub rss: u64,
pub heap_total: u64,
pub heap_used: u64,
pub external: u64,
pub array_buffers: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EpgStatus {
pub gathering_networks: Vec<NetworkId>,
pub stored_events: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StreamCount {
pub tuner_device: u64,
pub ts_filter: u64,
pub decoder: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorCount {
pub uncaught_exception: u64,
pub unhandled_rejection: u64,
pub buffer_overflow: u64,
pub tuner_device_respawn: u64,
pub decoder_respawn: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct TimerAccuracy {
pub last: f64,
pub m1: TimerSample,
pub m5: TimerSample,
pub m15: TimerSample,
}
impl Default for TimerAccuracy {
fn default() -> Self {
Self {
last: 0.0,
m1: TimerSample::default(),
m5: TimerSample::default(),
m15: TimerSample::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
pub struct TimerSample {
pub avg: f64,
pub min: f64,
pub max: f64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApiError {
pub code: u16,
pub reason: Option<String>,
pub errors: Vec<OpenApiError>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenApiError {
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
}
const fn default_allow_pna() -> bool {
true
}
fn default_tsplay_endpoint() -> String {
"https://mirakurun-secure-contexts-api.pages.dev/tsplay/".into()
}
#[must_use]
pub fn default_ipv4_ranges() -> Vec<String> {
[
"10.0.0.0/8",
"127.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
]
.into_iter()
.map(String::from)
.collect()
}
#[must_use]
pub fn default_ipv6_ranges() -> Vec<String> {
vec!["fc00::/7".into()]
}
#[must_use]
pub fn default_origins() -> Vec<String> {
vec!["https://mirakurun-secure-contexts-api.pages.dev".into()]
}
#[must_use]
pub const fn service_item_id(network_id: NetworkId, service_id: ServiceId) -> ServiceItemId {
(network_id as u64) * 100_000 + service_id as u64
}
#[must_use]
pub const fn program_id(
network_id: NetworkId,
service_id: ServiceId,
event_id: EventId,
) -> ProgramId {
service_item_id(network_id, service_id) * 100_000 + event_id as u64
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::{
ChannelType, ConfigChannel, ConfigServer, StreamSetting, program_id, service_item_id,
};
#[test]
fn compound_ids_match_mirakurun_formula() {
assert_eq!(service_item_id(32_738, 1024), 3_273_801_024);
assert_eq!(program_id(32_738, 1024, 65_535), 327_380_102_465_535);
}
#[test]
fn channel_type_has_wire_value() {
assert_eq!(
serde_json::to_string(&ChannelType::Gr).expect("serialize enum"),
"\"GR\""
);
}
#[test]
fn config_server_applies_wire_defaults() {
let config: ConfigServer = serde_json::from_str("{}").expect("deserialize config");
assert!(config.allow_pna);
assert!(!config.allow_origins.is_empty());
}
#[test]
fn acronym_config_fields_match_node_wire_names() {
let config: ConfigServer = serde_json::from_value(json!({
"disableIPv6": true,
"programGCJobSchedule": "45 * * * *",
"disableEITParsing": true,
"disableWebUI": true,
"allowIPv4CidrRanges": ["127.0.0.0/8"],
"allowIPv6CidrRanges": ["::1/128"],
"allowPNA": false
}))
.expect("deserialize config");
assert_eq!(config.disable_ipv6, Some(true));
assert_eq!(
config.program_gc_job_schedule.as_deref(),
Some("45 * * * *")
);
assert_eq!(config.disable_eit_parsing, Some(true));
assert_eq!(config.disable_web_ui, Some(true));
assert!(!config.allow_pna);
let encoded = serde_json::to_value(config).expect("serialize config");
assert_eq!(encoded["disableIPv6"], true);
assert_eq!(encoded["programGCJobSchedule"], "45 * * * *");
assert_eq!(encoded["disableEITParsing"], true);
assert_eq!(encoded["disableWebUI"], true);
assert_eq!(encoded["allowPNA"], false);
assert!(encoded.get("disableIpv6").is_none());
}
#[test]
fn stream_parser_flags_keep_uppercase_acronyms() {
let setting = StreamSetting {
channel: ConfigChannel {
name: "test".into(),
channel_type: ChannelType::Gr,
channel: "27".into(),
service_id: None,
tsmf_rel_ts: None,
command_vars: None,
is_disabled: None,
satelite: None,
satellite: None,
space: None,
freq: None,
polarity: None,
},
network_id: None,
service_id: None,
event_id: None,
no_provide: None,
parse_nit: Some(true),
parse_sdt: Some(true),
parse_eit: Some(true),
};
let encoded = serde_json::to_value(setting).expect("serialize stream setting");
assert_eq!(encoded["parseNIT"], true);
assert_eq!(encoded["parseSDT"], true);
assert_eq!(encoded["parseEIT"], true);
}
}