diff --git a/crates/parsedbuf/src/lib.rs b/crates/parsedbuf/src/lib.rs index aa7392ae17..9796494b15 100644 --- a/crates/parsedbuf/src/lib.rs +++ b/crates/parsedbuf/src/lib.rs @@ -99,7 +99,7 @@ macro_rules! parsed { } } - // Allow tests to manufacture owned instances with known good values. + // Allow tests and indexes to manufacture owned instances with known good values. #[allow(dead_code)] impl $owned_type_name { $crate::paste::paste! { diff --git a/crates/spk-schema/crates/foundation/src/ident_build/build_id.rs b/crates/spk-schema/crates/foundation/src/ident_build/build_id.rs index 2fc243a390..6b4c95d8fb 100644 --- a/crates/spk-schema/crates/foundation/src/ident_build/build_id.rs +++ b/crates/spk-schema/crates/foundation/src/ident_build/build_id.rs @@ -16,6 +16,18 @@ impl BuildId { Self(chars) } + /// Make a new BuildId from a list of chars without checking + /// them. + /// + /// # Safety + /// + /// The caller must make sure the chars represent a valid BuildId. + pub unsafe fn new_unchecked(value: Vec) -> Self { + let mut chars = [0 as char; Self::SIZE]; + chars.copy_from_slice(&value); + Self::new(chars) + } + pub fn new_from_bytes(bytes: &[u8]) -> Self { let encoded = data_encoding::BASE32.encode(bytes); Self( diff --git a/crates/spk-schema/crates/foundation/src/ident_ops/parsing/ident.rs b/crates/spk-schema/crates/foundation/src/ident_ops/parsing/ident.rs index ef2d8654da..e376d5a5ce 100644 --- a/crates/spk-schema/crates/foundation/src/ident_ops/parsing/ident.rs +++ b/crates/spk-schema/crates/foundation/src/ident_ops/parsing/ident.rs @@ -45,6 +45,17 @@ impl NormalizedVersionString { pub fn as_str(&self) -> &str { &self.0 } + + /// Make a new NormalizedVersionString from a string without + /// checking its validity. + /// + /// # Safety + /// + /// The caller must make sure the string is a valid normalized + /// version string. + pub unsafe fn new_unchecked(normalized_version_string: String) -> Self { + Self(normalized_version_string) + } } impl std::fmt::Display for NormalizedVersionString { diff --git a/crates/spk-schema/crates/foundation/src/ident_ops/parsing/mod.rs b/crates/spk-schema/crates/foundation/src/ident_ops/parsing/mod.rs index 1be1485981..d232a3b522 100644 --- a/crates/spk-schema/crates/foundation/src/ident_ops/parsing/mod.rs +++ b/crates/spk-schema/crates/foundation/src/ident_ops/parsing/mod.rs @@ -30,7 +30,13 @@ use crate::version::parsing::{version, version_str}; mod ident; mod request; -pub use ident::{IdentParts, IdentPartsBuf, ident_parts, ident_parts_with_components}; +pub use ident::{ + IdentParts, + IdentPartsBuf, + NormalizedVersionString, + ident_parts, + ident_parts_with_components, +}; pub use request::{range_ident_pkg_name, request_pkg_name_and_version}; pub static KNOWN_REPOSITORY_NAMES: Lazy> = Lazy::new(|| { diff --git a/crates/spk-schema/crates/foundation/src/version/compat/mod.rs b/crates/spk-schema/crates/foundation/src/version/compat/mod.rs index 822236ae6b..f6c8875c6c 100644 --- a/crates/spk-schema/crates/foundation/src/version/compat/mod.rs +++ b/crates/spk-schema/crates/foundation/src/version/compat/mod.rs @@ -55,12 +55,20 @@ pub enum CompatRule { Binary, } +// TODO: CompatRules that allow ::None are used in Compat +// structs. CompatRules that do not allow ::None are used in +// required_compat's in Requests. They should be separate types, +// perhaps one wrapping the other, to clarify where ::None is and is +// not a valid value. impl std::fmt::Display for CompatRule { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { if f.alternate() { // Request for alternate (long form) names. f.write_str(match self { - CompatRule::None => unreachable!(), + CompatRule::None => unreachable!( + "CompatRule '{}' fmt() cannot be displayed when using the alternate flag", + self + ), CompatRule::API => API_STR, CompatRule::Binary => BINARY_STR, }) @@ -690,6 +698,15 @@ impl Compat { } } + /// Create a compat from the given string without checking it. For + /// internal use only for data from a index. + /// + pub fn new_from_compat_str(compat: &str) -> Result { + // TODO: change this to be more direct once Compat objects are + // directly represented in indexes. + Self::from_str(compat) + } + /// Return true if the two versions are api compatible by this compat rule. pub fn is_api_compatible(&self, base: &Version, other: &Version) -> Compatibility { self.check_compat(base, other, CompatRule::API) diff --git a/crates/spk-schema/crates/foundation/src/version/mod.rs b/crates/spk-schema/crates/foundation/src/version/mod.rs index 221102fe24..43e3044977 100644 --- a/crates/spk-schema/crates/foundation/src/version/mod.rs +++ b/crates/spk-schema/crates/foundation/src/version/mod.rs @@ -392,6 +392,12 @@ impl Version { } } + /// Make a new Version from a string without checking it. + /// + pub fn new_from_version_str(version_str: &str) -> Result { + Version::try_from(version_str) + } + /// The major version number (first component) pub fn major(&self) -> u32 { self.parts.first().copied().unwrap_or_default() diff --git a/crates/spk-schema/crates/foundation/src/version_range/mod.rs b/crates/spk-schema/crates/foundation/src/version_range/mod.rs index 259a660d90..3998845b84 100644 --- a/crates/spk-schema/crates/foundation/src/version_range/mod.rs +++ b/crates/spk-schema/crates/foundation/src/version_range/mod.rs @@ -1208,6 +1208,19 @@ impl VersionFilter { } } + /// Makes a VersionFilter from the given string, without checking + /// that it is valid. This is used by indexing. + /// + /// # Safety + /// + /// The caller must make sure the string parses ad a valid + /// VersionFilter. + pub unsafe fn new_unchecked(filter_string: &str) -> Self { + VersionFilter::from_str(filter_string).expect( + "A filter string given to VersionFilter::new_unchecked() should be a valid VersionFilter when parsed", + ) + } + pub fn single(item: VersionRange) -> Self { let mut filter = Self::default(); filter.rules.insert(item); diff --git a/crates/spk-schema/src/component_embedded_packages.rs b/crates/spk-schema/src/component_embedded_packages.rs index 79559a3729..af8ff354b1 100644 --- a/crates/spk-schema/src/component_embedded_packages.rs +++ b/crates/spk-schema/src/component_embedded_packages.rs @@ -35,6 +35,17 @@ impl ComponentEmbeddedPackage { } } + /// Create a new `ComponentEmbeddedPackage` directly from the + /// given pieces without checking them. + /// + /// # Safety + /// + /// The caller must make sure the given pieces can make a valid + /// component embedded package. + pub unsafe fn new_unchecked(pkg: OptVersionIdent, components: BTreeSet) -> Self { + Self { pkg, components } + } + #[inline] pub fn components(&self) -> &BTreeSet { &self.components diff --git a/crates/spk-schema/src/component_spec.rs b/crates/spk-schema/src/component_spec.rs index 415c398fb5..db5dc2f8d7 100644 --- a/crates/spk-schema/src/component_spec.rs +++ b/crates/spk-schema/src/component_spec.rs @@ -11,7 +11,7 @@ use spk_schema_foundation::name::PkgName; use spk_schema_foundation::option_map::OptionMap; use spk_schema_foundation::spec_ops::{ComponentFileMatchMode, HasBuildIdent}; -use super::RequirementsList; +use super::{ComponentEmbeddedPackagesList, RequirementsList}; use crate::component_spec_list::ComponentSpecDefaults; use crate::foundation::ident_component::Component; use crate::foundation::spec_ops::{ComponentOps, FileMatcher}; @@ -32,7 +32,7 @@ struct RawComponentSpec { #[serde(default)] requirements: super::RequirementsList, #[serde(default)] - embedded: super::ComponentEmbeddedPackagesList, + embedded: ComponentEmbeddedPackagesList, #[serde(default)] file_match_mode: ComponentFileMatchMode, } @@ -72,7 +72,7 @@ pub struct ComponentSpec { default, skip_serializing_if = "super::ComponentEmbeddedPackagesList::is_fabricated" )] - pub embedded: super::ComponentEmbeddedPackagesList, + pub embedded: ComponentEmbeddedPackagesList, #[serde(default)] pub file_match_mode: ComponentFileMatchMode, @@ -193,6 +193,34 @@ impl ComponentSpec { file_match_mode, }) } + + /// Create a component spec from parts without checking them. + /// + /// # Safety + /// + /// The caller must make sure the pieces make up a valid + /// ComponentSpec. + #[allow(clippy::too_many_arguments)] + pub unsafe fn new_unchecked( + name: Component, + uses: Vec, + // This is needed because some cases call for empty rules and + // others for a single wild card rule. + files: FileMatcher, + options: &OptionMap, + requirements: RequirementsList, + embedded: ComponentEmbeddedPackagesList, + ) -> ComponentSpec { + ComponentSpec { + name, + uses, + files, + requirements_with_options: (options.iter(), &requirements).into(), + requirements, + embedded, + file_match_mode: Default::default(), + } + } } impl ComponentSpecDefaults for ComponentSpec { diff --git a/crates/spk-schema/src/component_spec_list.rs b/crates/spk-schema/src/component_spec_list.rs index 623fa2c97c..8b159fc816 100644 --- a/crates/spk-schema/src/component_spec_list.rs +++ b/crates/spk-schema/src/component_spec_list.rs @@ -58,6 +58,10 @@ impl ComponentSpecList where ComponentSpecT: ComponentOps, { + pub fn new(component_specs: Vec) -> Self { + ComponentSpecList(component_specs) + } + /// Collect the names of all components in this list pub fn names(&self) -> HashSet<&Component> { self.iter().map(|i| i.name()).collect() diff --git a/crates/spk-schema/src/lib.rs b/crates/spk-schema/src/lib.rs index cda47205a9..82cf94e419 100644 --- a/crates/spk-schema/src/lib.rs +++ b/crates/spk-schema/src/lib.rs @@ -28,7 +28,7 @@ pub mod validation; pub mod variant; pub use build_spec::BuildSpec; -pub use component_embedded_packages::ComponentEmbeddedPackagesList; +pub use component_embedded_packages::{ComponentEmbeddedPackage, ComponentEmbeddedPackagesList}; pub use component_spec::ComponentSpec; pub use component_spec_list::ComponentSpecList; pub use deprecate::{Deprecate, DeprecateMut}; diff --git a/crates/spk-schema/src/option.rs b/crates/spk-schema/src/option.rs index 0ed7c9d5eb..475ad31599 100644 --- a/crates/spk-schema/src/option.rs +++ b/crates/spk-schema/src/option.rs @@ -522,6 +522,30 @@ impl VarOpt { }) } + /// Create a VarOpt from the given pieces without checking them. + /// + /// # Safety + /// + /// The caller must make sure the pieces make up a valid var opt. + pub unsafe fn new_unchecked>( + var: S, + inheritance: Inheritance, + compat: Option, + required: bool, + value: Option, + ) -> Self { + Self { + var: unsafe { OptNameBuf::from_string(var.as_ref().to_string()) }, + default: String::default(), + choices: IndexSet::default(), + inheritance, + description: None, + compat, + required, + value, + } + } + pub fn get_value(&self, given: Option<&str>) -> Option { if let Some(v) = &self.value && !v.is_empty() @@ -668,6 +692,29 @@ impl PkgOpt { }) } + /// Create a PkgOpt from the given pieces without checking them. + /// + /// # Safety + /// + /// The caller must make sure the given pieces can combine into a + /// valid pkg opt. + pub unsafe fn new_unchecked( + name: PkgNameBuf, + components: ComponentBTreeSetBuf, + prerelease_policy: Option, + value: Option, + required_compat: Option, + ) -> Self { + Self { + pkg: name, + components, + default: String::default(), + prerelease_policy, + value, + required_compat, + } + } + pub fn get_value(&self, given: Option<&str>) -> Option { if let Some(v) = &self.value { Some(v.clone()) diff --git a/crates/spk-schema/src/requirements_list.rs b/crates/spk-schema/src/requirements_list.rs index e0ee9eafe3..1c7a42af0a 100644 --- a/crates/spk-schema/src/requirements_list.rs +++ b/crates/spk-schema/src/requirements_list.rs @@ -430,6 +430,17 @@ impl RequirementsList { } Ok(out) } + + /// Construct a RequirementsList from a RequestWithOptions list, + /// without checking them, or merging them together. + /// + /// # Safety + /// + /// The caller must make sure the requests are valid and they + /// have already been merged if needed. + pub unsafe fn new_checked(requests_with_options: Vec) -> Self { + Self(requests_with_options) + } } impl std::fmt::Display for RequirementsList diff --git a/crates/spk-schema/src/v0/embedded_package_spec.rs b/crates/spk-schema/src/v0/embedded_package_spec.rs index bb59ab3ce1..6949a1853d 100644 --- a/crates/spk-schema/src/v0/embedded_package_spec.rs +++ b/crates/spk-schema/src/v0/embedded_package_spec.rs @@ -104,6 +104,32 @@ impl EmbeddedPackageSpec { } } + /// Create a spec for the identified package from the given values + /// without checking them. + /// + /// # Safety + /// + /// The caller must make sure the pieces combine to make a valid + /// EmbeddedPackageSpec. + pub unsafe fn new_unchecked( + ident: BuildIdent, + build: EmbeddedBuildSpec, + requirements_with_options: RequirementsList, + install: EmbeddedInstallSpec, + ) -> Self { + EmbeddedPackageSpec { + pkg: ident, + meta: Meta::default(), + compat: Compat::default(), + deprecated: bool::default(), + sources: Vec::new(), + build, + tests: Vec::new(), + install, + install_requirements_with_options: requirements_with_options, + } + } + /// Read-only access to the build spec #[inline] pub fn build(&self) -> &EmbeddedBuildSpec {