docs(package): explain packaging and dependency workflows
This commit is contained in:
@@ -1,4 +1,22 @@
|
||||
//! Local build, watch, and deployment loop for Component development.
|
||||
//!
|
||||
//! The loop fingerprints Rust/WIT inputs, materializes WIT dependencies,
|
||||
//! compiles a Component, creates a content-addressed development revision,
|
||||
//! registers it, and atomically switches the Console deployment. A failed
|
||||
//! build or deployment never removes the previously active revision.
|
||||
//!
|
||||
//! Build and deployment are separate states. Once a build succeeds,
|
||||
//! `pending_deployment` retains that exact package and retries network or
|
||||
//! Console failures without rebuilding it. A later source change supersedes
|
||||
//! the pending package and starts a new build.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```text
|
||||
//! cargo run -p wasmeld-package --bin wasmeld -- \
|
||||
//! dev components/counter/Cargo.toml \
|
||||
//! --console http://127.0.0.1:8080
|
||||
//! ```
|
||||
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
@@ -57,6 +75,9 @@ pub(crate) fn run(arguments: Vec<String>) -> Result<(), Box<dyn std::error::Erro
|
||||
fs::create_dir_all(&temporary_dir)?;
|
||||
let output = temporary_dir.join("component.wasmpkg");
|
||||
let mut observed = None;
|
||||
// Keeping this separate from `observed` lets transient deployment errors
|
||||
// retry the already-built bytes. Rebuilding on every HTTP failure would
|
||||
// waste time and could create a different revision unexpectedly.
|
||||
let mut pending_deployment = None;
|
||||
|
||||
println!("watching:");
|
||||
@@ -138,6 +159,10 @@ fn deploy_component(
|
||||
console: &str,
|
||||
packed: &PackedComponent,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Order is intentional: register makes the immutable revision available,
|
||||
// activate switches new gateway resolutions, and only then may old
|
||||
// development revisions be removed. Cleanup is best-effort because a
|
||||
// successful deployment must not be reported as failed due to stale files.
|
||||
register(client, console, packed)?;
|
||||
activate(
|
||||
client,
|
||||
@@ -235,6 +260,9 @@ fn register(
|
||||
if response.status().is_success() {
|
||||
return Ok(());
|
||||
}
|
||||
// Retrying a package whose registration response was lost is safe only
|
||||
// when the exact immutable identity already exists. Do not treat every
|
||||
// conflict as success; it may represent a different control-plane error.
|
||||
if response.status() == StatusCode::CONFLICT
|
||||
&& service_exists(
|
||||
client,
|
||||
@@ -334,6 +362,9 @@ fn watch_roots(manifest_path: &Path) -> Result<Vec<PathBuf>, Box<dyn std::error:
|
||||
let module_path = component_root.join(MODULE_MANIFEST_FILE);
|
||||
if module_path.is_file() {
|
||||
let module = ModuleManifest::read(&module_path)?;
|
||||
// Path replacements are source inputs just like the Component itself.
|
||||
// Registry dependencies are immutable and represented by wit.lock, so
|
||||
// they do not need independent watch roots.
|
||||
for replacement in module.replacements.values() {
|
||||
let path = component_root.join(&replacement.path);
|
||||
if path.exists() {
|
||||
@@ -351,6 +382,8 @@ fn source_fingerprint(roots: &[PathBuf]) -> Result<Vec<u8>, Box<dyn std::error::
|
||||
}
|
||||
let mut digest = Sha256::new();
|
||||
for path in files {
|
||||
// Include paths as well as bytes so file renames trigger a rebuild even
|
||||
// when their contents are unchanged.
|
||||
digest.update(path.to_string_lossy().as_bytes());
|
||||
match fs::read(&path) {
|
||||
Ok(bytes) => digest.update(bytes),
|
||||
@@ -384,6 +417,8 @@ fn ignored_directory(path: &Path) -> bool {
|
||||
name,
|
||||
Some(".git" | ".wasmeld" | "deps" | "dist" | "node_modules" | "target")
|
||||
) {
|
||||
// `wit/deps` is generated by dependency sync. Watching it would make
|
||||
// each build rewrite watched files and trigger an endless rebuild.
|
||||
return name != Some("deps")
|
||||
|| path
|
||||
.parent()
|
||||
@@ -395,6 +430,8 @@ fn ignored_directory(path: &Path) -> bool {
|
||||
}
|
||||
|
||||
fn is_source_file(path: &Path) -> bool {
|
||||
// The list is deliberately narrow: fingerprints should represent inputs
|
||||
// to Cargo/WIT resolution, not editor state or generated artifacts.
|
||||
matches!(
|
||||
path.extension().and_then(|extension| extension.to_str()),
|
||||
Some("rs" | "wit")
|
||||
|
||||
@@ -5,6 +5,34 @@
|
||||
//! containing exactly [`PACKAGE_MANIFEST_PATH`] and [`COMPONENT_PATH`]. WIT
|
||||
//! packages use the Component Model binary WIT encoding implemented by the
|
||||
//! [`wit_package`] module; they are not stored in `.wasmpkg` containers.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```
|
||||
//! use std::io::Cursor;
|
||||
//! use wasmeld_package::{read_package, write_package};
|
||||
//!
|
||||
//! let component = b"\0asm\x0d\0\x01\0";
|
||||
//! let mut archive = Cursor::new(Vec::new());
|
||||
//! write_package(
|
||||
//! &mut archive,
|
||||
//! "image-resize",
|
||||
//! "1.0.0",
|
||||
//! "example:image-resize/service@1.0.0",
|
||||
//! component,
|
||||
//! )?;
|
||||
//!
|
||||
//! let decoded = read_package(archive.get_ref(), 1024 * 1024)?;
|
||||
//! assert_eq!(decoded.manifest.id, "image-resize");
|
||||
//! assert_eq!(decoded.component, component);
|
||||
//! # Ok::<(), wasmeld_package::PackageError>(())
|
||||
//! ```
|
||||
//!
|
||||
//! The decoder treats an uploaded archive as untrusted input: it accepts only
|
||||
//! the two canonical entries, limits uncompressed sizes, validates identifiers,
|
||||
//! and verifies the Component digest before returning bytes to the Runtime.
|
||||
|
||||
#![warn(missing_docs)]
|
||||
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
@@ -35,16 +63,26 @@ const MAX_MANIFEST_BYTES: usize = 64 * 1024;
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct PackageManifest {
|
||||
/// Manifest schema understood by this version of Wasmeld.
|
||||
pub schema_version: u32,
|
||||
/// Stable service identifier used in management and gateway routes.
|
||||
pub id: String,
|
||||
/// Immutable service revision; the same `id@revision` cannot be overwritten.
|
||||
pub revision: String,
|
||||
/// Fully versioned WIT world implemented by the Component.
|
||||
pub world: String,
|
||||
/// Archive path of the executable Component; currently always [`COMPONENT_PATH`].
|
||||
pub component: String,
|
||||
/// Lowercase SHA-256 digest of the uncompressed Component bytes.
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
impl PackageManifest {
|
||||
/// Creates a manifest and computes the component digest.
|
||||
///
|
||||
/// This constructor does not reject invalid identifiers or worlds. Call
|
||||
/// [`Self::validate`] before persisting it; [`write_package`] does this
|
||||
/// automatically.
|
||||
pub fn new(
|
||||
id: impl Into<String>,
|
||||
revision: impl Into<String>,
|
||||
@@ -94,31 +132,48 @@ impl PackageManifest {
|
||||
/// A validated component package decoded from a `.wasmpkg` archive.
|
||||
#[derive(Debug)]
|
||||
pub struct ComponentPackage {
|
||||
/// Validated identity, world, and integrity metadata.
|
||||
pub manifest: PackageManifest,
|
||||
/// Uncompressed Component bytes whose digest matches the manifest.
|
||||
pub component: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Errors produced while creating or validating component packages.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PackageError {
|
||||
/// The archive layout or a manifest value violates the package contract.
|
||||
#[error("invalid component package: {0}")]
|
||||
InvalidPackage(String),
|
||||
|
||||
/// The uncompressed Component exceeds the caller-selected upload limit.
|
||||
#[error("component exceeds the {limit}-byte uncompressed limit")]
|
||||
ComponentTooLarge { limit: usize },
|
||||
ComponentTooLarge {
|
||||
/// Maximum number of accepted uncompressed bytes.
|
||||
limit: usize,
|
||||
},
|
||||
|
||||
/// Component bytes do not match the immutable digest in `package.toml`.
|
||||
#[error("component digest mismatch: expected {expected}, found {actual}")]
|
||||
DigestMismatch { expected: String, actual: String },
|
||||
DigestMismatch {
|
||||
/// Digest declared by the package manifest.
|
||||
expected: String,
|
||||
/// Digest computed from the uploaded Component.
|
||||
actual: String,
|
||||
},
|
||||
|
||||
/// Reading or writing the package stream failed.
|
||||
#[error("failed to read or write package: {0}")]
|
||||
Io(#[from] io::Error),
|
||||
|
||||
/// The bytes are not a valid constrained ZIP archive.
|
||||
#[error("invalid ZIP container: {0}")]
|
||||
Zip(#[from] ZipError),
|
||||
|
||||
/// `package.toml` could not be decoded.
|
||||
#[error("invalid package manifest: {0}")]
|
||||
ManifestParse(#[from] toml::de::Error),
|
||||
|
||||
/// A generated package manifest could not be encoded.
|
||||
#[error("failed to serialize package manifest: {0}")]
|
||||
ManifestSerialize(#[from] toml::ser::Error),
|
||||
}
|
||||
@@ -127,6 +182,11 @@ pub enum PackageError {
|
||||
///
|
||||
/// The caller owns the destination writer. Runtime limits are intentionally not
|
||||
/// included because the Console injects platform policy during registration.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when the identity or world is invalid, the Component is
|
||||
/// empty, or the destination cannot be written.
|
||||
pub fn write_package<W>(
|
||||
writer: W,
|
||||
id: impl Into<String>,
|
||||
@@ -162,6 +222,11 @@ where
|
||||
///
|
||||
/// `max_component_bytes` applies to the uncompressed component so compressed
|
||||
/// archives cannot bypass the platform's artifact limit.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error for malformed ZIP data, extra or duplicate entries,
|
||||
/// oversized content, invalid metadata, empty Components, or digest mismatch.
|
||||
pub fn read_package(
|
||||
package_bytes: &[u8],
|
||||
max_component_bytes: usize,
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
//! Command-line entry point for Component packaging and WIT dependency workflows.
|
||||
//!
|
||||
//! Typical release packaging:
|
||||
//!
|
||||
//! ```text
|
||||
//! wasmeld wit fetch --manifest components/counter/wasmeld.toml --locked
|
||||
//! wasmeld pack components/counter/Cargo.toml --locked
|
||||
//! ```
|
||||
//!
|
||||
//! `wit fetch` resolves registry or local `replace` sources into `wit/deps`
|
||||
//! and writes `wit.lock`. `--locked` verifies that resolution matches the
|
||||
//! existing lock file and is the appropriate mode for CI. A `replace` changes
|
||||
//! only the development source used for a declared package identity; it does
|
||||
//! not rename that package or make the local path part of the published
|
||||
//! Component contract.
|
||||
|
||||
mod dev;
|
||||
|
||||
@@ -21,7 +35,9 @@ const TARGET: &str = "wasm32-wasip2";
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(crate) enum BuildProfile {
|
||||
/// Fast local build with Cargo's `debug` profile.
|
||||
Debug,
|
||||
/// Optimized artifact from Cargo's `release` profile.
|
||||
Release,
|
||||
}
|
||||
|
||||
@@ -35,22 +51,36 @@ impl BuildProfile {
|
||||
}
|
||||
|
||||
pub(crate) enum PackageIdentity {
|
||||
/// Use `[package.metadata.wasmeld].id` and the Cargo package version.
|
||||
Cargo,
|
||||
/// Use a development service ID and a Component-content revision suffix.
|
||||
Development { id: Option<String> },
|
||||
}
|
||||
|
||||
pub(crate) struct PackRequest {
|
||||
/// Cargo manifest for the Component crate.
|
||||
pub manifest_path: PathBuf,
|
||||
/// Destination package, or the workspace `dist` default.
|
||||
pub output: Option<PathBuf>,
|
||||
/// Reuse an artifact already present under Cargo's target directory.
|
||||
///
|
||||
/// The caller is responsible for ensuring it matches current sources and
|
||||
/// the requested profile.
|
||||
pub no_build: bool,
|
||||
/// Require `wit.lock` to match dependency resolution exactly.
|
||||
pub locked: bool,
|
||||
/// Cargo profile used to build and locate the Component.
|
||||
pub profile: BuildProfile,
|
||||
/// Stable release or content-addressed development identity.
|
||||
pub identity: PackageIdentity,
|
||||
}
|
||||
|
||||
pub(crate) struct PackedComponent {
|
||||
/// Validated metadata embedded in the `.wasmpkg`.
|
||||
pub manifest: PackageManifest,
|
||||
/// Path to the resulting `.wasmpkg`.
|
||||
pub output: PathBuf,
|
||||
/// Path to the raw Component produced or reused by Cargo.
|
||||
pub artifact: PathBuf,
|
||||
}
|
||||
|
||||
@@ -141,6 +171,9 @@ pub(crate) fn pack_component(
|
||||
request: PackRequest,
|
||||
) -> Result<PackedComponent, Box<dyn std::error::Error>> {
|
||||
let manifest_path = fs::canonicalize(request.manifest_path)?;
|
||||
// Materialize WIT before asking Cargo for metadata or compiling: the
|
||||
// component's `wit_bindgen::generate!` reads `wit/world.wit` and
|
||||
// `wit/deps` during macro expansion.
|
||||
sync_component_dependencies(&manifest_path, request.locked)?;
|
||||
let metadata = cargo_metadata(&manifest_path)?;
|
||||
let package = metadata
|
||||
@@ -189,6 +222,9 @@ pub(crate) fn pack_component(
|
||||
let (id, revision) = match request.identity {
|
||||
PackageIdentity::Cargo => (cargo_id, cargo_revision),
|
||||
PackageIdentity::Development { id } => {
|
||||
// The revision changes only when compiled Component bytes change.
|
||||
// This makes registration retryable and avoids mutating an
|
||||
// immutable identity while editing.
|
||||
let digest = format!("{:x}", Sha256::digest(&component));
|
||||
(
|
||||
id.unwrap_or_else(|| format!("{cargo_id}-dev")),
|
||||
@@ -316,6 +352,8 @@ fn run_wit_replace(
|
||||
|
||||
let manifest_path = resolve_module_manifest(manifest_path)?;
|
||||
let mut manifest = ModuleManifest::read(&manifest_path)?;
|
||||
// Replace is recorded in the source manifest. The next fetch/pack resolves
|
||||
// and locks its contents; generated `wit/deps` must not be hand-edited.
|
||||
if drop {
|
||||
if !manifest.drop_replacement(&package) {
|
||||
return Err(format!("replacement {package:?} does not exist").into());
|
||||
@@ -407,6 +445,9 @@ fn run_wit_publish(
|
||||
return Err(format!("registry returned HTTP {status}: {}", response.text()?).into());
|
||||
}
|
||||
let published = response.json::<WitPackageMetadata>()?;
|
||||
// A successful status is insufficient: compare the server's canonical
|
||||
// identity and digest to detect a proxy or Registry returning metadata for
|
||||
// different bytes.
|
||||
if published != package.metadata {
|
||||
return Err(format!(
|
||||
"registry metadata mismatch: expected {}@{} ({})",
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
//! prefers root-level path replacements and otherwise downloads immutable
|
||||
//! binary packages from the configured Registry. The complete transitive graph
|
||||
//! is materialized under `wit/deps` and recorded in `wit.lock`.
|
||||
//!
|
||||
//! `wasmeld.toml` is the human-authored input; `wit.lock` is the reproducibility
|
||||
//! record; and `wit/deps` is disposable generated output. Commit the first two,
|
||||
//! but regenerate `wit/deps` with [`crate::module::sync_dependencies`] after
|
||||
//! cloning.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use wasmeld_package::module::sync_dependencies;
|
||||
//!
|
||||
//! // CI uses locked mode so changed Registry content, replacements, or
|
||||
//! // transitive dependencies fail instead of rewriting wit.lock.
|
||||
//! let report = sync_dependencies("components/image-resize/wasmeld.toml", true)?;
|
||||
//! println!("resolved {} packages", report.packages.len());
|
||||
//! # Ok::<(), wasmeld_package::module::ModuleError>(())
|
||||
//! ```
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, BTreeSet},
|
||||
@@ -156,39 +173,79 @@ pub struct SyncReport {
|
||||
/// Errors raised while parsing, resolving, locking, or materializing WIT dependencies.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ModuleError {
|
||||
/// A manifest value, dependency identity, version, or path is invalid.
|
||||
#[error("invalid WIT module: {0}")]
|
||||
InvalidManifest(String),
|
||||
|
||||
/// A manifest, lock file, or local WIT source could not be read.
|
||||
#[error("failed to read {path}: {source}")]
|
||||
Read { path: PathBuf, source: io::Error },
|
||||
Read {
|
||||
/// File or directory involved in the failed read.
|
||||
path: PathBuf,
|
||||
/// Underlying filesystem error.
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
/// A lock file or materialized dependency could not be written.
|
||||
#[error("failed to write {path}: {source}")]
|
||||
Write { path: PathBuf, source: io::Error },
|
||||
Write {
|
||||
/// Destination that could not be updated.
|
||||
path: PathBuf,
|
||||
/// Underlying filesystem error.
|
||||
source: io::Error,
|
||||
},
|
||||
|
||||
/// TOML input could not be decoded.
|
||||
#[error("failed to parse {path}: {source}")]
|
||||
ParseManifest {
|
||||
/// Manifest or lock file containing invalid TOML.
|
||||
path: PathBuf,
|
||||
/// TOML decoder error.
|
||||
source: toml::de::Error,
|
||||
},
|
||||
|
||||
/// A canonical manifest or lock file could not be encoded as TOML.
|
||||
#[error("failed to serialize {path}: {source}")]
|
||||
SerializeManifest {
|
||||
/// Destination whose model could not be serialized.
|
||||
path: PathBuf,
|
||||
/// TOML encoder error.
|
||||
source: toml::ser::Error,
|
||||
},
|
||||
|
||||
/// Local or downloaded bytes are not the requested versioned WIT package.
|
||||
#[error("invalid WIT package at {path}: {message}")]
|
||||
InvalidWitPackage { path: PathBuf, message: String },
|
||||
InvalidWitPackage {
|
||||
/// Source path used while validating the package.
|
||||
path: PathBuf,
|
||||
/// Identity, version, parsing, or dependency validation detail.
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// Locked resolution produced a graph different from `wit.lock`.
|
||||
#[error("locked dependency graph differs from {path}; run `wasmeld wit tidy`")]
|
||||
LockMismatch { path: PathBuf },
|
||||
LockMismatch {
|
||||
/// Existing lock file that must be updated outside locked mode.
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// Fetching or validating an immutable Registry artifact failed.
|
||||
#[error("WIT registry request to {url} failed: {message}")]
|
||||
Registry { url: String, message: String },
|
||||
Registry {
|
||||
/// Exact Registry endpoint requested by the resolver.
|
||||
url: String,
|
||||
/// HTTP, size-limit, digest, or package-validation detail.
|
||||
message: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl ModuleManifest {
|
||||
/// Reads and validates a `wasmeld.toml` manifest.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns [`ModuleError::Read`], [`ModuleError::ParseManifest`], or
|
||||
/// [`ModuleError::InvalidManifest`] without modifying the source file.
|
||||
pub fn read(path: impl AsRef<Path>) -> Result<Self, ModuleError> {
|
||||
let path = path.as_ref();
|
||||
let source = fs::read_to_string(path).map_err(|source| ModuleError::Read {
|
||||
@@ -205,6 +262,9 @@ impl ModuleManifest {
|
||||
}
|
||||
|
||||
/// Validates and writes a canonical TOML manifest.
|
||||
///
|
||||
/// Callers editing replacements should mutate a parsed value and invoke
|
||||
/// this method once, so invalid intermediate state is never persisted.
|
||||
pub fn write(&self, path: impl AsRef<Path>) -> Result<(), ModuleError> {
|
||||
self.validate()?;
|
||||
let path = path.as_ref();
|
||||
@@ -248,6 +308,9 @@ impl ModuleManifest {
|
||||
}
|
||||
|
||||
/// Sets a package-wide or exact-version path replacement.
|
||||
///
|
||||
/// An exact key such as `wasmeld:kv@0.1.0` takes precedence over the
|
||||
/// package-wide `wasmeld:kv` key during resolution.
|
||||
pub fn set_path_replacement(
|
||||
&mut self,
|
||||
package: impl Into<String>,
|
||||
@@ -311,6 +374,16 @@ impl ModuleLock {
|
||||
/// dependencies embedded in Registry artifacts. With `locked = true`, the
|
||||
/// computed graph must exactly match the existing `wit.lock`; the function does
|
||||
/// not update the lock file.
|
||||
///
|
||||
/// The dependency directory is assembled in a temporary sibling directory and
|
||||
/// swapped into place only after the complete graph validates. A failed fetch
|
||||
/// therefore leaves both the lock and materialized dependencies intact.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when the manifest or lock is invalid, a replacement has the
|
||||
/// wrong identity, a Registry package is unavailable or oversized, or two paths
|
||||
/// resolve the same package version to different content.
|
||||
pub fn sync_dependencies(
|
||||
manifest_path: impl AsRef<Path>,
|
||||
locked: bool,
|
||||
|
||||
@@ -21,7 +21,9 @@ pub const WIT_PACKAGE_SCHEMA_VERSION: u32 = 1;
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WitDependency {
|
||||
/// Package identity in `namespace:name` form.
|
||||
pub name: String,
|
||||
/// Exact semantic version encoded by the dependency package.
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
@@ -29,32 +31,48 @@ pub struct WitDependency {
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct WitPackageMetadata {
|
||||
/// Metadata response schema used by the Registry API.
|
||||
pub schema_version: u32,
|
||||
/// Package identity derived from binary WIT contents.
|
||||
pub name: String,
|
||||
/// Explicit semantic version derived from binary WIT contents.
|
||||
pub version: String,
|
||||
/// SHA-256 digest of the complete binary WIT artifact.
|
||||
pub sha256: String,
|
||||
/// Exact direct dependencies embedded in the package.
|
||||
pub dependencies: Vec<WitDependency>,
|
||||
}
|
||||
|
||||
/// Encoded package bytes together with metadata derived from those bytes.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct BuiltWitPackage {
|
||||
/// Metadata derived by decoding the bytes after encoding.
|
||||
pub metadata: WitPackageMetadata,
|
||||
/// Standard Component Model binary WIT package.
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Errors raised while parsing WIT source or decoding a binary WIT package.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WitPackageError {
|
||||
/// WIT source could not be parsed or resolved.
|
||||
#[error("failed to parse WIT package at {path}: {message}")]
|
||||
Source { path: PathBuf, message: String },
|
||||
Source {
|
||||
/// Source file or directory passed to the parser.
|
||||
path: PathBuf,
|
||||
/// Parser or dependency-resolution detail.
|
||||
message: String,
|
||||
},
|
||||
|
||||
/// A resolved WIT package could not be encoded.
|
||||
#[error("failed to encode WIT package: {0}")]
|
||||
Encode(String),
|
||||
|
||||
/// Input bytes are not a standard binary WIT package.
|
||||
#[error("invalid binary WIT package: {0}")]
|
||||
Decode(String),
|
||||
|
||||
/// The package identity omits the version required by the Registry.
|
||||
#[error("WIT package {0} must declare an explicit semantic version")]
|
||||
MissingVersion(String),
|
||||
}
|
||||
@@ -64,6 +82,11 @@ pub enum WitPackageError {
|
||||
/// The package must declare an explicit semantic version. Dependencies
|
||||
/// available through the source path are encoded as Component Model package
|
||||
/// references and reported in [`WitPackageMetadata::dependencies`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error when WIT parsing, dependency resolution, binary encoding,
|
||||
/// or post-encode inspection fails.
|
||||
pub fn build_wit_package(path: impl AsRef<Path>) -> Result<BuiltWitPackage, WitPackageError> {
|
||||
let path = path.as_ref();
|
||||
let mut resolve = Resolve::default();
|
||||
@@ -83,6 +106,11 @@ pub fn build_wit_package(path: impl AsRef<Path>) -> Result<BuiltWitPackage, WitP
|
||||
///
|
||||
/// Ordinary WebAssembly Components are rejected even though both formats use a
|
||||
/// Component Model binary container.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the bytes are malformed, contain a Component instead of
|
||||
/// a WIT package, or any package in the direct dependency set lacks a version.
|
||||
pub fn inspect_wit_package(bytes: &[u8]) -> Result<WitPackageMetadata, WitPackageError> {
|
||||
let decoded = decode(bytes).map_err(|error| WitPackageError::Decode(error.to_string()))?;
|
||||
let DecodedWasm::WitPackage(resolve, package) = decoded else {
|
||||
|
||||
Reference in New Issue
Block a user