You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
synctv/synctv-api-common/src/admin_settings_mapping.rs

746 lines
31 KiB
Rust

use std::collections::{BTreeMap, BTreeSet};
use synctv_core::{
models::PlayMode,
service::{
ConfiguredIceServer, OAuth2AppleProviderConfig, OAuth2CasdoorProviderConfig,
OAuth2GithubProviderConfig, OAuth2GoogleProviderConfig, OAuth2LogtoProviderConfig,
OAuth2OidcProviderConfig, OAuth2ProviderConfig, OAuth2ProviderConfigs,
OAuth2ProviderPrivateConfig, RoomPasswordPolicy,
},
};
use synctv_proto::{admin as admin_proto, client as client_proto};
use crate::impls::admin::settings::{
ChatSettingsPatch, CorsSettingsPatch, EmailSettingsPatch, OAuth2SettingsPatch,
OptionalConfigPatch, PermissionSettingsPatch, PlaybackHistorySettingsPatch,
RoomAutoPlaySettingsPatch, RoomCreationSettingsPatch, RoomDefaultsSettingsPatch,
RoomSettingsUpdatePatch, RtmpSettingsPatch, RuntimeSettingsPatch, ServerSettingsPatch,
SmtpCredentialsInput, SmtpProxyInput, UserSettingsPatch, WebRtcSettingsPatch,
};
use crate::ApiError;
pub fn runtime_settings_patch_from_admin_proto(
req: admin_proto::UpdateSettingsRequest,
) -> Result<RuntimeSettingsPatch, ApiError> {
runtime_settings_patch_from_admin_proto_with_oauth2(req, None)
}
pub fn runtime_settings_patch_from_admin_proto_with_current(
req: admin_proto::UpdateSettingsRequest,
current_oauth2: &OAuth2ProviderConfigs,
) -> Result<RuntimeSettingsPatch, ApiError> {
runtime_settings_patch_from_admin_proto_with_oauth2(req, Some(current_oauth2))
}
fn runtime_settings_patch_from_admin_proto_with_oauth2(
req: admin_proto::UpdateSettingsRequest,
current_oauth2: Option<&OAuth2ProviderConfigs>,
) -> Result<RuntimeSettingsPatch, ApiError> {
crate::impls::validate_proto_request(&req)?;
let settings = req
.settings
.ok_or_else(|| ApiError::InvalidInput("settings is required".to_string()))?;
let paths = req
.update_mask
.ok_or_else(|| ApiError::InvalidInput("update_mask is required".to_string()))?
.paths;
let patch = RuntimeSettingsPatch {
server: settings
.server
.map(|patch| ServerSettingsPatch { name: patch.name }),
room_defaults: settings
.room_defaults
.map(|patch| RoomDefaultsSettingsPatch {
default_max_members: patch.default_max_members,
default_max_chat_messages: patch.default_max_chat_messages,
}),
permissions: settings.permissions.map(|patch| PermissionSettingsPatch {
admin_default_permissions: patch.admin_default_permissions,
member_default_permissions: patch.member_default_permissions,
guest_default_permissions: patch.guest_default_permissions,
}),
room_creation: settings
.room_creation
.map(room_creation_settings_patch_from_admin_proto)
.transpose()?,
user: settings.user.map(|patch| UserSettingsPatch {
enable_password_signup: patch.enable_password_signup,
password_signup_need_review: patch.password_signup_need_review,
enable_email_signup: patch.enable_email_signup,
email_signup_need_review: patch.email_signup_need_review,
enable_webauthn_signup: patch.enable_webauthn_signup,
webauthn_signup_need_review: patch.webauthn_signup_need_review,
enable_guest: patch.enable_guest,
}),
oauth2: settings
.oauth2
.map(|patch| oauth2_settings_patch_from_admin_proto(patch, current_oauth2))
.transpose()?,
rtmp: settings.rtmp.map(|patch| RtmpSettingsPatch {
custom_publish_host: patch.custom_publish_host.map(OptionalConfigPatch::Set),
ts_disguised_as_png: patch.ts_disguised_as_png,
}),
email: settings.email.map(email_settings_patch_from_admin_proto),
webrtc: settings.webrtc.map(|patch| WebRtcSettingsPatch {
external_ice_servers: Some(
patch
.external_ice_servers
.into_iter()
.map(core_ice_server)
.collect(),
),
max_voice_participants_per_room: patch.max_voice_participants_per_room,
}),
chat: settings.chat.map(|patch| ChatSettingsPatch {
max_messages_per_room: patch.max_messages_per_room,
max_pinned_messages_per_room: patch.max_pinned_messages_per_room,
message_retention_days: patch.message_retention_days,
}),
playback_history: settings
.playback_history
.map(|patch| PlaybackHistorySettingsPatch {
retention_days: patch.retention_days,
max_entries_per_room: patch.max_entries_per_room,
}),
cors: settings.cors.map(|patch| CorsSettingsPatch {
allowed_origins: Some(patch.allowed_origins),
}),
};
select_runtime_settings_patch(patch, &paths)
}
fn required_runtime_snapshot_section<T>(value: Option<T>, path: &str) -> Result<T, ApiError> {
value.ok_or_else(|| ApiError::InvalidInput(format!("{path} is required")))
}
fn runtime_snapshot_credentials(
credentials: admin_proto::SmtpCredentials,
path: &str,
) -> Result<SmtpCredentialsInput, ApiError> {
let password = credentials.password.ok_or_else(|| {
ApiError::InvalidInput(format!(
"{path}.password is required in a runtime settings snapshot"
))
})?;
Ok(SmtpCredentialsInput {
username: credentials.username,
password: Some(password),
})
}
pub fn runtime_settings_replacement_patch_from_admin_proto(
settings: admin_proto::RuntimeSettings,
) -> Result<RuntimeSettingsPatch, ApiError> {
crate::impls::validate_proto_request(&settings)?;
let server = required_runtime_snapshot_section(settings.server, "settings.server")?;
let room_defaults =
required_runtime_snapshot_section(settings.room_defaults, "settings.room_defaults")?;
let permissions =
required_runtime_snapshot_section(settings.permissions, "settings.permissions")?;
let room_creation =
required_runtime_snapshot_section(settings.room_creation, "settings.room_creation")?;
let user = required_runtime_snapshot_section(settings.user, "settings.user")?;
let oauth2 = required_runtime_snapshot_section(settings.oauth2, "settings.oauth2")?;
let rtmp = required_runtime_snapshot_section(settings.rtmp, "settings.rtmp")?;
let email = required_runtime_snapshot_section(settings.email, "settings.email")?;
let webrtc = required_runtime_snapshot_section(settings.webrtc, "settings.webrtc")?;
let chat = required_runtime_snapshot_section(settings.chat, "settings.chat")?;
let playback_history =
required_runtime_snapshot_section(settings.playback_history, "settings.playback_history")?;
let cors = required_runtime_snapshot_section(settings.cors, "settings.cors")?;
let smtp_credentials = email
.smtp_credentials
.map(|credentials| {
runtime_snapshot_credentials(credentials, "settings.email.smtp_credentials")
.map(OptionalConfigPatch::Set)
})
.transpose()?
.unwrap_or(OptionalConfigPatch::Clear);
let smtp_proxy = email
.smtp_proxy
.map(|proxy| {
let credentials = proxy
.credentials
.map(|credentials| {
runtime_snapshot_credentials(
credentials,
"settings.email.smtp_proxy.credentials",
)
})
.transpose()?;
Ok::<_, ApiError>(OptionalConfigPatch::Set(SmtpProxyInput {
url: proxy.url,
credentials,
}))
})
.transpose()?
.unwrap_or(OptionalConfigPatch::Clear);
Ok(RuntimeSettingsPatch {
server: Some(ServerSettingsPatch {
name: Some(server.name),
}),
room_defaults: Some(RoomDefaultsSettingsPatch {
default_max_members: Some(room_defaults.default_max_members),
default_max_chat_messages: Some(room_defaults.default_max_chat_messages),
}),
permissions: Some(PermissionSettingsPatch {
admin_default_permissions: Some(permissions.admin_default_permissions),
member_default_permissions: Some(permissions.member_default_permissions),
guest_default_permissions: Some(permissions.guest_default_permissions),
}),
room_creation: Some(RoomCreationSettingsPatch {
enabled: Some(room_creation.enabled),
approval_required: Some(room_creation.approval_required),
password_policy: Some(core_room_password_policy(room_creation.password_policy)?),
max_rooms_per_user: Some(room_creation.max_rooms_per_user),
}),
user: Some(UserSettingsPatch {
enable_password_signup: Some(user.enable_password_signup),
password_signup_need_review: Some(user.password_signup_need_review),
enable_email_signup: Some(user.enable_email_signup),
email_signup_need_review: Some(user.email_signup_need_review),
enable_webauthn_signup: Some(user.enable_webauthn_signup),
webauthn_signup_need_review: Some(user.webauthn_signup_need_review),
enable_guest: Some(user.enable_guest),
}),
oauth2: Some(OAuth2SettingsPatch {
providers: Some(oauth2_provider_configs_from_admin_proto(
oauth2.providers,
None,
)?),
allowed_redirect_urls: Some(oauth2.allowed_redirect_urls),
}),
rtmp: Some(RtmpSettingsPatch {
custom_publish_host: Some(match rtmp.custom_publish_host {
Some(host) => OptionalConfigPatch::Set(host),
None => OptionalConfigPatch::Clear,
}),
ts_disguised_as_png: Some(rtmp.ts_disguised_as_png),
}),
email: Some(EmailSettingsPatch {
enabled: Some(email.enabled),
smtp_host: Some(match email.smtp_host {
Some(host) => OptionalConfigPatch::Set(host),
None => OptionalConfigPatch::Clear,
}),
smtp_port: Some(email.smtp_port),
smtp_credentials: Some(smtp_credentials),
smtp_proxy: Some(smtp_proxy),
use_tls: Some(email.use_tls),
from_email: Some(match email.from_email {
Some(from_email) => OptionalConfigPatch::Set(from_email),
None => OptionalConfigPatch::Clear,
}),
from_name: Some(email.from_name),
whitelist_enabled: Some(email.whitelist_enabled),
whitelist_domains: Some(email.whitelist_domains),
}),
webrtc: Some(WebRtcSettingsPatch {
external_ice_servers: Some(
webrtc
.external_ice_servers
.into_iter()
.map(core_ice_server)
.collect(),
),
max_voice_participants_per_room: Some(webrtc.max_voice_participants_per_room),
}),
chat: Some(ChatSettingsPatch {
max_messages_per_room: Some(chat.max_messages_per_room),
max_pinned_messages_per_room: Some(chat.max_pinned_messages_per_room),
message_retention_days: Some(chat.message_retention_days),
}),
playback_history: Some(PlaybackHistorySettingsPatch {
retention_days: Some(playback_history.retention_days),
max_entries_per_room: Some(playback_history.max_entries_per_room),
}),
cors: Some(CorsSettingsPatch {
allowed_origins: Some(cors.allowed_origins),
}),
})
}
fn required_mask_value<T>(value: Option<T>, path: &str) -> Result<T, ApiError> {
value.ok_or_else(|| ApiError::InvalidInput(format!("{path} is required by update_mask")))
}
fn select_runtime_settings_patch(
mut source: RuntimeSettingsPatch,
paths: &[String],
) -> Result<RuntimeSettingsPatch, ApiError> {
if paths.is_empty() {
return Err(ApiError::InvalidInput(
"update_mask.paths must not be empty".to_string(),
));
}
let mut seen = BTreeSet::new();
let mut selected = RuntimeSettingsPatch::default();
macro_rules! select_required {
($section:ident, $field:ident, $path:expr) => {{
let value = source
.$section
.as_mut()
.and_then(|section| section.$field.take());
selected
.$section
.get_or_insert_with(Default::default)
.$field = Some(required_mask_value(value, $path)?);
}};
}
macro_rules! select_optional {
($section:ident, $field:ident) => {{
let value = source
.$section
.as_mut()
.and_then(|section| section.$field.take())
.unwrap_or(OptionalConfigPatch::Clear);
selected
.$section
.get_or_insert_with(Default::default)
.$field = Some(value);
}};
}
for path in paths {
if !seen.insert(path.as_str()) {
return Err(ApiError::InvalidInput(format!(
"duplicate update_mask path '{path}'"
)));
}
match path.as_str() {
"server.name" => select_required!(server, name, path),
"room_defaults.default_max_members" => {
select_required!(room_defaults, default_max_members, path);
}
"room_defaults.default_max_chat_messages" => {
select_required!(room_defaults, default_max_chat_messages, path);
}
"permissions.admin_default_permissions" => {
select_required!(permissions, admin_default_permissions, path);
}
"permissions.member_default_permissions" => {
select_required!(permissions, member_default_permissions, path);
}
"permissions.guest_default_permissions" => {
select_required!(permissions, guest_default_permissions, path);
}
"room_creation.enabled" => select_required!(room_creation, enabled, path),
"room_creation.approval_required" => {
select_required!(room_creation, approval_required, path);
}
"room_creation.password_policy" => {
select_required!(room_creation, password_policy, path);
}
"room_creation.max_rooms_per_user" => {
select_required!(room_creation, max_rooms_per_user, path);
}
"user.enable_password_signup" => {
select_required!(user, enable_password_signup, path);
}
"user.password_signup_need_review" => {
select_required!(user, password_signup_need_review, path);
}
"user.enable_email_signup" => select_required!(user, enable_email_signup, path),
"user.email_signup_need_review" => {
select_required!(user, email_signup_need_review, path);
}
"user.enable_webauthn_signup" => {
select_required!(user, enable_webauthn_signup, path);
}
"user.webauthn_signup_need_review" => {
select_required!(user, webauthn_signup_need_review, path);
}
"user.enable_guest" => select_required!(user, enable_guest, path),
"oauth2.providers" => select_required!(oauth2, providers, path),
"oauth2.allowedRedirectUrls" | "oauth2.allowed_redirect_urls" => {
select_required!(oauth2, allowed_redirect_urls, path);
}
"rtmp.custom_publish_host" => select_optional!(rtmp, custom_publish_host),
"rtmp.ts_disguised_as_png" => {
select_required!(rtmp, ts_disguised_as_png, path);
}
"email.enabled" => select_required!(email, enabled, path),
"email.smtp_host" => select_optional!(email, smtp_host),
"email.smtp_port" => select_required!(email, smtp_port, path),
"email.smtp_credentials" => select_optional!(email, smtp_credentials),
"email.smtp_proxy" => select_optional!(email, smtp_proxy),
"email.use_tls" => select_required!(email, use_tls, path),
"email.from_email" => select_optional!(email, from_email),
"email.from_name" => select_required!(email, from_name, path),
"email.whitelist_enabled" => {
select_required!(email, whitelist_enabled, path);
}
"email.whitelist_domains" => {
select_required!(email, whitelist_domains, path);
}
"webrtc.external_ice_servers" => {
select_required!(webrtc, external_ice_servers, path);
}
"webrtc.max_voice_participants_per_room" => {
select_required!(webrtc, max_voice_participants_per_room, path);
}
"chat.max_messages_per_room" => {
select_required!(chat, max_messages_per_room, path);
}
"chat.max_pinned_messages_per_room" => {
select_required!(chat, max_pinned_messages_per_room, path);
}
"chat.message_retention_days" => {
select_required!(chat, message_retention_days, path);
}
"playback_history.retention_days" => {
select_required!(playback_history, retention_days, path);
}
"playback_history.max_entries_per_room" => {
select_required!(playback_history, max_entries_per_room, path);
}
"cors.allowed_origins" => select_required!(cors, allowed_origins, path),
_ => {
return Err(ApiError::InvalidInput(format!(
"unsupported update_mask path '{path}'"
)))
}
}
}
Ok(selected)
}
pub fn room_settings_patch_from_admin_proto(
req: &admin_proto::UpdateRoomSettingsRequest,
) -> Result<RoomSettingsUpdatePatch, ApiError> {
crate::impls::validate_proto_request(req)?;
let settings = req
.settings
.ok_or_else(|| ApiError::InvalidInput("settings is required".to_string()))?;
let paths = &req
.update_mask
.as_ref()
.ok_or_else(|| ApiError::InvalidInput("update_mask is required".to_string()))?
.paths;
let patch = crate::room_settings_mapping::select_room_settings_patch(settings, paths)?;
room_settings_patch_from_client_proto(patch)
}
fn email_settings_patch_from_admin_proto(
patch: admin_proto::EmailSettingsPatch,
) -> EmailSettingsPatch {
EmailSettingsPatch {
enabled: patch.enabled,
smtp_host: patch.smtp_host.map(OptionalConfigPatch::Set),
smtp_port: patch.smtp_port,
smtp_credentials: patch.smtp_credentials.map(|credentials| {
OptionalConfigPatch::Set(SmtpCredentialsInput {
username: credentials.username,
password: credentials.password,
})
}),
smtp_proxy: patch.smtp_proxy.map(|proxy| {
OptionalConfigPatch::Set(SmtpProxyInput {
url: proxy.url,
credentials: proxy.credentials.map(|credentials| SmtpCredentialsInput {
username: credentials.username,
password: credentials.password,
}),
})
}),
use_tls: patch.use_tls,
from_email: patch.from_email.map(OptionalConfigPatch::Set),
from_name: patch.from_name,
whitelist_enabled: patch.whitelist_enabled,
whitelist_domains: Some(patch.whitelist_domains),
}
}
fn room_settings_patch_from_client_proto(
patch: client_proto::RoomSettingsPatch,
) -> Result<RoomSettingsUpdatePatch, ApiError> {
Ok(RoomSettingsUpdatePatch {
allow_guest_join: patch.allow_guest_join,
max_members: patch.max_members,
require_approval: patch.require_approval,
allow_auto_join: patch.allow_auto_join,
chat_enabled: patch.chat_enabled,
voice_chat_enabled: patch.voice_chat_enabled,
p2p_media_enabled: patch.p2p_media_enabled,
auto_play: patch
.auto_play
.map(auto_play_patch_from_client_proto)
.transpose()?,
admin_added_permissions: patch.admin_added_permissions,
admin_removed_permissions: patch.admin_removed_permissions,
member_added_permissions: patch.member_added_permissions,
member_removed_permissions: patch.member_removed_permissions,
guest_added_permissions: patch.guest_added_permissions,
guest_removed_permissions: patch.guest_removed_permissions,
})
}
fn room_creation_settings_patch_from_admin_proto(
patch: admin_proto::RoomCreationSettingsPatch,
) -> Result<RoomCreationSettingsPatch, ApiError> {
Ok(RoomCreationSettingsPatch {
enabled: patch.enabled,
approval_required: patch.approval_required,
password_policy: patch
.password_policy
.map(core_room_password_policy)
.transpose()?,
max_rooms_per_user: patch.max_rooms_per_user,
})
}
fn oauth2_settings_patch_from_admin_proto(
patch: admin_proto::OAuth2SettingsPatch,
current: Option<&OAuth2ProviderConfigs>,
) -> Result<OAuth2SettingsPatch, ApiError> {
Ok(OAuth2SettingsPatch {
providers: Some(oauth2_provider_configs_from_admin_proto(
patch.providers,
current,
)?),
allowed_redirect_urls: Some(patch.allowed_redirect_urls),
})
}
fn oauth2_provider_configs_from_admin_proto(
providers: Vec<admin_proto::OAuth2ProviderSettings>,
current: Option<&OAuth2ProviderConfigs>,
) -> Result<OAuth2ProviderConfigs, ApiError> {
let mut configs = BTreeMap::new();
for provider in providers {
let name = provider.name.clone();
let config = oauth2_config_from_admin_proto(
&name,
&provider,
current.and_then(|configs| configs.0.get(&name)),
)?;
if configs
.insert(
name.clone(),
OAuth2ProviderConfig {
enable_signup: provider.enable_signup,
signup_need_review: provider.signup_need_review,
config,
},
)
.is_some()
{
return Err(ApiError::InvalidInput(format!(
"Duplicate OAuth2 provider name '{name}'"
)));
}
}
Ok(OAuth2ProviderConfigs(configs))
}
fn oauth2_config_from_admin_proto(
name: &str,
provider: &admin_proto::OAuth2ProviderSettings,
current: Option<&OAuth2ProviderConfig>,
) -> Result<OAuth2ProviderPrivateConfig, ApiError> {
use admin_proto::o_auth2_provider_settings::Config;
Ok(
match provider.config.clone().ok_or_else(|| {
ApiError::InvalidInput(format!("OAuth2 provider '{name}' config is required"))
})? {
Config::Github(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::GitHub(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::GitHub(OAuth2GithubProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"github",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
})
}
Config::Google(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::Google(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::Google(OAuth2GoogleProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"google",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
})
}
Config::Logto(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::Logto(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::Logto(OAuth2LogtoProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"logto",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
endpoint: config.endpoint,
})
}
Config::Oidc(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::Oidc(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::Oidc(OAuth2OidcProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"oidc",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
issuer: config.issuer,
auth_url: config.auth_url,
token_url: config.token_url,
userinfo_url: config.userinfo_url,
jwks_url: config.jwks_url,
scopes: config.scopes,
})
}
Config::Casdoor(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::Casdoor(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::Casdoor(OAuth2CasdoorProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"casdoor",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
issuer: config.issuer,
auth_url: config.auth_url,
token_url: config.token_url,
userinfo_url: config.userinfo_url,
jwks_url: config.jwks_url,
})
}
Config::Apple(config) => {
let current_secret = current.and_then(|provider| match &provider.config {
OAuth2ProviderPrivateConfig::Apple(current) => {
Some((current.client_id.as_str(), current.client_secret.as_str()))
}
_ => None,
});
OAuth2ProviderPrivateConfig::Apple(OAuth2AppleProviderConfig {
client_secret: resolve_oauth2_client_secret(
name,
"apple",
&config.client_id,
config.client_secret,
current_secret,
)?,
client_id: config.client_id,
redirect_url: config.redirect_url,
})
}
},
)
}
fn resolve_oauth2_client_secret(
name: &str,
provider_type: &str,
client_id: &str,
supplied: Option<String>,
current: Option<(&str, &str)>,
) -> Result<String, ApiError> {
if let Some(secret) = supplied {
return Ok(secret);
}
if let Some((current_client_id, current_secret)) = current {
if current_client_id == client_id {
return Ok(current_secret.to_string());
}
}
Err(ApiError::InvalidInput(format!(
"OAuth2 provider '{name}' {provider_type}.client_secret is required for a new provider, provider type change, or client_id change"
)))
}
fn core_room_password_policy(value: i32) -> Result<RoomPasswordPolicy, ApiError> {
match admin_proto::RoomPasswordPolicy::try_from(value) {
Ok(admin_proto::RoomPasswordPolicy::Optional) => Ok(RoomPasswordPolicy::Optional),
Ok(admin_proto::RoomPasswordPolicy::Required) => Ok(RoomPasswordPolicy::Required),
Ok(admin_proto::RoomPasswordPolicy::Forbidden) => Ok(RoomPasswordPolicy::Forbidden),
Ok(admin_proto::RoomPasswordPolicy::Unspecified) | Err(_) => Err(ApiError::InvalidInput(
"room_creation password policy is required".to_string(),
)),
}
}
fn auto_play_patch_from_client_proto(
patch: client_proto::AutoPlaySettingsPatch,
) -> Result<RoomAutoPlaySettingsPatch, ApiError> {
Ok(RoomAutoPlaySettingsPatch {
enabled: patch.enabled,
mode: patch.mode.map(core_play_mode).transpose()?,
delay: patch.delay,
})
}
fn core_play_mode(value: i32) -> Result<PlayMode, ApiError> {
match client_proto::PlayMode::try_from(value)
.map_err(|_| ApiError::InvalidInput("Unsupported auto_play.mode".to_string()))?
{
client_proto::PlayMode::Unspecified | client_proto::PlayMode::Sequential => {
Ok(PlayMode::Sequential)
}
client_proto::PlayMode::RepeatOne => Ok(PlayMode::RepeatOne),
client_proto::PlayMode::RepeatAll => Ok(PlayMode::RepeatAll),
client_proto::PlayMode::Shuffle => Ok(PlayMode::Shuffle),
}
}
fn core_ice_server(value: client_proto::IceServer) -> ConfiguredIceServer {
ConfiguredIceServer {
urls: value.urls,
username: value.username,
credential: value.credential,
}
}