Skip to content

Commit

Permalink
feat(js): add Gateway object (#1130)
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra authored Mar 4, 2025
1 parent 4a6ede9 commit 3f484ae
Show file tree
Hide file tree
Showing 15 changed files with 828 additions and 12 deletions.
7 changes: 0 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions js-rattler/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions js-rattler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ path = "crate/lib.rs"
default = ["console_error_panic_hook"]

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6.5"

wasm-bindgen = "0.2.95"
wasm-bindgen-futures = "0.4.50"

Expand All @@ -35,6 +38,8 @@ rattler_conda_types = { path = "../crates/rattler_conda_types" }
rattler_repodata_gateway = { path = "../crates/rattler_repodata_gateway", features = ["gateway"] }
rattler_solve = { path = "../crates/rattler_solve", default-features = false, features = ["resolvo"] }

url = "2.5.4"

# By adding the `libbz2-rs-sys` feature we ensure that bzip2 is using the rust
# implementation. This is important because the C implementation is not
# compatible with wasm.
Expand Down
10 changes: 8 additions & 2 deletions js-rattler/crate/error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rattler_conda_types::version_spec::ParseVersionSpecError;
use rattler_conda_types::{
ParseChannelError, ParseMatchSpecError, ParsePlatformError, ParseVersionError,
VersionBumpError, VersionExtendError,
InvalidPackageNameError, ParseChannelError, ParseMatchSpecError, ParsePlatformError,
ParseVersionError, VersionBumpError, VersionExtendError,
};
use rattler_repodata_gateway::GatewayError;
use rattler_solve::SolveError;
Expand All @@ -28,6 +28,10 @@ pub enum JsError {
GatewayError(#[from] GatewayError),
#[error(transparent)]
SolveError(#[from] SolveError),
#[error(transparent)]
Serde(#[from] serde_wasm_bindgen::Error),
#[error(transparent)]
PackageNameError(#[from] InvalidPackageNameError),
}

pub type JsResult<T> = Result<T, JsError>;
Expand All @@ -44,6 +48,8 @@ impl From<JsError> for JsValue {
JsError::ParseMatchSpec(error) => JsValue::from_str(&error.to_string()),
JsError::GatewayError(error) => JsValue::from_str(&error.to_string()),
JsError::SolveError(error) => JsValue::from_str(&error.to_string()),
JsError::PackageNameError(error) => JsValue::from_str(&error.to_string()),
JsError::Serde(error) => error.into(),
}
}
}
151 changes: 151 additions & 0 deletions js-rattler/crate/gateway.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use std::{collections::HashMap, path::PathBuf, str::FromStr};

use rattler_conda_types::{Channel, Platform};
use rattler_repodata_gateway::{fetch::CacheAction, ChannelConfig, Gateway, SourceConfig};
use serde::Deserialize;
use url::Url;
use wasm_bindgen::prelude::*;

use crate::JsResult;

#[wasm_bindgen]
#[repr(transparent)]
#[derive(Clone)]
pub struct JsGateway {
inner: Gateway,
}

impl From<Gateway> for JsGateway {
fn from(value: Gateway) -> Self {
JsGateway { inner: value }
}
}

impl From<JsGateway> for Gateway {
fn from(value: JsGateway) -> Self {
value.inner
}
}

impl AsRef<Gateway> for JsGateway {
fn as_ref(&self) -> &Gateway {
&self.inner
}
}

#[derive(Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct JsGatewayOptions {
max_concurrent_requests: Option<usize>,

#[serde(default)]
channel_config: JsChannelConfig,
}

#[derive(Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct JsChannelConfig {
#[serde(default)]
default: JsSourceConfig,
#[serde(default)]
per_channel: HashMap<Url, JsSourceConfig>,
}

impl From<JsChannelConfig> for ChannelConfig {
fn from(value: JsChannelConfig) -> Self {
ChannelConfig {
default: value.default.into(),
per_channel: value
.per_channel
.into_iter()
.map(|(key, value)| (key, value.into()))
.collect(),
}
}
}

fn yes() -> bool {
true
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct JsSourceConfig {
#[serde(default = "yes")]
zstd_enabled: bool,

#[serde(default = "yes")]
bz2_enabled: bool,

#[serde(default = "yes")]
sharded_enabled: bool,
}

impl Default for JsSourceConfig {
fn default() -> Self {
Self {
zstd_enabled: true,
bz2_enabled: true,
sharded_enabled: true,
}
}
}

impl From<JsSourceConfig> for SourceConfig {
fn from(value: JsSourceConfig) -> Self {
Self {
jlap_enabled: false,
zstd_enabled: value.zstd_enabled,
bz2_enabled: value.bz2_enabled,
sharded_enabled: value.sharded_enabled,
cache_action: CacheAction::default(),
}
}
}

#[wasm_bindgen]
impl JsGateway {
#[wasm_bindgen(constructor)]
pub fn new(input: JsValue) -> JsResult<Self> {
let mut builder = Gateway::builder();
let options: Option<JsGatewayOptions> = serde_wasm_bindgen::from_value(input)?;
if let Some(options) = options {
if let Some(max_concurrent_requests) = options.max_concurrent_requests {
builder.set_max_concurrent_requests(max_concurrent_requests);
}
builder.set_channel_config(options.channel_config.into());
};

Ok(Self {
inner: builder.finish(),
})
}

pub async fn names(
&self,
channels: Vec<String>,
platforms: Vec<String>,
) -> Result<Vec<String>, JsError> {
// TODO: Dont hardcode
let channel_config =
rattler_conda_types::ChannelConfig::default_with_root_dir(PathBuf::from(""));

let channels = channels
.into_iter()
.map(|s| Channel::from_str(&s, &channel_config))
.collect::<Result<Vec<_>, _>>()?;
let platforms = platforms
.into_iter()
.map(|p| Platform::from_str(&p))
.collect::<Result<Vec<_>, _>>()?;

Ok(self
.inner
.names(channels, platforms)
.execute()
.await?
.into_iter()
.map(|name| name.as_source().to_string())
.collect())
}
}
1 change: 1 addition & 0 deletions js-rattler/crate/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
mod error;
mod gateway;
pub mod solve;
mod utils;
mod version;
Expand Down
64 changes: 64 additions & 0 deletions js-rattler/src/Gateway.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, expect, it } from "@jest/globals";
import { Gateway } from "./Gateway";

describe("Gateway", () => {
describe("constructor", () => {
it("works without arguments", () => {
expect(() => new Gateway()).not.toThrowError();
expect(() => new Gateway(null)).not.toThrowError();
expect(() => new Gateway(undefined)).not.toThrowError();
});
it("throws on invalid arguments", () => {
expect(() => new Gateway(true as any)).toThrowError();
});
it("accepts an empty object", () => {
expect(() => new Gateway({})).not.toThrowError();
});
it("accepts null for maxConcurrentRequests", () => {
expect(
() =>
new Gateway({
maxConcurrentRequests: null,
}),
).not.toThrowError();
});
it("accepts empty channelConfig", () => {
expect(
() =>
new Gateway({
channelConfig: {},
}),
).not.toThrowError();
});
it("accepts perChannel channelConfig", () => {
expect(
() =>
new Gateway({
channelConfig: {
default: {},
perChannel: {
"https://prefix.dev": {
bz2Enabled: false,
shardedEnabled: false,
zstdEnabled: false,
},
},
},
}),
).not.toThrowError();
});
});
describe("names", () => {
const gateway = new Gateway();
it("can query prefix.dev", () => {
return gateway
.names(
["https://prefix.dev/emscripten-forge-dev"],
["noarch", "emscripten-wasm32"],
)
.then((names) => {
expect(names.length).toBeGreaterThanOrEqual(177);
});
});
});
});
Loading

0 comments on commit 3f484ae

Please sign in to comment.