Add elyspace core import export model
This commit is contained in:
Generated
+2
@@ -2263,6 +2263,8 @@ name = "ely_browser_core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ely_domain",
|
"ely_domain",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ rust-version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ely_domain = { path = "../ely_domain" }
|
ely_domain = { path = "../ely_domain" }
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,9 @@ pub enum CoreError {
|
|||||||
#[error("space not found: {id}")]
|
#[error("space not found: {id}")]
|
||||||
SpaceNotFound { id: SpaceId },
|
SpaceNotFound { id: SpaceId },
|
||||||
|
|
||||||
|
#[error("invalid .elyspace package: {reason}")]
|
||||||
|
InvalidSpacePackage { reason: String },
|
||||||
|
|
||||||
#[error("trashed space not found: {id}")]
|
#[error("trashed space not found: {id}")]
|
||||||
TrashedSpaceNotFound { id: SpaceId },
|
TrashedSpaceNotFound { id: SpaceId },
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ mod state;
|
|||||||
|
|
||||||
pub use error::CoreError;
|
pub use error::CoreError;
|
||||||
pub use state::{
|
pub use state::{
|
||||||
BrowserCore, BrowserSnapshot, InitialBrowserConfig, InstalledPlugin, PluginAuditAction,
|
BrowserCore, BrowserSnapshot, ELYSPACE_FILE_EXTENSION, ELYSPACE_SCHEMA_VERSION,
|
||||||
PluginAuditEvent, SiteDataClearance, TrashedSpace,
|
ElySpacePackage, InitialBrowserConfig, InstalledPlugin, PluginAuditAction, PluginAuditEvent,
|
||||||
|
SiteDataClearance, SpaceImportProfileMapping, TrashedSpace,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ mod profiles;
|
|||||||
mod reading_list;
|
mod reading_list;
|
||||||
mod site_data;
|
mod site_data;
|
||||||
mod site_permissions;
|
mod site_permissions;
|
||||||
|
mod space_exports;
|
||||||
mod spaces;
|
mod spaces;
|
||||||
mod splits;
|
mod splits;
|
||||||
mod sync;
|
mod sync;
|
||||||
@@ -33,6 +34,9 @@ mod tabs;
|
|||||||
|
|
||||||
pub use plugins::{InstalledPlugin, PluginAuditAction, PluginAuditEvent};
|
pub use plugins::{InstalledPlugin, PluginAuditAction, PluginAuditEvent};
|
||||||
pub use site_data::SiteDataClearance;
|
pub use site_data::SiteDataClearance;
|
||||||
|
pub use space_exports::{
|
||||||
|
ELYSPACE_FILE_EXTENSION, ELYSPACE_SCHEMA_VERSION, ElySpacePackage, SpaceImportProfileMapping,
|
||||||
|
};
|
||||||
pub use spaces::TrashedSpace;
|
pub use spaces::TrashedSpace;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|||||||
@@ -0,0 +1,295 @@
|
|||||||
|
use ely_domain::{
|
||||||
|
ArchivePolicy, BrowserTab, ProfileId, ProfileKind, Space, SpaceId, TabId, UrlText,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::BrowserCore;
|
||||||
|
use crate::CoreError;
|
||||||
|
|
||||||
|
pub const ELYSPACE_SCHEMA_VERSION: u16 = 1;
|
||||||
|
pub const ELYSPACE_FILE_EXTENSION: &str = "elyspace";
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum SpaceImportProfileMapping {
|
||||||
|
PreserveExisting,
|
||||||
|
UseActiveProfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct ElySpacePackage {
|
||||||
|
version: u16,
|
||||||
|
space: ElySpaceRecord,
|
||||||
|
tabs: Vec<ElySpaceTabRecord>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElySpacePackage {
|
||||||
|
#[must_use]
|
||||||
|
pub fn version(&self) -> u16 {
|
||||||
|
self.version
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn space_name(&self) -> &str {
|
||||||
|
&self.space.name
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn tab_count(&self) -> usize {
|
||||||
|
self.tabs.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct ElySpaceRecord {
|
||||||
|
name: String,
|
||||||
|
icon: String,
|
||||||
|
accent_hex: u32,
|
||||||
|
default_profile_id: String,
|
||||||
|
archive_policy: ElySpaceArchivePolicy,
|
||||||
|
sidebar_width_px: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
struct ElySpaceTabRecord {
|
||||||
|
title: String,
|
||||||
|
url: String,
|
||||||
|
profile_id: String,
|
||||||
|
favicon_key: Option<String>,
|
||||||
|
pinned: bool,
|
||||||
|
favorite: bool,
|
||||||
|
sort_key: u64,
|
||||||
|
sync_enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
enum ElySpaceArchivePolicy {
|
||||||
|
Manual,
|
||||||
|
IdleDays { days: u16 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserCore {
|
||||||
|
pub fn export_space_package_json(&self, space_id: &SpaceId) -> Result<String, CoreError> {
|
||||||
|
serde_json::to_string_pretty(&self.export_space_package(space_id)?)
|
||||||
|
.map_err(invalid_space_package)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_space_package(&self, space_id: &SpaceId) -> Result<ElySpacePackage, CoreError> {
|
||||||
|
let space = self
|
||||||
|
.spaces
|
||||||
|
.iter()
|
||||||
|
.find(|space| space.id() == space_id)
|
||||||
|
.ok_or_else(|| CoreError::SpaceNotFound { id: space_id.clone() })?;
|
||||||
|
let tabs = self
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.filter(|tab| tab.space_id() == space_id)
|
||||||
|
.map(ElySpaceTabRecord::from_tab)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(ElySpacePackage {
|
||||||
|
version: ELYSPACE_SCHEMA_VERSION,
|
||||||
|
space: ElySpaceRecord::from_space(space),
|
||||||
|
tabs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_space_package_json(
|
||||||
|
&mut self,
|
||||||
|
package_json: &str,
|
||||||
|
profile_mapping: SpaceImportProfileMapping,
|
||||||
|
) -> Result<SpaceId, CoreError> {
|
||||||
|
let package = serde_json::from_str(package_json).map_err(invalid_space_package)?;
|
||||||
|
self.import_space_package(package, profile_mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_space_package(
|
||||||
|
&mut self,
|
||||||
|
package: ElySpacePackage,
|
||||||
|
profile_mapping: SpaceImportProfileMapping,
|
||||||
|
) -> Result<SpaceId, CoreError> {
|
||||||
|
validate_package_version(package.version)?;
|
||||||
|
self.validate_imported_favorites(&package)?;
|
||||||
|
let default_profile_id = self.imported_default_profile_id(&package, profile_mapping)?;
|
||||||
|
let sort_key = self.next_space_sort_key();
|
||||||
|
let mut space = Space::new(
|
||||||
|
package.space.name.clone(),
|
||||||
|
package.space.icon.clone(),
|
||||||
|
package.space.accent_hex,
|
||||||
|
default_profile_id.clone(),
|
||||||
|
sort_key,
|
||||||
|
);
|
||||||
|
space.set_archive_policy(package.space.archive_policy.clone().into());
|
||||||
|
space.set_sidebar_width_px(package.space.sidebar_width_px);
|
||||||
|
|
||||||
|
let space_id = space.id().clone();
|
||||||
|
let tabs = self.imported_tabs(&space_id, &default_profile_id, &package, profile_mapping)?;
|
||||||
|
let active_tab_id = tabs[0].id().clone();
|
||||||
|
|
||||||
|
self.spaces.push(space);
|
||||||
|
self.tabs.extend(tabs);
|
||||||
|
self.active_tabs_by_space.insert(space_id.clone(), active_tab_id.clone());
|
||||||
|
self.select_tab(&active_tab_id)?;
|
||||||
|
Ok(space_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imported_default_profile_id(
|
||||||
|
&self,
|
||||||
|
package: &ElySpacePackage,
|
||||||
|
profile_mapping: SpaceImportProfileMapping,
|
||||||
|
) -> Result<ProfileId, CoreError> {
|
||||||
|
match profile_mapping {
|
||||||
|
SpaceImportProfileMapping::PreserveExisting => {
|
||||||
|
let profile_id = ProfileId::parse(&package.space.default_profile_id)?;
|
||||||
|
self.standard_profile_id(&profile_id)?;
|
||||||
|
Ok(profile_id)
|
||||||
|
}
|
||||||
|
SpaceImportProfileMapping::UseActiveProfile => self.stable_active_profile_id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_imported_favorites(&self, package: &ElySpacePackage) -> Result<(), CoreError> {
|
||||||
|
let imported_favorites = package.tabs.iter().filter(|tab| tab.favorite).count();
|
||||||
|
let favorite_limit = self.favorite_limit.value();
|
||||||
|
if self.favorites().len() + imported_favorites > favorite_limit {
|
||||||
|
return Err(CoreError::FavoriteLimitReached { limit: favorite_limit });
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imported_tabs(
|
||||||
|
&self,
|
||||||
|
space_id: &SpaceId,
|
||||||
|
default_profile_id: &ProfileId,
|
||||||
|
package: &ElySpacePackage,
|
||||||
|
profile_mapping: SpaceImportProfileMapping,
|
||||||
|
) -> Result<Vec<BrowserTab>, CoreError> {
|
||||||
|
let mut tabs = package
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.map(|tab| self.imported_tab(space_id, default_profile_id, tab, profile_mapping))
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
if tabs.is_empty() {
|
||||||
|
tabs.push(self.build_tab_for(
|
||||||
|
space_id.clone(),
|
||||||
|
default_profile_id.clone(),
|
||||||
|
self.new_tab_url()?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(tabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn imported_tab(
|
||||||
|
&self,
|
||||||
|
space_id: &SpaceId,
|
||||||
|
default_profile_id: &ProfileId,
|
||||||
|
record: &ElySpaceTabRecord,
|
||||||
|
profile_mapping: SpaceImportProfileMapping,
|
||||||
|
) -> Result<BrowserTab, CoreError> {
|
||||||
|
let profile_id = match profile_mapping {
|
||||||
|
SpaceImportProfileMapping::PreserveExisting => {
|
||||||
|
let profile_id = ProfileId::parse(&record.profile_id)?;
|
||||||
|
self.standard_profile_id(&profile_id)?;
|
||||||
|
profile_id
|
||||||
|
}
|
||||||
|
SpaceImportProfileMapping::UseActiveProfile => default_profile_id.clone(),
|
||||||
|
};
|
||||||
|
let mut tab = BrowserTab::new(
|
||||||
|
TabId::new(),
|
||||||
|
space_id.clone(),
|
||||||
|
profile_id,
|
||||||
|
record.title.clone(),
|
||||||
|
UrlText::parse(&record.url)?,
|
||||||
|
)
|
||||||
|
.with_sort_key(record.sort_key);
|
||||||
|
tab.set_pinned(record.pinned);
|
||||||
|
tab.set_favorite(record.favorite);
|
||||||
|
tab.set_sync_enabled(record.sync_enabled);
|
||||||
|
if let Some(favicon_key) = &record.favicon_key {
|
||||||
|
tab.set_favicon_key(favicon_key.clone())?;
|
||||||
|
}
|
||||||
|
Ok(tab)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn standard_profile_id(&self, profile_id: &ProfileId) -> Result<(), CoreError> {
|
||||||
|
let profile = self
|
||||||
|
.profiles
|
||||||
|
.iter()
|
||||||
|
.find(|profile| profile.id() == profile_id)
|
||||||
|
.ok_or_else(|| CoreError::ProfileNotFound { id: profile_id.clone() })?;
|
||||||
|
if profile.kind() == &ProfileKind::Private {
|
||||||
|
return Err(CoreError::PrivateProfileDefaultLocked { id: profile_id.clone() });
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stable_active_profile_id(&self) -> Result<ProfileId, CoreError> {
|
||||||
|
if self.active_profile()?.kind() == &ProfileKind::Private {
|
||||||
|
Ok(self.active_space()?.default_profile_id().clone())
|
||||||
|
} else {
|
||||||
|
Ok(self.active_profile_id.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElySpaceRecord {
|
||||||
|
fn from_space(space: &Space) -> Self {
|
||||||
|
Self {
|
||||||
|
name: space.name().to_string(),
|
||||||
|
icon: space.icon().to_string(),
|
||||||
|
accent_hex: space.accent_hex(),
|
||||||
|
default_profile_id: space.default_profile_id().as_str().to_string(),
|
||||||
|
archive_policy: ElySpaceArchivePolicy::from(space.archive_policy()),
|
||||||
|
sidebar_width_px: space.sidebar_width_px(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElySpaceTabRecord {
|
||||||
|
fn from_tab(tab: &BrowserTab) -> Self {
|
||||||
|
Self {
|
||||||
|
title: tab.title().to_string(),
|
||||||
|
url: tab.url().as_str().to_string(),
|
||||||
|
profile_id: tab.profile_id().as_str().to_string(),
|
||||||
|
favicon_key: tab.favicon_key().map(str::to_string),
|
||||||
|
pinned: tab.flags().pinned,
|
||||||
|
favorite: tab.flags().favorite,
|
||||||
|
sort_key: tab.sort_key(),
|
||||||
|
sync_enabled: tab.sync_enabled(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ArchivePolicy> for ElySpaceArchivePolicy {
|
||||||
|
fn from(policy: &ArchivePolicy) -> Self {
|
||||||
|
match policy {
|
||||||
|
ArchivePolicy::Manual => Self::Manual,
|
||||||
|
ArchivePolicy::IdleDays(days) => Self::IdleDays { days: *days },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ElySpaceArchivePolicy> for ArchivePolicy {
|
||||||
|
fn from(policy: ElySpaceArchivePolicy) -> Self {
|
||||||
|
match policy {
|
||||||
|
ElySpaceArchivePolicy::Manual => Self::Manual,
|
||||||
|
ElySpaceArchivePolicy::IdleDays { days } => Self::IdleDays(days),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_package_version(version: u16) -> Result<(), CoreError> {
|
||||||
|
if version == ELYSPACE_SCHEMA_VERSION {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(CoreError::InvalidSpacePackage { reason: format!("unsupported version {version}") })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn invalid_space_package(error: serde_json::Error) -> CoreError {
|
||||||
|
CoreError::InvalidSpacePackage { reason: error.to_string() }
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use ely_browser_core::{
|
||||||
|
BrowserCore, CoreError, ELYSPACE_FILE_EXTENSION, ELYSPACE_SCHEMA_VERSION, InitialBrowserConfig,
|
||||||
|
SpaceImportProfileMapping,
|
||||||
|
};
|
||||||
|
use ely_domain::{ArchivePolicy, ProfileKind, UrlText};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn exports_space_package_json_with_settings_and_tabs() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let space_id = core.snapshot()?.active_space_id;
|
||||||
|
let active_tab_id = core.snapshot()?.active_tab_id;
|
||||||
|
|
||||||
|
core.set_space_archive_policy(&space_id, ArchivePolicy::IdleDays(14))?;
|
||||||
|
core.set_space_sidebar_width(&space_id, 320)?;
|
||||||
|
core.set_tab_favicon_key(&active_tab_id, "favicons/work.ico")?;
|
||||||
|
core.toggle_active_tab_pinned()?;
|
||||||
|
core.toggle_active_tab_favorite()?;
|
||||||
|
core.open_tab(UrlText::parse("https://servo.org/")?);
|
||||||
|
|
||||||
|
let package = core.export_space_package(&space_id)?;
|
||||||
|
let package_json = core.export_space_package_json(&space_id)?;
|
||||||
|
let value: Value = serde_json::from_str(&package_json)?;
|
||||||
|
|
||||||
|
assert_eq!(ELYSPACE_FILE_EXTENSION, "elyspace");
|
||||||
|
assert_eq!(package.version(), ELYSPACE_SCHEMA_VERSION);
|
||||||
|
assert_eq!(package.space_name(), "Work");
|
||||||
|
assert_eq!(package.tab_count(), 2);
|
||||||
|
assert_eq!(json_u64(&value, "version")?, 1);
|
||||||
|
assert_eq!(json_str(json_object(&value, "space")?, "name")?, "Work");
|
||||||
|
assert_eq!(json_object_u64(json_object(&value, "space")?, "sidebar_width_px")?, 320);
|
||||||
|
assert_eq!(json_array(&value, "tabs")?.len(), 2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imports_space_package_with_active_profile_mapping() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let source_space_id = source.snapshot()?.active_space_id;
|
||||||
|
source.set_space_archive_policy(&source_space_id, ArchivePolicy::IdleDays(7))?;
|
||||||
|
source.set_space_sidebar_width(&source_space_id, 360)?;
|
||||||
|
source.open_tab(UrlText::parse("https://example.com")?);
|
||||||
|
let package_json = source.export_space_package_json(&source_space_id)?;
|
||||||
|
|
||||||
|
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let target_profile_id = target.snapshot()?.active_profile_id;
|
||||||
|
let imported_space_id = target
|
||||||
|
.import_space_package_json(&package_json, SpaceImportProfileMapping::UseActiveProfile)?;
|
||||||
|
let snapshot = target.snapshot()?;
|
||||||
|
let Some(imported_space) =
|
||||||
|
snapshot.spaces.iter().find(|space| space.id() == &imported_space_id)
|
||||||
|
else {
|
||||||
|
return Err("missing imported space".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(imported_space.name(), "Work");
|
||||||
|
assert_eq!(imported_space.archive_policy(), &ArchivePolicy::IdleDays(7));
|
||||||
|
assert_eq!(imported_space.sidebar_width_px(), 360);
|
||||||
|
assert_eq!(imported_space.default_profile_id(), &target_profile_id);
|
||||||
|
assert!(snapshot.tabs.iter().all(|tab| tab.profile_id() == &target_profile_id));
|
||||||
|
assert!(snapshot.tabs.iter().any(|tab| tab.url().as_str() == "https://example.com"));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn imports_space_package_with_existing_profile_mapping() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let research_profile_id = core.create_profile("Research", 0x9fc9a2, ProfileKind::Standard)?;
|
||||||
|
let research_space_id = core.create_space("Research", "R", 0x9fc9a2)?;
|
||||||
|
core.open_tab(UrlText::parse("https://servo.org/")?);
|
||||||
|
let package_json = core.export_space_package_json(&research_space_id)?;
|
||||||
|
|
||||||
|
let imported_space_id =
|
||||||
|
core.import_space_package_json(&package_json, SpaceImportProfileMapping::PreserveExisting)?;
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let Some(imported_space) =
|
||||||
|
snapshot.spaces.iter().find(|space| space.id() == &imported_space_id)
|
||||||
|
else {
|
||||||
|
return Err("missing imported space".into());
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(imported_space.default_profile_id(), &research_profile_id);
|
||||||
|
assert!(snapshot.tabs.iter().all(|tab| tab.profile_id() == &research_profile_id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn importing_space_package_rejects_missing_preserved_profile() -> Result<(), Box<dyn Error>> {
|
||||||
|
let source = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let source_profile_id = source.snapshot()?.active_profile_id;
|
||||||
|
let source_space_id = source.snapshot()?.active_space_id;
|
||||||
|
let package_json = source.export_space_package_json(&source_space_id)?;
|
||||||
|
|
||||||
|
let mut target = BrowserCore::new(InitialBrowserConfig::ely_defaults()?)?;
|
||||||
|
let error = match target
|
||||||
|
.import_space_package_json(&package_json, SpaceImportProfileMapping::PreserveExisting)
|
||||||
|
{
|
||||||
|
Err(error) => error,
|
||||||
|
Ok(_) => return Err("import should reject missing preserved profile".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(error, CoreError::ProfileNotFound { id: source_profile_id });
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_object<'a>(
|
||||||
|
value: &'a Value,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<&'a serde_json::Map<String, Value>, Box<dyn Error>> {
|
||||||
|
value.get(key).and_then(Value::as_object).ok_or_else(|| format!("missing object {key}").into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_array<'a>(value: &'a Value, key: &str) -> Result<&'a Vec<Value>, Box<dyn Error>> {
|
||||||
|
value.get(key).and_then(Value::as_array).ok_or_else(|| format!("missing array {key}").into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_str<'a>(
|
||||||
|
value: &'a serde_json::Map<String, Value>,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<&'a str, Box<dyn Error>> {
|
||||||
|
value.get(key).and_then(Value::as_str).ok_or_else(|| format!("missing string {key}").into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_object_u64(
|
||||||
|
value: &serde_json::Map<String, Value>,
|
||||||
|
key: &str,
|
||||||
|
) -> Result<u64, Box<dyn Error>> {
|
||||||
|
value.get(key).and_then(Value::as_u64).ok_or_else(|| format!("missing number {key}").into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json_u64(value: &Value, key: &str) -> Result<u64, Box<dyn Error>> {
|
||||||
|
value.get(key).and_then(Value::as_u64).ok_or_else(|| format!("missing number {key}").into())
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user