diff --git a/Cargo.lock b/Cargo.lock index 6df7948702..b6f7a42264 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5161,6 +5161,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + [[package]] name = "monostate" version = "0.1.13" @@ -5337,7 +5348,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio", + "mio 0.8.11", "walkdir", "windows-sys 0.45.0", ] @@ -6305,9 +6316,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -8354,6 +8365,7 @@ dependencies = [ "serde", "spin-app", "spin-factor-outbound-http", + "spin-routes", "toml", "tracing", "wasmtime", @@ -8507,7 +8519,6 @@ dependencies = [ "toml", "ui-testing", "url", - "wasm-pkg-common", ] [[package]] @@ -8585,6 +8596,18 @@ dependencies = [ name = "spin-resource-table" version = "3.4.0-pre0" +[[package]] +name = "spin-routes" +version = "3.4.0-pre0" +dependencies = [ + "anyhow", + "indexmap 2.7.1", + "percent-encoding", + "routefinder", + "serde", + "tracing", +] + [[package]] name = "spin-runtime-config" version = "3.4.0-pre0" @@ -8649,10 +8672,11 @@ version = "3.4.0-pre0" dependencies = [ "anyhow", "base64 0.22.1", + "futures-util", + "http 1.1.0", "schemars", "semver", "serde", - "wasm-pkg-common", ] [[package]] @@ -9410,28 +9434,27 @@ dependencies = [ [[package]] name = "tokio" -version = "1.38.1" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", "libc", - "mio", - "num_cpus", + "mio 1.0.4", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 6802ef1604..27f18a8a0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ pretty_assertions = "1.3" regex = { workspace = true } reqwest = { workspace = true } rpassword = "7" -schemars = { version = "0.8.21", features = ["indexmap2", "semver"] } +schemars = { workspace = true } semver = { workspace = true } serde = { version = "1", features = ["derive"] } serde_json = { workspace = true } @@ -151,6 +151,7 @@ rusqlite = "0.34" # If both `aws_lc_rs` and `ring` are enabled, a panic at runtime will occur. rustls = { version = "0.23", default-features = false, features = ["ring", "std", "logging", "tls12"] } rustls-pki-types = "1.12" +schemars = { version = "0.8.22", features = ["indexmap2", "semver"] } semver = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "1.0" @@ -170,7 +171,6 @@ walkdir = "2" wasm-encoder = "0.230" wasm-metadata = "0.230" wasm-pkg-client = "0.10" -wasm-pkg-common = "0.10" wasmparser = "0.230" wasmtime = "33.0.0" wasmtime-wasi = "33.0.0" diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml index a374dba640..6806c32d7d 100644 --- a/crates/http/Cargo.toml +++ b/crates/http/Cargo.toml @@ -15,6 +15,7 @@ routefinder = "0.5.4" serde = { workspace = true } spin-app = { path = "../app", optional = true } spin-factor-outbound-http = { path = "../factor-outbound-http" } +spin-routes = { path = "../routes" } tracing = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } diff --git a/crates/http/src/config.rs b/crates/http/src/config.rs index 5ccee8e577..09e427e4b9 100644 --- a/crates/http/src/config.rs +++ b/crates/http/src/config.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use spin_routes::HttpTriggerRouteConfig; /// Configuration for the HTTP trigger #[derive(Clone, Debug, Default, Deserialize, Serialize)] @@ -13,34 +14,6 @@ pub struct HttpTriggerConfig { pub executor: Option, } -/// An HTTP trigger route -#[derive(Clone, Debug, Deserialize, Serialize)] -#[serde(untagged)] -pub enum HttpTriggerRouteConfig { - Route(String), - Private(HttpPrivateEndpoint), -} - -/// Indicates that a trigger is a private endpoint (not routable). -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -#[serde(deny_unknown_fields)] -pub struct HttpPrivateEndpoint { - /// Whether the private endpoint is private. This must be true. - pub private: bool, -} - -impl Default for HttpTriggerRouteConfig { - fn default() -> Self { - Self::Route(Default::default()) - } -} - -impl> From for HttpTriggerRouteConfig { - fn from(value: T) -> Self { - Self::Route(value.into()) - } -} - /// The executor for the HTTP component. /// The component can either implement the Spin HTTP interface, /// the `wasi-http` interface, or the Wagi CGI interface. diff --git a/crates/http/src/lib.rs b/crates/http/src/lib.rs index f92658bf63..8119a3ae9c 100644 --- a/crates/http/src/lib.rs +++ b/crates/http/src/lib.rs @@ -3,12 +3,12 @@ pub use wasmtime_wasi_http::body::HyperIncomingBody as Body; pub mod app_info; pub mod config; -pub mod routes; pub mod trigger; #[cfg(feature = "runtime")] pub mod wagi; -pub const WELL_KNOWN_PREFIX: &str = "/.well-known/spin/"; +pub use spin_routes as routes; +pub use spin_routes::WELL_KNOWN_PREFIX; #[cfg(feature = "runtime")] pub mod body { diff --git a/crates/loader/src/local.rs b/crates/loader/src/local.rs index 511202fb76..f9467f5725 100644 --- a/crates/loader/src/local.rs +++ b/crates/loader/src/local.rs @@ -15,7 +15,7 @@ use spin_manifest::schema::v2::{self, AppManifest, KebabId, WasiFilesMount}; use spin_outbound_networking_config::allowed_hosts::{ AllowedHostsConfig, SERVICE_CHAINING_DOMAIN_SUFFIX, }; -use spin_serde::DependencyName; +use spin_serde::{DependencyName, PackageRef, Registry}; use std::collections::BTreeMap; use tokio::{io::AsyncWriteExt, sync::Semaphore}; @@ -405,19 +405,27 @@ impl LocalLoader { async fn load_registry_source( &self, - registry: Option<&wasm_pkg_client::Registry>, - package: &wasm_pkg_client::PackageRef, + registry: Option<&Registry>, + package: &PackageRef, version: &semver::VersionReq, ) -> Result { let mut client_config = wasm_pkg_client::Config::global_defaults().await?; - - if let Some(registry) = registry.cloned() { + let registry = registry + .map(|r| wasm_pkg_client::Registry::try_from(r.to_string())) + .transpose() + .context("Failed to parse registry")?; + let package = package + .to_string() + .parse::() + .context("Failed to parse package name")?; + + if let Some(registry) = registry.clone() { let mapping = wasm_pkg_client::RegistryMapping::Registry(registry); client_config.set_package_registry_override(package.clone(), mapping); } let pkg_loader = wasm_pkg_client::Client::new(client_config); - let mut releases = pkg_loader.list_all_versions(package).await.map_err(|e| { + let mut releases = pkg_loader.list_all_versions(&package).await.map_err(|e| { if matches!(e, wasm_pkg_client::Error::NoRegistryForNamespace(_)) && registry.is_none() { anyhow!("No default registry specified for wasm-pkg-loader. Create a default config, or set `registry` for package {package:?}") } else { @@ -434,7 +442,7 @@ impl LocalLoader { .with_context(|| format!("No matching version found for {package} {version}",))?; let release = pkg_loader - .get_release(package, &release_version.version) + .get_release(&package, &release_version.version) .await?; let digest = match &release.content_digest { @@ -444,7 +452,7 @@ impl LocalLoader { let path = if let Ok(cached_path) = self.cache.wasm_file(&digest) { cached_path } else { - let mut stm = pkg_loader.stream_content(package, &release).await?; + let mut stm = pkg_loader.stream_content(&package, &release).await?; self.cache.ensure_dirs().await?; let dest = self.cache.wasm_path(&digest); diff --git a/crates/manifest/Cargo.toml b/crates/manifest/Cargo.toml index 42344658db..13b0ca007e 100644 --- a/crates/manifest/Cargo.toml +++ b/crates/manifest/Cargo.toml @@ -7,7 +7,7 @@ edition = { workspace = true } [dependencies] anyhow = { workspace = true } indexmap = { workspace = true, features = ["serde"] } -schemars = { version = "0.8.21", features = ["indexmap2", "semver"] } +schemars = { workspace = true } semver = { workspace = true, features = ["serde"] } serde = { workspace = true } spin-serde = { path = "../serde" } @@ -15,7 +15,6 @@ terminal = { path = "../terminal" } thiserror = { workspace = true } toml = { workspace = true, features = ["preserve_order"] } url = { workspace = true } -wasm-pkg-common = { workspace = true } [dev-dependencies] anyhow = { workspace = true } diff --git a/crates/manifest/src/schema/common.rs b/crates/manifest/src/schema/common.rs index b56094d88d..b50f64d5e2 100644 --- a/crates/manifest/src/schema/common.rs +++ b/crates/manifest/src/schema/common.rs @@ -2,8 +2,7 @@ use std::fmt::Display; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; - -use wasm_pkg_common::{package::PackageRef, registry::Registry}; +use spin_serde::{PackageRef, Registry}; /// Variable definition #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] diff --git a/crates/routes/Cargo.toml b/crates/routes/Cargo.toml new file mode 100644 index 0000000000..cb030648de --- /dev/null +++ b/crates/routes/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "spin-routes" +version.workspace = true +authors.workspace = true +edition.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +rust-version.workspace = true + +[dependencies] +anyhow = { workspace = true } +indexmap = { workspace = true } +percent-encoding = "2" +routefinder = "0.5.4" +serde = { workspace = true } +tracing = { workspace = true } + +[lints] +workspace = true diff --git a/crates/http/src/routes.rs b/crates/routes/src/lib.rs similarity index 96% rename from crates/http/src/routes.rs rename to crates/routes/src/lib.rs index 6751aaf727..64208a5e15 100644 --- a/crates/http/src/routes.rs +++ b/crates/routes/src/lib.rs @@ -4,9 +4,11 @@ use anyhow::{anyhow, Result}; use indexmap::IndexMap; +use serde::{Deserialize, Serialize}; use std::{borrow::Cow, collections::HashMap, fmt}; -use crate::config::HttpTriggerRouteConfig; +/// The prefix for well-known routes. +pub const WELL_KNOWN_PREFIX: &str = "/.well-known/spin/"; /// Router for the HTTP trigger. #[derive(Clone, Debug)] @@ -383,10 +385,38 @@ fn sanitize>(s: S) -> String { } } +/// An HTTP trigger route +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum HttpTriggerRouteConfig { + /// A route that is routable. + Route(String), + /// A route that is not routable, but indicates a private endpoint. + Private(HttpPrivateEndpoint), +} + +/// Indicates that a trigger is a private endpoint (not routable). +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(deny_unknown_fields)] +pub struct HttpPrivateEndpoint { + /// Whether the private endpoint is private. This must be true. + pub private: bool, +} + +impl Default for HttpTriggerRouteConfig { + fn default() -> Self { + Self::Route(Default::default()) + } +} + +impl> From for HttpTriggerRouteConfig { + fn from(value: T) -> Self { + Self::Route(value.into()) + } +} + #[cfg(test)] mod route_tests { - use crate::config::HttpPrivateEndpoint; - use super::*; #[test] diff --git a/crates/serde/Cargo.toml b/crates/serde/Cargo.toml index a6a60eb17f..0800099011 100644 --- a/crates/serde/Cargo.toml +++ b/crates/serde/Cargo.toml @@ -7,7 +7,11 @@ edition = { workspace = true } [dependencies] anyhow = { workspace = true } base64 = { workspace = true } -schemars = { version = "0.8.21", features = ["indexmap2", "semver"] } +http = { workspace = true } +schemars = { workspace = true } semver = { workspace = true, features = ["serde"] } serde = { workspace = true } -wasm-pkg-common = { workspace = true } + +# Without this dependency, this crate will not compile on its own. +# We should figure out why. +futures-util = "0.3.30" diff --git a/crates/serde/src/dependencies.rs b/crates/serde/src/dependencies.rs index 30c0729e62..de3b091c5f 100644 --- a/crates/serde/src/dependencies.rs +++ b/crates/serde/src/dependencies.rs @@ -4,7 +4,6 @@ use crate::KebabId; use anyhow::anyhow; use serde::{Deserialize, Serialize}; use std::str::FromStr; -use wasm_pkg_common::package::PackageRef; /// Name of an import package dependency. /// @@ -77,6 +76,120 @@ impl FromStr for DependencyPackageName { } } +/// A package reference, consisting of kebab-case namespace and name. +/// +/// Ex: `wasm-pkg:client` +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)] +#[serde(into = "String", try_from = "String")] +pub struct PackageRef { + /// The package namespace + pub namespace: String, + /// The package name + pub name: String, +} + +impl PackageRef { + /// Returns the package namespace. + pub fn namespace(&self) -> &str { + &self.namespace + } + + /// Returns the package name. + pub fn name(&self) -> &str { + &self.name + } +} + +impl std::fmt::Display for PackageRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.namespace, self.name) + } +} + +impl From for String { + fn from(value: PackageRef) -> Self { + value.to_string() + } +} + +impl TryFrom for PackageRef { + type Error = anyhow::Error; + + fn try_from(mut value: String) -> Result { + let Some(colon) = value.find(':') else { + anyhow::bail!("missing expected ':'"); + }; + let name = value.split_off(colon + 1); + value.truncate(colon); + Ok(Self { + // TODO(rylev): parse both fields as labels + namespace: value, + name: name, + }) + } +} + +impl FromStr for PackageRef { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + s.to_string().try_into() + } +} + +/// A registry identifier. +/// +/// This must be a valid HTTP Host. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(into = "String", try_from = "String")] +pub struct Registry(http::uri::Authority); + +impl Registry { + /// Returns the registry host, without port number. + pub fn host(&self) -> &str { + self.0.host() + } + + /// Returns the registry port number, if given. + pub fn port(&self) -> Option { + self.0.port_u16() + } +} + +impl AsRef for Registry { + fn as_ref(&self) -> &str { + self.0.as_str() + } +} + +impl std::fmt::Display for Registry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for String { + fn from(value: Registry) -> Self { + value.to_string() + } +} + +impl std::str::FromStr for Registry { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(Self(s.parse()?)) + } +} + +impl TryFrom for Registry { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result { + Ok(Self(value.try_into()?)) + } +} + /// Name of an import dependency. /// /// For example: `foo:bar/baz@0.1.0`, `foo:bar/baz`, `foo:bar@0.1.0`, `foo:bar`, `foo-bar`. @@ -103,8 +216,8 @@ impl Ord for DependencyName { (DependencyName::Plain(a), DependencyName::Plain(b)) => a.cmp(b), (DependencyName::Package(a), DependencyName::Package(b)) => { let big_ole_tup = ( - a.package.namespace().as_ref(), - a.package.name().as_ref(), + a.package.namespace(), + a.package.name(), a.interface.as_ref(), a.version.as_ref(), ); diff --git a/crates/serde/src/lib.rs b/crates/serde/src/lib.rs index adf9a7abba..8622f5a433 100644 --- a/crates/serde/src/lib.rs +++ b/crates/serde/src/lib.rs @@ -9,7 +9,7 @@ mod version; pub use version::{FixedStringVersion, FixedVersion, FixedVersionBackwardCompatible}; -pub use dependencies::{DependencyName, DependencyPackageName}; +pub use dependencies::{DependencyName, DependencyPackageName, PackageRef, Registry}; /// A "kebab-case" identifier. pub type KebabId = id::Id<'-', false>;