Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions bindings/java/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_init<'local>(
LocalSrcError::ImpossibleRelativePath(_) => {
env.throw_exception(ExceptionKind::PathError, suberror.to_string())
}
LocalSrcError::MissingMeta => {
LocalSrcError::MissingMeta | LocalSrcError::MissingInfoMeta => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
},
Expand Down Expand Up @@ -172,12 +172,12 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_env<'local>(
LocalWriteError::ImpossibleRelativePath(_) => {
env.throw_exception(ExceptionKind::PathError, suberror.to_string())
}
LocalWriteError::MissingMeta => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
LocalWriteError::AddProject(subsuberror) => {
env.throw_exception(ExceptionKind::IOError, subsuberror.to_string())
}
LocalWriteError::MissingMeta | LocalWriteError::MissingInfoMeta => {
env.throw_exception(ExceptionKind::SysandException, suberror.to_string())
}
},
},
}
Expand All @@ -195,6 +195,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_infoPath<'local>(
let project = LocalSrcProject {
nominal_path: None,
project_path: Utf8PathBuf::from(&path),
expected_checksum: None,
};

let command_result = commands::info::do_info_project(&project);
Expand Down Expand Up @@ -348,6 +349,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_setProjectIndex<'local>(
let mut project = LocalSrcProject {
nominal_path: None,
project_path: Utf8PathBuf::from(project_path),
expected_checksum: None,
};
let _ = project
.set_index(rust_index)
Expand All @@ -370,6 +372,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_setProjectInfo<'local>(
let mut project = LocalSrcProject {
nominal_path: None,
project_path: Utf8PathBuf::from(project_path),
expected_checksum: None,
};
let _ = project
.put_info(&info_raw, true)
Expand All @@ -392,6 +395,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_setProjectMetadata<'loca
let mut project = LocalSrcProject {
nominal_path: None,
project_path: Utf8PathBuf::from(project_path),
expected_checksum: None,
};
let _ = project
.put_meta(&metadata_raw, true)
Expand Down Expand Up @@ -514,6 +518,7 @@ pub extern "system" fn Java_com_sensmetry_sysand_Sysand_buildProject<'local>(
let project = LocalSrcProject {
nominal_path: None,
project_path: Utf8PathBuf::from(project_path),
expected_checksum: None,
};
let Some(compression) = env.get_str(&compression, "compression") else {
return;
Expand Down
16 changes: 15 additions & 1 deletion bindings/js/src/env/local_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// SPDX-FileCopyrightText: © 2025 Sysand contributors <opensource@sensmetry.com>

use sysand_core::{
env::{PutProjectError, ReadEnvironment, WriteEnvironment},
env::{ProjectChecksumResult, PutProjectError, ReadEnvironment, WriteEnvironment},
project::ProjectChecksum,
utils::sha256_lowercase_hex,
};
use thiserror::Error;
Expand Down Expand Up @@ -117,8 +118,19 @@ impl ReadEnvironment for LocalBrowserStorageEnvironment {
Ok(ProjectLocalBrowserStorage {
vfs: self.vfs.clone(),
root_path: self.project_path(&uri, &version),
expected_checksum: None,
})
}

// TODO: fix this when this env contains sufficient info
fn has_version_verified<S: AsRef<str>, V: AsRef<str>>(
&self,
_uri: S,
_version: V,
_checksum: &sysand_core::project::ProjectChecksum,
) -> Result<ProjectChecksumResult, Self::ReadError> {
Ok(ProjectChecksumResult::ChecksumNotPresent)
}
}

impl WriteEnvironment for LocalBrowserStorageEnvironment {
Expand All @@ -130,6 +142,7 @@ impl WriteEnvironment for LocalBrowserStorageEnvironment {
&mut self,
uri: S,
version: T,
_checksum: Option<ProjectChecksum>,
write_project: F,
) -> Result<Self::InterchangeProjectMut, sysand_core::env::PutProjectError<Self::WriteError, E>>
where
Expand All @@ -138,6 +151,7 @@ impl WriteEnvironment for LocalBrowserStorageEnvironment {
let mut project = ProjectLocalBrowserStorage {
vfs: self.vfs.clone(),
root_path: self.project_path(&uri, &version),
expected_checksum: None,
};

// TODO: For production JS-version this should be made more robust
Expand Down
35 changes: 35 additions & 0 deletions bindings/js/src/io/local_storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::io::{Cursor, Read};
use sysand_core::context::ProjectContext;

use sysand_core::lock::Source;
use sysand_core::project::{CanonicalizationError, ProjectChecksum};
use sysand_core::{
model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw},
project::{ProjectMut, ProjectRead, utils::FsIoError},
Expand All @@ -19,6 +20,9 @@ use crate::local_storage_utils::{LocalStorageError, LocalStorageVFS, get_local_b
pub struct ProjectLocalBrowserStorage {
pub root_path: Utf8UnixPathBuf,
pub vfs: LocalStorageVFS,
// TODO: enforce that the project matches the checksum if provided
// before reading; see LocalKparProject for example
pub expected_checksum: Option<String>,
}

pub fn open_project_local_storage<S: AsRef<str>, P: AsRef<Utf8UnixPath>>(
Expand All @@ -28,6 +32,7 @@ pub fn open_project_local_storage<S: AsRef<str>, P: AsRef<Utf8UnixPath>>(
Ok(ProjectLocalBrowserStorage {
root_path: root_path.as_ref().to_path_buf(),
vfs: get_local_browser_storage(prefix)?,
expected_checksum: None,
})
}

Expand All @@ -50,6 +55,8 @@ pub enum Error {
SerializeHandle(#[from] serde_json::Error),
#[error("key `{0}` not found in local storage")]
KeyNotFound(String),
#[error("project is missing `.project.json` and/or `.meta.json` files")]
MissingInfoMeta,
}

impl From<wasm_bindgen::JsValue> for Error {
Expand Down Expand Up @@ -128,10 +135,38 @@ impl ProjectRead for ProjectLocalBrowserStorage {
}

fn sources(&self, _ctx: &ProjectContext) -> Result<Vec<Source>, Self::Error> {
let checksum = match &self.expected_checksum {
Some(c) => c.clone(),
None => self
.checksum_canonical_hex()
.map_err(|e| match e {
CanonicalizationError::ProjectRead(e) => e,
CanonicalizationError::FileRead(path, error) => {
Error::Io(FsIoError::ReadFile(String::from(path).into(), error).into())
}
})?
.ok_or(Error::MissingInfoMeta)?,
};
Ok(vec![sysand_core::lock::Source::LocalSrc {
src_path: self.root_path.as_str().into(),
checksum,
}])
}

fn checksum_canonical_variant(&self) -> Result<ProjectChecksum, Self::Error> {
match self.checksum_canonical_hex() {
Ok(c) => match c {
Some(c) => Ok(ProjectChecksum::Project(c)),
None => Err(Error::MissingInfoMeta),
},
Err(e) => match e {
CanonicalizationError::ProjectRead(e) => Err(e),
CanonicalizationError::FileRead(path, error) => Err(Error::Io(
FsIoError::ReadFile(String::from(path).into(), error).into(),
)),
},
}
}
}

impl ProjectMut for ProjectLocalBrowserStorage {
Expand Down
1 change: 1 addition & 0 deletions bindings/js/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ pub fn do_init_js_local_storage(
vfs: local_storage_utils::get_local_browser_storage(prefix)
.map_err(|e| JsValue::from_str(&e.to_string()))?,
root_path: Utf8UnixPath::new(root_path).to_path_buf(),
expected_checksum: None,
},
)
.map_err(|e| JsValue::from_str(&e.to_string()))
Expand Down
28 changes: 22 additions & 6 deletions bindings/py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use sysand_core::{
model::{InterchangeProjectInfoRaw, InterchangeProjectMetadataRaw, InterchangeProjectUsageRaw},
project::{
ProjectRead as _,
local_kpar::LocalKParProject,
local_kpar::{KparInnerPath, LocalKParProject},
local_src::{LocalSrcError, LocalSrcProject},
utils::wrapfs,
},
Expand Down Expand Up @@ -79,6 +79,7 @@ fn do_init_py_local_file(
PyValueError::new_err(error.to_string())
}
LocalSrcError::MissingMeta => PyFileNotFoundError::new_err(err.to_string()),
LocalSrcError::MissingInfoMeta => PyFileNotFoundError::new_err(err.to_string()),
},
},
)?;
Expand Down Expand Up @@ -106,8 +107,10 @@ fn do_env_py_local_dir(path: String) -> PyResult<()> {
LocalWriteError::ImpossibleRelativePath(error) => {
PyValueError::new_err(error.to_string())
}
LocalWriteError::MissingMeta => PyFileNotFoundError::new_err(werr.to_string()),
LocalWriteError::AddProject(error) => PyIOError::new_err(error.to_string()),
LocalWriteError::MissingMeta | LocalWriteError::MissingInfoMeta => {
PyFileNotFoundError::new_err(werr.to_string())
}
},
})?;

Expand All @@ -126,6 +129,7 @@ fn do_info_py_path(
let project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

match do_info_project(&project) {
Expand Down Expand Up @@ -210,6 +214,7 @@ fn do_build_py(
let project = LocalSrcProject {
nominal_path: None,
project_path: current_project_path.into(),
expected_checksum: None,
};

let compression = match compression {
Expand Down Expand Up @@ -370,6 +375,7 @@ pub fn do_sources_project_py(
let current_project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

for src_path in do_sources_local_src_project_no_deps(&current_project, true)
Expand Down Expand Up @@ -433,6 +439,7 @@ fn do_add_py(path: String, iri: String, version: Option<String>) -> PyResult<()>
let mut project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

// TODO: do dependency resolution and locking?
Expand All @@ -458,6 +465,7 @@ fn do_remove_py(path: String, iri: String) -> PyResult<()> {
let mut project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

do_remove(&mut project, iri).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
Expand All @@ -481,6 +489,7 @@ fn do_include_py(
let mut project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

let force_format = match force_format {
Expand Down Expand Up @@ -516,6 +525,7 @@ fn do_exclude_py(path: String, src_path: String) -> PyResult<()> {
let mut project = LocalSrcProject {
nominal_path: None,
project_path: path.into(),
expected_checksum: None,
};

do_exclude(&mut project, src_path).map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
Expand All @@ -537,8 +547,7 @@ fn do_env_install_path_py(env_path: String, iri: String, location: String) -> Py
let metadata =
wrapfs::metadata(&location).map_err(|e| PyErr::new::<PyIOError, _>(e.to_string()))?;
if metadata.is_file() {
let project = LocalKParProject::new_guess_root(&location)
.map_err(|e| PyErr::new::<PyIOError, _>(e.to_string()))?;
let project = LocalKParProject::new(&location, KparInnerPath::Guess, None, None);

let Some(version) = project
.version()
Expand All @@ -550,14 +559,18 @@ fn do_env_install_path_py(env_path: String, iri: String, location: String) -> Py
)));
};

env.put_project(iri, version, |to| {
let checksum = project
.checksum_canonical_variant()
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
env.put_project(iri, version, Some(checksum), |to| {
clone_project(&project, to, true).map(|_| ())
})
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
} else if metadata.is_dir() {
let project = LocalSrcProject {
nominal_path: None,
project_path: location,
expected_checksum: None,
};

let Some(version) = project
Expand All @@ -569,8 +582,11 @@ fn do_env_install_path_py(env_path: String, iri: String, location: String) -> Py
project.project_path
)));
};
let checksum = project
.checksum_canonical_variant()
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;

env.put_project(iri, version, |to| {
env.put_project(iri, version, Some(checksum), |to| {
clone_project(&project, to, true).map(|_| ())
})
.map_err(|e| PyRuntimeError::new_err(e.to_string()))?;
Expand Down
4 changes: 2 additions & 2 deletions core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ lenient_checks = []
# Binding support (but not binding libraries themselves)
python = ["dep:pyo3"]
js = ["dep:wasm-bindgen"]
filesystem = ["dep:camino-tempfile", "dep:dirs", "dep:zip", "dep:idna"]
filesystem = ["dep:camino-tempfile", "dep:dirs", "dep:zip"]
networking = ["dep:reqwest", "dep:gix"] # "dep:reqwest-middleware", "dep:partialzip"
# Different compression methods for creating KPARs
kpar-bzip2 = ["zip?/bzip2"]
Expand All @@ -41,7 +41,7 @@ sha2.workspace = true
hex.workspace = true
dirs = { version = "6.0.0", optional = true}
fluent-uri = { version = "0.4.1", features = ["serde", "net"] }
idna = { version = "1.1.0", default-features = false, features = ["compiled_data"], optional = true }
idna = { version = "1.1.0", default-features = false, features = ["compiled_data"] }
indexmap = { version = "2.13.0", default-features = false, features = ["serde"] }
log = { version = "0.4.29", default-features = false }
pubgrub = { version = "0.4.0", default-features = false }
Expand Down
Loading
Loading