From 1ce9b213ccd7b7ad0ff41e7b4b5bd5b3e7044c01 Mon Sep 17 00:00:00 2001 From: Brent Date: Mon, 5 Oct 2026 13:15:18 -0400 Subject: [PATCH 1/2] feat(rust): detection-only build behind a default 'file' feature; infer 0.19 The I/O File type (and its AWS SDK, reqwest and tokio deps) moves behind a default-on 'file' feature, so default-features = false leaves only detection, content_disposition and the validation errors. Lets a service's core library sniff bytes it already holds without pulling the AWS SDK in: the Smoo AI media pipeline had two hand-written infer sniffers that drifted apart (SMOODEV-3648). Co-Authored-By: Claude Opus 5.5 --- .changeset/rust-detection-only-feature.md | 7 +++ package.json | 2 +- rust/file/Cargo.lock | 4 +- rust/file/Cargo.toml | 61 ++++++++++++++++------- rust/file/README.md | 7 +++ rust/file/src/error.rs | 1 + rust/file/src/lib.rs | 46 +++++++++++++---- rust/file/tests/detection_tests.rs | 2 + rust/file/tests/file_tests.rs | 2 + rust/file/tests/integration_tests.rs | 2 + rust/file/tests/lazy_contract_tests.rs | 2 + rust/file/tests/lazy_stream_tests.rs | 2 + 12 files changed, 109 insertions(+), 29 deletions(-) create mode 100644 .changeset/rust-detection-only-feature.md diff --git a/.changeset/rust-detection-only-feature.md b/.changeset/rust-detection-only-feature.md new file mode 100644 index 0000000..e6ee7e9 --- /dev/null +++ b/.changeset/rust-detection-only-feature.md @@ -0,0 +1,7 @@ +--- +'@smooai/file': patch +--- + +**Rust: a detection-only build.** The crate's I/O `File` type (local, URL, stream and S3 sources) is now behind a `file` feature, on by default, so existing users see no change. With `default-features = false`, `smooai-file` is just magic-byte detection (`detection`), `content_disposition` and the validation error types, and depends on only `infer`, `mime_guess` and `thiserror`, with no AWS SDK, reqwest or tokio. That makes it usable from a service's core library that only needs to sniff bytes it already holds, which is where dogfooding needs it: the Smoo AI media pipeline had two hand-written `infer` sniffers that drifted apart. CI now checks both builds. + +**Rust: `infer` 0.16 → 0.19** for newer magic-number signatures. diff --git a/package.json b/package.json index 6cd613a..3b967ac 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,7 @@ "python:test": "(cd python && uv run poe test)", "python:typecheck": "(cd python && uv run poe typecheck)", "rust:build": "(cd rust/file && cargo build --release)", - "rust:check": "(cd rust/file && cargo check --all-targets)", + "rust:check": "(cd rust/file && cargo check --all-targets && cargo check --no-default-features --all-targets)", "rust:fmt": "(cd rust/file && cargo fmt)", "rust:fmt:check": "(cd rust/file && cargo fmt --all -- --check)", "rust:lint": "(cd rust/file && cargo clippy --all-targets -- -D warnings)", diff --git a/rust/file/Cargo.lock b/rust/file/Cargo.lock index e61cbf9..dca28b9 100644 --- a/rust/file/Cargo.lock +++ b/rust/file/Cargo.lock @@ -1489,9 +1489,9 @@ dependencies = [ [[package]] name = "infer" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" dependencies = [ "cfb", ] diff --git a/rust/file/Cargo.toml b/rust/file/Cargo.toml index fbed268..ded2987 100644 --- a/rust/file/Cargo.toml +++ b/rust/file/Cargo.toml @@ -10,28 +10,55 @@ keywords = ["file", "s3", "streaming", "smooai"] categories = ["filesystem"] readme = "README.md" +[features] +# `file` = the I/O `File` type (local, URL, stream and S3 sources). On by default. +# `default-features = false` leaves `detection`, `content_disposition` and the +# validation error types, with no AWS SDK, reqwest or tokio — for services that +# only need to sniff bytes they already hold. +default = ["file"] +file = [ + "dep:reqwest", + "dep:bytes", + "dep:tokio", + "dep:tokio-util", + "dep:serde", + "dep:serde_json", + "dep:sha2", + "dep:hex", + "dep:base64", + "dep:aws-sdk-s3", + "dep:aws-config", + "dep:chrono", + "dep:futures", + "dep:tracing", + "dep:url", + "dep:tempfile", +] + [dependencies] -reqwest = { version = "0.12", features = ["json", "stream", "multipart"] } -bytes = "1" -tokio = { version = "1", features = ["full"] } -tokio-util = { version = "0.7", features = ["io"] } -serde = { version = "1", features = ["derive"] } -serde_json = "1" -infer = "0.16" +infer = "0.19" mime_guess = "2" -sha2 = "0.10" -hex = "0.4" -base64 = "0.22" -aws-sdk-s3 = "1" -aws-config = "1" -chrono = { version = "0.4", features = ["serde"] } thiserror = "2" -futures = "0.3" -tracing = "0.1" -url = "2" -tempfile = "3" +reqwest = { version = "0.12", features = ["json", "stream", "multipart"], optional = true } +bytes = { version = "1", optional = true } +tokio = { version = "1", features = ["full"], optional = true } +tokio-util = { version = "0.7", features = ["io"], optional = true } +serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1", optional = true } +sha2 = { version = "0.10", optional = true } +hex = { version = "0.4", optional = true } +base64 = { version = "0.22", optional = true } +aws-sdk-s3 = { version = "1", optional = true } +aws-config = { version = "1", optional = true } +chrono = { version = "0.4", features = ["serde"], optional = true } +futures = { version = "0.3", optional = true } +tracing = { version = "0.1", optional = true } +url = { version = "2", optional = true } +tempfile = { version = "3", optional = true } [dev-dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" tokio = { version = "1", features = ["test-util", "macros", "rt-multi-thread"] } wiremock = "0.6" memory-stats = "1" diff --git a/rust/file/README.md b/rust/file/README.md index 352c64c..2fd59a9 100644 --- a/rust/file/README.md +++ b/rust/file/README.md @@ -68,6 +68,13 @@ Or via cargo: cargo add smooai-file ``` +Only need to sniff bytes you already hold? Turn off the default `file` feature. That leaves `detection`, `content_disposition` and the validation errors, with no AWS SDK, reqwest or tokio: + +```toml +[dependencies] +smooai-file = { version = "2", default-features = false } +``` + ### Multi-Language Support smooai-file is available as native implementations in **TypeScript**, **Python**, **Rust**, and **Go** — each built with idiomatic patterns for its ecosystem. diff --git a/rust/file/src/error.rs b/rust/file/src/error.rs index eadc629..7980bbc 100644 --- a/rust/file/src/error.rs +++ b/rust/file/src/error.rs @@ -12,6 +12,7 @@ pub enum FileError { Io(#[from] std::io::Error), /// An HTTP error occurred while fetching a file from a URL. + #[cfg(feature = "file")] #[error("HTTP error: {0}")] Http(#[from] reqwest::Error), diff --git a/rust/file/src/lib.rs b/rust/file/src/lib.rs index 7a44f15..eae92f2 100644 --- a/rust/file/src/lib.rs +++ b/rust/file/src/lib.rs @@ -14,30 +14,58 @@ //! - **Streams**: Async byte streams //! - **Amazon S3**: Objects in S3 buckets //! +//! # Features +//! +//! - `file` (default): the I/O [`File`] type and its sources. Without it +//! (`default-features = false`) the crate is just [`detection`], +//! [`content_disposition`] and the [`error`] types — no AWS SDK, reqwest or +//! tokio. +//! //! # Examples //! -//! ```no_run -//! # use smooai_file::File; -//! # use bytes::Bytes; -//! # async fn example() -> smooai_file::error::Result<()> { -//! let file = File::from_bytes(Bytes::from("hello world"), None).await?; -//! let text = file.read_text().await?; -//! assert_eq!(text, "hello world"); -//! # Ok(()) -//! # } +//! Sniffing bytes you already hold (no features needed): +//! //! ``` +//! use smooai_file::detection::detect_from_bytes; +//! +//! let png = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A, 0, 0, 0, 0x0D]; +//! assert_eq!(detect_from_bytes(&png, None).mime_type.as_deref(), Some("image/png")); +//! ``` +#![cfg_attr( + feature = "file", + doc = r#" +With the `file` feature: + +```no_run +# use smooai_file::File; +# use bytes::Bytes; +# async fn example() -> smooai_file::error::Result<()> { +let file = File::from_bytes(Bytes::from("hello world"), None).await?; +let text = file.read_text().await?; +assert_eq!(text, "hello world"); +# Ok(()) +# } +``` +"# +)] pub mod content_disposition; pub mod detection; pub mod error; +#[cfg(feature = "file")] pub mod file; +#[cfg(feature = "file")] pub mod metadata; +#[cfg(feature = "file")] pub mod source; // Re-export primary types at the crate root for convenience. pub use crate::error::{FileError, FileValidationError}; +#[cfg(feature = "file")] pub use crate::file::{File, PresignedUploadOptions, LAZY_HEAD_BYTES}; +#[cfg(feature = "file")] pub use crate::metadata::{Metadata, MetadataHint}; +#[cfg(feature = "file")] pub use crate::source::FileSource; /// The crate version. diff --git a/rust/file/tests/detection_tests.rs b/rust/file/tests/detection_tests.rs index 5eb2b87..007b20c 100644 --- a/rust/file/tests/detection_tests.rs +++ b/rust/file/tests/detection_tests.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "file")] + //! Integration tests for file type detection. use smooai_file::detection::{ diff --git a/rust/file/tests/file_tests.rs b/rust/file/tests/file_tests.rs index 4e97a49..d38e9ad 100644 --- a/rust/file/tests/file_tests.rs +++ b/rust/file/tests/file_tests.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "file")] + //! Integration tests for the File struct. use bytes::Bytes; diff --git a/rust/file/tests/integration_tests.rs b/rust/file/tests/integration_tests.rs index 9a9f384..efc4ecd 100644 --- a/rust/file/tests/integration_tests.rs +++ b/rust/file/tests/integration_tests.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "file")] + //! Full pipeline integration tests. use bytes::Bytes; diff --git a/rust/file/tests/lazy_contract_tests.rs b/rust/file/tests/lazy_contract_tests.rs index 318231a..8329017 100644 --- a/rust/file/tests/lazy_contract_tests.rs +++ b/rust/file/tests/lazy_contract_tests.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "file")] + //! The Rust loader for the shared lazy-streaming contract. //! //! Every port has one of these and they all read the SAME file diff --git a/rust/file/tests/lazy_stream_tests.rs b/rust/file/tests/lazy_stream_tests.rs index 8176f65..ca6aa30 100644 --- a/rust/file/tests/lazy_stream_tests.rs +++ b/rust/file/tests/lazy_stream_tests.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "file")] + //! Lazy-stream tests for SMOODEV-967. //! //! These exercise `File::from_stream_lazy`, `iter_bytes`, and the lazy path From a0c9cffcb5aa0b300c228a024a2103ea360fdcb2 Mon Sep 17 00:00:00 2001 From: Brent Date: Mon, 5 Oct 2026 13:46:30 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(dotnet):=20SourceLink=2010.0.303=20?= =?UTF-8?q?=E2=80=94=208.0.0's=20Build.Tasks.Git=20hit=20GHSA-23fw-v26w-5f?= =?UTF-8?q?gq?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TreatWarningsAsErrors turns the NU1902 advisory into a restore failure, so dotnet format/build failed on every PR. Build-time only (PrivateAssets=All). Co-Authored-By: Claude Opus 5.5 --- .changeset/rust-detection-only-feature.md | 2 ++ dotnet/Directory.Build.props | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/rust-detection-only-feature.md b/.changeset/rust-detection-only-feature.md index e6ee7e9..b42f49b 100644 --- a/.changeset/rust-detection-only-feature.md +++ b/.changeset/rust-detection-only-feature.md @@ -5,3 +5,5 @@ **Rust: a detection-only build.** The crate's I/O `File` type (local, URL, stream and S3 sources) is now behind a `file` feature, on by default, so existing users see no change. With `default-features = false`, `smooai-file` is just magic-byte detection (`detection`), `content_disposition` and the validation error types, and depends on only `infer`, `mime_guess` and `thiserror`, with no AWS SDK, reqwest or tokio. That makes it usable from a service's core library that only needs to sniff bytes it already holds, which is where dogfooding needs it: the Smoo AI media pipeline had two hand-written `infer` sniffers that drifted apart. CI now checks both builds. **Rust: `infer` 0.16 → 0.19** for newer magic-number signatures. + +**.NET: SourceLink 8.0.0 → 10.0.303.** `Microsoft.Build.Tasks.Git` 8.0.0 picked up advisory GHSA-23fw-v26w-5fgq (CVE-2026-62900), and with `TreatWarningsAsErrors` that failed every restore, which red-lit CI. This is build-time only (`PrivateAssets="All"`), so nothing ships to consumers. diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props index cbeb91d..84e8ec3 100644 --- a/dotnet/Directory.Build.props +++ b/dotnet/Directory.Build.props @@ -27,6 +27,6 @@ - +