diff --git a/Cargo.lock b/Cargo.lock index 124c6f3e..c5bf503d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -2891,6 +2891,7 @@ dependencies = [ name = "rocks" version = "0.1.0" dependencies = [ + "assert_fs", "clap 4.5.23", "eyre", "git-url-parse", @@ -2912,6 +2913,7 @@ dependencies = [ "text_trees", "tokio", "toml", + "url", "uzers", "walkdir", "which 7.0.0", @@ -2969,6 +2971,7 @@ dependencies = [ "tempdir", "thiserror 2.0.3", "tokio", + "url", "vfs", "walkdir", "which 7.0.0", @@ -4123,9 +4126,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", diff --git a/rocks-bin/Cargo.toml b/rocks-bin/Cargo.toml index ebc7de2a..da0c42ce 100644 --- a/rocks-bin/Cargo.toml +++ b/rocks-bin/Cargo.toml @@ -27,6 +27,10 @@ walkdir = "2.5.0" which = "7.0.0" indicatif = "0.17.8" path-absolutize = "3.1.1" +url = "2.5.4" + +[dev-dependencies] +assert_fs = "1.1.2" [dependencies.rocks-lib] path = "../rocks-lib/" diff --git a/rocks-bin/src/fetch.rs b/rocks-bin/src/fetch.rs index c7d1993c..d6c06052 100644 --- a/rocks-bin/src/fetch.rs +++ b/rocks-bin/src/fetch.rs @@ -16,7 +16,8 @@ pub async fn fetch_remote(data: UnpackRemote, config: Config) -> Result<()> { let rockspec = Download::new(&package_req, &config, &bar) .download_rockspec() - .await?; + .await? + .rockspec; let destination = data .path diff --git a/rocks-bin/src/info.rs b/rocks-bin/src/info.rs index 0d600fbe..0508281d 100644 --- a/rocks-bin/src/info.rs +++ b/rocks-bin/src/info.rs @@ -22,7 +22,8 @@ pub async fn info(data: Info, config: Config) -> Result<()> { let rockspec = Download::new(&data.package, &config, &bar) .download_rockspec() - .await?; + .await? + .rockspec; bar.map(|b| b.finish_and_clear()); diff --git a/rocks-bin/src/main.rs b/rocks-bin/src/main.rs index e3a33259..d4f06055 100644 --- a/rocks-bin/src/main.rs +++ b/rocks-bin/src/main.rs @@ -29,6 +29,7 @@ use rocks_lib::{ config::{ConfigBuilder, LuaVersion}, lockfile::PinnedState::{Pinned, Unpinned}, }; +use url::Url; /// A fast and efficient Lua package manager. #[derive(Parser)] @@ -42,12 +43,12 @@ pub struct Cli { /// Fetch rocks/rockspecs from this server (takes priority /// over config file). #[arg(long, value_name = "server")] - pub server: Option, + pub server: Option, /// Fetch rocks/rockspecs from this server in addition to the main server /// (overrides any entries in the config file). #[arg(long, value_name = "extra-server")] - pub extra_servers: Option>, + pub extra_servers: Option>, /// Restrict downloads to paths matching the given URL. #[arg(long, value_name = "url")] diff --git a/rocks-bin/src/pin.rs b/rocks-bin/src/pin.rs index e47890ac..b30c3834 100644 --- a/rocks-bin/src/pin.rs +++ b/rocks-bin/src/pin.rs @@ -21,7 +21,7 @@ pub fn set_pinned_state(data: ChangePin, config: Config, pin: PinnedState) -> Re match tree.match_rocks_and(&data.package.clone().into_package_req(), |package| { pin != package.pinned() })? { - RockMatches::Single(mut rock) => Ok(operations::set_pinned_state(&mut rock, &tree, pin)?), + RockMatches::Single(rock) => Ok(operations::set_pinned_state(&rock, &tree, pin)?), RockMatches::Many(_) => { todo!("Add an error here about many conflicting types and to use `all:`") } diff --git a/rocks-bin/src/run_lua.rs b/rocks-bin/src/run_lua.rs index 7209c209..7220343e 100644 --- a/rocks-bin/src/run_lua.rs +++ b/rocks-bin/src/run_lua.rs @@ -113,6 +113,8 @@ Options: #[cfg(test)] mod test { + use std::path::PathBuf; + use rocks_lib::config::ConfigBuilder; use super::*; @@ -123,7 +125,12 @@ mod test { args: Some(vec!["-v".into()]), ..RunLua::default() }; - let config = ConfigBuilder::new().build().unwrap(); + let temp: PathBuf = assert_fs::TempDir::new().unwrap().path().into(); + let config = ConfigBuilder::new() + .tree(Some(temp.clone())) + .luarocks_tree(Some(temp)) + .build() + .unwrap(); run_lua(args, config).await.unwrap() } } diff --git a/rocks-lib/Cargo.toml b/rocks-lib/Cargo.toml index 0c1b0432..3c95bc48 100644 --- a/rocks-lib/Cargo.toml +++ b/rocks-lib/Cargo.toml @@ -52,6 +52,7 @@ async-recursion = "1.1.1" shell-words = "1.1.0" shlex = "1.3.0" pkg-config = "0.3.31" +url = "2.5.4" [dev-dependencies] httptest = { version = "0.16.1" } diff --git a/rocks-lib/resources/test/sample-tree/5.1/lock.json b/rocks-lib/resources/test/sample-tree/5.1/lock.json index f1e1d548..6a2f749e 100644 --- a/rocks-lib/resources/test/sample-tree/5.1/lock.json +++ b/rocks-lib/resources/test/sample-tree/5.1/lock.json @@ -9,6 +9,7 @@ "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -20,6 +21,7 @@ "pinned": false, "dependencies": [], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -33,6 +35,7 @@ "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -43,4 +46,4 @@ "d7164c4921869877c7f29fce3f14b8ea78e0aec0d0c36123a3a920a894785ea5", "aa0a5bcd396f3b8e59fa9dd8059a06c3bf4952f48473214d96fc46895edb59f0" ] -} \ No newline at end of file +} diff --git a/rocks-lib/src/build/mod.rs b/rocks-lib/src/build/mod.rs index 819e0c77..8b702e58 100644 --- a/rocks-lib/src/build/mod.rs +++ b/rocks-lib/src/build/mod.rs @@ -8,6 +8,7 @@ use crate::{ operations::{self, FetchSrcRockError}, package::PackageSpec, progress::{Progress, ProgressBar}, + remote_package_source::RemotePackageSource, rockspec::{Build as _, BuildBackendSpec, LuaVersionError, Rockspec}, tree::{RockLayout, Tree}, }; @@ -43,6 +44,7 @@ pub struct Build<'a> { pin: PinnedState, constraint: LockConstraint, behaviour: BuildBehaviour, + source: Option, } impl<'a> Build<'a> { @@ -59,6 +61,7 @@ impl<'a> Build<'a> { pin: PinnedState::default(), constraint: LockConstraint::default(), behaviour: BuildBehaviour::default(), + source: None, } } @@ -77,13 +80,25 @@ impl<'a> Build<'a> { Self { behaviour, ..self } } + /// Sets the remote source of the package to be built. + pub(crate) fn source(self, source: RemotePackageSource) -> Self { + Self { + source: Some(source), + ..self + } + } + /// Builds the package. pub async fn build(self) -> Result { + let source = self.source.unwrap_or_else(|| { + RemotePackageSource::RockspecContent(self.rockspec.raw_content.clone()) + }); build( self.rockspec, self.pin, self.constraint, self.behaviour, + source, self.config, self.progress, ) @@ -252,6 +267,7 @@ async fn build( pinned: PinnedState, constraint: LockConstraint, behaviour: BuildBehaviour, + source: RemotePackageSource, config: &Config, progress: &Progress, ) -> Result { @@ -308,6 +324,7 @@ async fn build( let mut package = LocalPackage::from( &PackageSpec::new(rockspec.package.clone(), rockspec.version.clone()), constraint, + source, hashes, ); package.spec.pinned = pinned; diff --git a/rocks-lib/src/config/mod.rs b/rocks-lib/src/config/mod.rs index 8263a223..70eb28bf 100644 --- a/rocks-lib/src/config/mod.rs +++ b/rocks-lib/src/config/mod.rs @@ -4,6 +4,7 @@ use std::{ collections::HashMap, env, fmt::Display, io, path::PathBuf, str::FromStr, time::Duration, }; use thiserror::Error; +use url::Url; use crate::{ build::{ @@ -145,8 +146,8 @@ pub struct NoValidHomeDirectory; #[derive(Debug, Clone)] pub struct Config { enable_development_rockspecs: bool, - server: String, - extra_servers: Vec, + server: Url, + extra_servers: Vec, only_sources: Option, namespace: String, lua_dir: PathBuf, @@ -197,11 +198,11 @@ impl Config { self.enable_development_rockspecs } - pub fn server(&self) -> &String { + pub fn server(&self) -> &Url { &self.server } - pub fn extra_servers(&self) -> &Vec { + pub fn extra_servers(&self) -> &Vec { self.extra_servers.as_ref() } @@ -285,8 +286,8 @@ pub enum ConfigError { #[derive(Default)] pub struct ConfigBuilder { enable_development_rockspecs: Option, - server: Option, - extra_servers: Option>, + server: Option, + extra_servers: Option>, only_sources: Option, namespace: Option, lua_dir: Option, @@ -317,11 +318,11 @@ impl ConfigBuilder { } } - pub fn server(self, server: Option) -> Self { + pub fn server(self, server: Option) -> Self { Self { server, ..self } } - pub fn extra_servers(self, extra_servers: Option>) -> Self { + pub fn extra_servers(self, extra_servers: Option>) -> Self { Self { extra_servers, ..self @@ -431,7 +432,7 @@ impl ConfigBuilder { enable_development_rockspecs: self.enable_development_rockspecs.unwrap_or(false), server: self .server - .unwrap_or_else(|| "https://luarocks.org/".to_string()), + .unwrap_or_else(|| Url::parse("https://luarocks.org/").unwrap()), extra_servers: self.extra_servers.unwrap_or_default(), only_sources: self.only_sources, namespace: self.namespace.unwrap_or_default(), diff --git a/rocks-lib/src/lib.rs b/rocks-lib/src/lib.rs index 33e29dd8..2b93eb3d 100644 --- a/rocks-lib/src/lib.rs +++ b/rocks-lib/src/lib.rs @@ -15,6 +15,8 @@ pub mod rockspec; pub mod tree; pub mod upload; +pub(crate) mod remote_package_source; + /// An internal string describing the server-side API version that we support. /// Whenever we connect to a server (like `luarocks.org`), we ensure that these /// two versions match (meaning we can safely communicate with the server). diff --git a/rocks-lib/src/lockfile/mod.rs b/rocks-lib/src/lockfile/mod.rs index e8da4c38..d12f8b8b 100644 --- a/rocks-lib/src/lockfile/mod.rs +++ b/rocks-lib/src/lockfile/mod.rs @@ -11,6 +11,7 @@ use thiserror::Error; use crate::package::{ PackageName, PackageReq, PackageSpec, PackageVersion, PackageVersionReq, PackageVersionReqError, }; +use crate::remote_package_source::RemotePackageSource; #[cfg(feature = "lua")] use mlua::{ExternalResult as _, FromLua}; @@ -110,6 +111,13 @@ impl Display for LocalPackageId { } } +#[cfg(feature = "lua")] +impl mlua::IntoLua for LocalPackageId { + fn into_lua(self, lua: &mlua::Lua) -> mlua::Result { + self.0.into_lua(lua) + } +} + impl LocalPackageSpec { pub fn new( name: &PackageName, @@ -176,6 +184,7 @@ impl LocalPackageSpec { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct LocalPackage { pub(crate) spec: LocalPackageSpec, + source: RemotePackageSource, hashes: LocalPackageHashes, } @@ -187,6 +196,7 @@ struct LocalPackageIntermediate { pinned: PinnedState, dependencies: Vec, constraint: Option, + source: RemotePackageSource, hashes: LocalPackageHashes, } @@ -203,6 +213,7 @@ impl TryFrom for LocalPackage { value.dependencies, &value.pinned, ), + source: value.source, hashes: value.hashes, }) } @@ -216,6 +227,7 @@ impl From<&LocalPackage> for LocalPackageIntermediate { pinned: value.spec.pinned, dependencies: value.spec.dependencies.clone(), constraint: value.spec.constraint.clone(), + source: value.source.clone(), hashes: value.hashes.clone(), } } @@ -249,9 +261,10 @@ impl FromLua for LocalPackage { } impl LocalPackage { - pub fn from( + pub(crate) fn from( package: &PackageSpec, constraint: LockConstraint, + source: RemotePackageSource, hashes: LocalPackageHashes, ) -> Self { Self { @@ -262,6 +275,7 @@ impl LocalPackage { Vec::default(), &PinnedState::Unpinned, ), + source, hashes, } } @@ -318,7 +332,7 @@ impl mlua::UserData for LocalPackage { .collect_vec()) }); fields.add_field_method_get("constraint", |_, this| Ok(this.spec.constraint.clone())); - fields.add_field_method_get("id", |_, this| Ok(this.id().0)); + fields.add_field_method_get("id", |_, this| Ok(this.id())); } fn add_methods>(methods: &mut M) { @@ -455,8 +469,12 @@ impl Lockfile { } } - pub fn remove(&mut self, target: &LocalPackage) { - self.rocks.remove(&target.id()); + pub(crate) fn remove(&mut self, target: &LocalPackage) { + self.remove_by_id(&target.id()) + } + + pub(crate) fn remove_by_id(&mut self, target: &LocalPackageId) { + self.rocks.remove(target); } pub fn version(&self) -> &String { @@ -610,6 +628,7 @@ mod tests { let test_local_package = LocalPackage::from( &test_package, crate::lockfile::LockConstraint::Unconstrained, + RemotePackageSource::Test, mock_hashes.clone(), ); lockfile.add(&test_local_package); @@ -619,6 +638,7 @@ mod tests { let mut test_local_dep_package = LocalPackage::from( &test_dep_package, crate::lockfile::LockConstraint::Constrained(">= 1.0.0".parse().unwrap()), + RemotePackageSource::Test, mock_hashes.clone(), ); test_local_dep_package.spec.pinned = PinnedState::Pinned; diff --git a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap index 34033fc8..8cd6c42a 100644 --- a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap +++ b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__add_rocks.snap @@ -1,6 +1,6 @@ --- source: rocks-lib/src/lockfile/mod.rs -assertion_line: 288 +assertion_line: 649 expression: lockfile --- { @@ -12,6 +12,7 @@ expression: lockfile "pinned": false, "dependencies": [], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -25,6 +26,7 @@ expression: lockfile "a9f137d1dad1af603e33935a3f8722dfbb2aebeac03bec5ed0b6e9cc5828c7f3" ], "constraint": null, + "source": "test+foo_bar", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -36,6 +38,7 @@ expression: lockfile "pinned": true, "dependencies": [], "constraint": ">=1.0.0", + "source": "test+foo_bar", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -49,6 +52,7 @@ expression: lockfile "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -62,6 +66,7 @@ expression: lockfile "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" diff --git a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap index 9a039b65..fffc3c6e 100644 --- a/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap +++ b/rocks-lib/src/lockfile/snapshots/rocks_lib__lockfile__tests__parse_lockfile.snap @@ -1,7 +1,8 @@ --- source: rocks-lib/src/lockfile/mod.rs -assertion_line: 244 +assertion_line: 603 expression: lockfile +snapshot_kind: text --- { "version": "1.0.0", @@ -12,6 +13,7 @@ expression: lockfile "pinned": false, "dependencies": [], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -25,6 +27,7 @@ expression: lockfile "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" @@ -38,6 +41,7 @@ expression: lockfile "48ec344951668eca0e0a4ff284d804a11e4e709194df3191a72ed8fac89cf2e0" ], "constraint": null, + "source": "luarocks+https://luarocks.org/", "hashes": { "rockspec": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=", "source": "sha256-uU0nuZNNPgilLlLX2n2r+sSE7+N6U4DukIj3rOLvzek=" diff --git a/rocks-lib/src/manifest/mod.rs b/rocks-lib/src/manifest/mod.rs index d7a87d6f..ad1d84a4 100644 --- a/rocks-lib/src/manifest/mod.rs +++ b/rocks-lib/src/manifest/mod.rs @@ -5,10 +5,12 @@ use std::collections::HashMap; use std::time::SystemTime; use thiserror::Error; use tokio::{fs, io}; +use url::Url; use crate::{ config::{Config, LuaVersion}, package::{PackageName, PackageReq, PackageSpec, PackageVersion, RemotePackage}, + remote_package_source::RemotePackageSource, }; #[derive(Error, Debug)] @@ -24,7 +26,7 @@ pub enum ManifestFromServerError { } async fn manifest_from_server( - url: &str, + url: &Url, config: &Config, ) -> Result { let manifest_filename = "manifest".to_string() @@ -46,7 +48,9 @@ async fn manifest_from_server( })) .map(|s| format!("-{}", s)) .unwrap_or_default(); - let url = url.trim_end_matches('/').to_string() + "/" + &manifest_filename; + let url = url + .join(&manifest_filename) + .expect("could not parse the manifest URL"); // Stores a path to the manifest cache (this allows us to operate on a manifest without // needing to pull it from the luarocks servers each time). @@ -62,7 +66,7 @@ async fn manifest_from_server( let last_modified_local: SystemTime = metadata.modified()?; // Ask the server for the last modified date of its manifest. - let response = client.head(&url).send().await?; + let response = client.head(url.clone()).send().await?; if let Some(last_modified_header) = response.headers().get("Last-Modified") { let server_last_modified = httpdate::parse_http_date(last_modified_header.to_str()?)?; @@ -71,7 +75,7 @@ async fn manifest_from_server( if server_last_modified > last_modified_local { // Since we only pulled in the headers previously we must now request the entire // manifest from scratch. - let new_manifest_content = client.get(&url).send().await?.text().await?; + let new_manifest_content = client.get(url).send().await?.text().await?; fs::write(&cache, &new_manifest_content).await?; return Ok(new_manifest_content); @@ -163,24 +167,24 @@ impl ManifestMetadata { #[derive(Clone)] pub(crate) struct Manifest { - server_url: String, + server_url: Url, metadata: ManifestMetadata, } impl Manifest { - pub fn new(server_url: &str, metadata: ManifestMetadata) -> Self { + pub fn new(server_url: Url, metadata: ManifestMetadata) -> Self { Self { - server_url: server_url.into(), + server_url, metadata, } } - pub async fn from_config(server_url: &str, config: &Config) -> Result { - let manifest = crate::manifest::manifest_from_server(server_url, config).await?; + pub async fn from_config(server_url: Url, config: &Config) -> Result { + let manifest = crate::manifest::manifest_from_server(&server_url, config).await?; let metadata = ManifestMetadata::new(&manifest)?; Ok(Self::new(server_url, metadata)) } - pub fn server_url(&self) -> &String { + pub fn server_url(&self) -> &Url { &self.server_url } pub fn metadata(&self) -> &ManifestMetadata { @@ -190,10 +194,10 @@ impl Manifest { if !self.metadata().has_rock(package_req.name()) { None } else { - Some(RemotePackage { - package: self.metadata().latest_match(package_req).unwrap(), - server_url: self.server_url().into(), - }) + Some(RemotePackage::new( + self.metadata().latest_match(package_req).unwrap(), + RemotePackageSource::LuarocksServer(self.server_url().clone()), + )) } } } @@ -271,7 +275,9 @@ mod tests { .lua_version(Some(crate::config::LuaVersion::LuaJIT)) .build() .unwrap(); - manifest_from_server(&url_str, &config).await.unwrap(); + manifest_from_server(&Url::parse(&url_str).unwrap(), &config) + .await + .unwrap(); } #[tokio::test] @@ -288,7 +294,9 @@ mod tests { .build() .unwrap(); - manifest_from_server(&url_str, &config).await.unwrap(); + manifest_from_server(&Url::parse(&url_str).unwrap(), &config) + .await + .unwrap(); } #[tokio::test] @@ -307,7 +315,9 @@ mod tests { .lua_version(Some(crate::config::LuaVersion::Lua51)) .build() .unwrap(); - let result = manifest_from_server(&url_str, &config).await.unwrap(); + let result = manifest_from_server(&Url::parse(&url_str).unwrap(), &config) + .await + .unwrap(); assert_eq!(result, manifest_content); } diff --git a/rocks-lib/src/operations/download.rs b/rocks-lib/src/operations/download.rs index cbe42007..6bdcb3f7 100644 --- a/rocks-lib/src/operations/download.rs +++ b/rocks-lib/src/operations/download.rs @@ -8,6 +8,7 @@ use crate::{ package::{PackageName, PackageReq, PackageVersion, RemotePackage}, progress::{Progress, ProgressBar}, remote_package_db::{RemotePackageDB, RemotePackageDBError, SearchError}, + remote_package_source::RemotePackageSource, rockspec::{Rockspec, RockspecError}, }; @@ -44,7 +45,7 @@ impl<'a> Download<'a> { } /// Download the package's Rockspec. - pub async fn download_rockspec(self) -> Result { + pub async fn download_rockspec(self) -> Result { match self.package_db { Some(db) => download_rockspec(self.package_req, db, self.progress).await, None => { @@ -100,6 +101,11 @@ pub struct DownloadedSrcRock { pub path: PathBuf, } +pub struct DownloadedRockspec { + pub rockspec: Rockspec, + pub(crate) source: RemotePackageSource, +} + #[derive(Error, Debug)] pub enum DownloadRockspecError { #[error("failed to download rockspec: {0}")] @@ -114,7 +120,7 @@ async fn download_rockspec( package_req: &PackageReq, package_db: &RemotePackageDB, progress: &Progress, -) -> Result { +) -> Result { let package = package_db.find(package_req, progress)?; progress.map(|p| p.set_message(format!("📥 Downloading rockspec for {}", package_req))); download_rockspec_impl(package).await @@ -187,17 +193,30 @@ async fn download_src_rock_to_file( async fn download_rockspec_impl( remote_package: RemotePackage, -) -> Result { - let package = &remote_package.package; - let rockspec_name = format!("{}-{}.rockspec", package.name(), package.version()); - let bytes = reqwest::get(format!("{}/{}", &remote_package.server_url, rockspec_name)) - .await - .map_err(DownloadRockspecError::Request)? - .bytes() - .await - .map_err(DownloadRockspecError::Request)?; - let content = String::from_utf8(bytes.into())?; - Ok(Rockspec::new(&content)?) +) -> Result { + match &remote_package.source { + RemotePackageSource::LuarocksServer(url) => { + let package = &remote_package.package; + let rockspec_name = format!("{}-{}.rockspec", package.name(), package.version()); + let bytes = reqwest::get(format!("{}/{}", &url, rockspec_name)) + .await + .map_err(DownloadRockspecError::Request)? + .bytes() + .await + .map_err(DownloadRockspecError::Request)?; + let content = String::from_utf8(bytes.into())?; + Ok(DownloadedRockspec { + rockspec: Rockspec::new(&content)?, + source: remote_package.source, + }) + } + RemotePackageSource::RockspecContent(content) => Ok(DownloadedRockspec { + rockspec: Rockspec::new(content)?, + source: remote_package.source, + }), + #[cfg(test)] + RemotePackageSource::Test => unimplemented!(), + } } async fn download_src_rock_impl( @@ -206,7 +225,7 @@ async fn download_src_rock_impl( let package = &remote_package.package; let full_rock_name = full_rock_name(package.name(), package.version()); - let bytes = reqwest::get(format!("{}/{}", remote_package.server_url, full_rock_name)) + let bytes = reqwest::get(format!("{}/{}", remote_package.source, full_rock_name)) .await? .bytes() .await?; diff --git a/rocks-lib/src/operations/fetch.rs b/rocks-lib/src/operations/fetch.rs index 98c5cf6c..1a367bdf 100644 --- a/rocks-lib/src/operations/fetch.rs +++ b/rocks-lib/src/operations/fetch.rs @@ -18,6 +18,7 @@ use crate::package::PackageSpec; use crate::package::RemotePackage; use crate::progress::Progress; use crate::progress::ProgressBar; +use crate::remote_package_source::RemotePackageSource; use crate::{rockspec::RockSource, rockspec::RockSourceSpec}; use super::DownloadSrcRockError; @@ -140,7 +141,8 @@ pub async fn fetch_src_rock( config: &Config, progress: &Progress, ) -> Result<(), FetchSrcRockError> { - let remote_package = RemotePackage::new(package.clone(), config.server().clone()); + let source = RemotePackageSource::LuarocksServer(config.server().clone()); + let remote_package = RemotePackage::new(package.clone(), source); let src_rock = operations::download_src_rock(&remote_package, progress).await?; let cursor = Cursor::new(src_rock.bytes); let mime_type = infer::get(cursor.get_ref()).map(|file_type| file_type.mime_type()); diff --git a/rocks-lib/src/operations/install.rs b/rocks-lib/src/operations/install.rs index cc59f365..f1bdc3a0 100644 --- a/rocks-lib/src/operations/install.rs +++ b/rocks-lib/src/operations/install.rs @@ -190,6 +190,7 @@ async fn install_impl( .pin(pin) .constraint(install_spec.spec.constraint()) .behaviour(install_spec.build_behaviour) + .source(install_spec.source) .build() .await .map_err(|err| InstallError::BuildError(package, err))?; diff --git a/rocks-lib/src/operations/pin.rs b/rocks-lib/src/operations/pin.rs index d6b091c9..666dbd0c 100644 --- a/rocks-lib/src/operations/pin.rs +++ b/rocks-lib/src/operations/pin.rs @@ -5,7 +5,7 @@ use itertools::Itertools; use thiserror::Error; use crate::{ - lockfile::{LocalPackage, PinnedState}, + lockfile::{LocalPackageId, PinnedState}, package::PackageSpec, tree::Tree, }; @@ -14,6 +14,8 @@ use crate::{ #[derive(Error, Debug)] pub enum PinError { + #[error("package with ID {0} not found in lockfile")] + PackageNotFound(LocalPackageId), #[error("rock {rock} is already {}pinned!", if *.pin_state == PinnedState::Unpinned { "un" } else { "" })] PinStateUnchanged { pin_state: PinnedState, @@ -31,10 +33,16 @@ pub enum PinError { } pub fn set_pinned_state( - package: &mut LocalPackage, + package_id: &LocalPackageId, tree: &Tree, pin: PinnedState, ) -> Result<(), PinError> { + let mut lockfile = tree.lockfile()?; + let mut package = lockfile + .get(package_id) + .ok_or_else(|| PinError::PackageNotFound(package_id.clone()))? + .clone(); + if pin == package.pinned() { return Err(PinError::PinStateUnchanged { pin_state: package.pinned(), @@ -42,9 +50,8 @@ pub fn set_pinned_state( }); } - let mut lockfile = tree.lockfile()?; let old_package = package.clone(); - let items = std::fs::read_dir(tree.root_for(package))? + let items = std::fs::read_dir(tree.root_for(&package))? .filter_map(Result::ok) .map(|dir| dir.path()) .collect_vec(); @@ -58,14 +65,14 @@ pub fn set_pinned_state( }); } - let new_root = tree.root_for(package); + let new_root = tree.root_for(&package); std::fs::create_dir_all(&new_root)?; fs_extra::move_items(&items, new_root, &CopyOptions::new())?; lockfile.remove(&old_package); - lockfile.add(package); + lockfile.add(&package); lockfile.flush()?; Ok(()) diff --git a/rocks-lib/src/operations/remove.rs b/rocks-lib/src/operations/remove.rs index abcf7738..ef559299 100644 --- a/rocks-lib/src/operations/remove.rs +++ b/rocks-lib/src/operations/remove.rs @@ -2,7 +2,7 @@ use std::io; use std::sync::Arc; use crate::config::{LuaVersion, LuaVersionUnset}; -use crate::lockfile::LocalPackage; +use crate::lockfile::{LocalPackage, LocalPackageId}; use crate::progress::{MultiProgress, Progress, ProgressBar}; use crate::{config::Config, tree::Tree}; use futures::future::join_all; @@ -18,7 +18,7 @@ pub enum RemoveError { pub struct Remove<'a> { config: &'a Config, - packages: Vec, + packages: Vec, progress: Option>>, } @@ -37,7 +37,7 @@ impl<'a> Remove<'a> { /// Add packages to remove. pub fn packages(self, packages: I) -> Self where - I: IntoIterator, + I: IntoIterator, { Self { packages: self.packages.into_iter().chain(packages).collect_vec(), @@ -46,7 +46,7 @@ impl<'a> Remove<'a> { } /// Add a package to the set of packages to remove. - pub fn package(self, package: LocalPackage) -> Self { + pub fn package(self, package: LocalPackageId) -> Self { self.packages(std::iter::once(package)) } @@ -65,41 +65,57 @@ impl<'a> Remove<'a> { Some(p) => p, None => MultiProgress::new_arc(), }; - remove(self.packages, self.config, &Arc::clone(&progress)).await + let tree = Tree::new(self.config.tree().clone(), LuaVersion::from(self.config)?)?; + remove(self.packages, tree, &Arc::clone(&progress)).await } } // TODO: Remove dependencies recursively too! async fn remove( - packages: Vec, - config: &Config, + package_ids: Vec, + tree: Tree, progress: &Progress, ) -> Result<(), RemoveError> { - join_all(packages.into_iter().map(|package| { - let _bar = progress.map(|p| { - p.add(ProgressBar::from(format!( - "🗑️ Removing {}@{}", - package.name(), - package.version() - ))) - }); + let mut lockfile = tree.lockfile()?; + + let packages = package_ids + .iter() + .filter_map(|id| lockfile.get(id)) + .cloned() + .collect_vec(); - let config = config.clone(); + join_all(packages.into_iter().map(|package| { + let bar = progress.map(|p| p.new_bar()); - tokio::spawn(remove_package(package, config)) + let tree = tree.clone(); + tokio::spawn(remove_package(package, tree, bar)) })) .await; + package_ids + .iter() + .for_each(|package| lockfile.remove_by_id(package)); + + lockfile.flush()?; + Ok(()) } -async fn remove_package(package: LocalPackage, config: Config) -> Result<(), RemoveError> { - let tree = Tree::new(config.tree().clone(), LuaVersion::from(&config)?)?; - - tree.lockfile()?.remove(&package); +async fn remove_package( + package: LocalPackage, + tree: Tree, + bar: Progress, +) -> Result<(), RemoveError> { + bar.map(|p| { + p.set_message(format!( + "🗑️ Removing {}@{}", + package.name(), + package.version() + )) + }); - std::fs::remove_dir_all(tree.root_for(&package))?; tokio::fs::remove_dir_all(tree.root_for(&package)).await?; + bar.map(|p| p.finish_and_clear()); Ok(()) } diff --git a/rocks-lib/src/operations/resolve.rs b/rocks-lib/src/operations/resolve.rs index 15c10f24..d5f5090b 100644 --- a/rocks-lib/src/operations/resolve.rs +++ b/rocks-lib/src/operations/resolve.rs @@ -13,6 +13,7 @@ use crate::{ package::{PackageReq, PackageVersionReq}, progress::{MultiProgress, Progress}, remote_package_db::RemotePackageDB, + remote_package_source::RemotePackageSource, rockspec::Rockspec, }; @@ -23,6 +24,7 @@ pub(crate) struct PackageInstallSpec { pub build_behaviour: BuildBehaviour, pub rockspec: Rockspec, pub spec: LocalPackageSpec, + pub source: RemotePackageSource, } #[async_recursion] @@ -52,10 +54,11 @@ pub(crate) async fn get_all_dependencies( tokio::spawn(async move { let bar = progress.map(|p| p.new_bar()); - let rockspec = Download::new(&package, &config, &bar) + let rockspec_download = Download::new(&package, &config, &bar) .package_db(&package_db) .download_rockspec() .await?; + let rockspec = rockspec_download.rockspec; let constraint = if *package.version_req() == PackageVersionReq::SemVer(VersionReq::STAR) { @@ -95,6 +98,7 @@ pub(crate) async fn get_all_dependencies( build_behaviour, spec: local_spec.clone(), rockspec, + source: rockspec_download.source, }; tx.send(install_spec).unwrap(); diff --git a/rocks-lib/src/operations/update.rs b/rocks-lib/src/operations/update.rs index 132a8904..cb88ac25 100644 --- a/rocks-lib/src/operations/update.rs +++ b/rocks-lib/src/operations/update.rs @@ -131,7 +131,7 @@ async fn update( .install() .await?; Remove::new(config) - .packages(packages.into_iter().map(|package| package.0)) + .packages(packages.into_iter().map(|package| package.0.id())) .progress(progress) .remove() .await?; diff --git a/rocks-lib/src/package/mod.rs b/rocks-lib/src/package/mod.rs index 1e22a048..125dba81 100644 --- a/rocks-lib/src/package/mod.rs +++ b/rocks-lib/src/package/mod.rs @@ -12,6 +12,8 @@ pub use version::{ PackageVersion, PackageVersionParseError, PackageVersionReq, PackageVersionReqError, }; +use crate::remote_package_source::RemotePackageSource; + #[derive(Clone, Debug)] #[cfg_attr(feature = "clap", derive(clap::Args))] #[cfg_attr(feature = "lua", derive(mlua::FromLua))] @@ -61,15 +63,12 @@ impl mlua::UserData for PackageSpec { #[derive(Clone, Debug)] pub(crate) struct RemotePackage { pub package: PackageSpec, - pub server_url: String, + pub source: RemotePackageSource, } impl RemotePackage { - pub fn new(package: PackageSpec, server_url: String) -> Self { - Self { - package, - server_url, - } + pub fn new(package: PackageSpec, source: RemotePackageSource) -> Self { + Self { package, source } } } diff --git a/rocks-lib/src/package/outdated.rs b/rocks-lib/src/package/outdated.rs index 7f74fab7..d9627f14 100644 --- a/rocks-lib/src/package/outdated.rs +++ b/rocks-lib/src/package/outdated.rs @@ -68,6 +68,8 @@ impl Display for PackageSpec { mod test { use std::path::PathBuf; + use url::Url; + use crate::{ manifest::{Manifest, ManifestMetadata}, package::PackageSpec, @@ -79,7 +81,7 @@ mod test { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("resources/test/manifest-5.1"); let content = String::from_utf8(std::fs::read(&test_manifest_path).unwrap()).unwrap(); let metadata = ManifestMetadata::new(&content).unwrap(); - let package_db = Manifest::new("example.com", metadata).into(); + let package_db = Manifest::new(Url::parse("https://example.com").unwrap(), metadata).into(); let test_package = PackageSpec::parse("lua-cjson".to_string(), "2.0.0".to_string()).unwrap(); diff --git a/rocks-lib/src/remote_package_db/mod.rs b/rocks-lib/src/remote_package_db/mod.rs index d3df85d4..94a7e822 100644 --- a/rocks-lib/src/remote_package_db/mod.rs +++ b/rocks-lib/src/remote_package_db/mod.rs @@ -30,10 +30,10 @@ impl RemotePackageDB { pub async fn from_config(config: &Config) -> Result { let mut manifests = Vec::new(); for server in config.extra_servers() { - let manifest = Manifest::from_config(server, config).await?; + let manifest = Manifest::from_config(server.clone(), config).await?; manifests.push(manifest); } - manifests.push(Manifest::from_config(config.server(), config).await?); + manifests.push(Manifest::from_config(config.server().clone(), config).await?); Ok(Self(manifests)) } diff --git a/rocks-lib/src/remote_package_source/mod.rs b/rocks-lib/src/remote_package_source/mod.rs new file mode 100644 index 00000000..39b26b20 --- /dev/null +++ b/rocks-lib/src/remote_package_source/mod.rs @@ -0,0 +1,116 @@ +use std::fmt::Display; + +use serde::{de, Deserialize, Deserializer, Serialize}; +use thiserror::Error; +use url::Url; + +const PLUS: &str = "+"; + +// NOTE: We don't want to expose the internals to the API, +// because adding variants would be a breaking change. + +/// The source of a remote package. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] +pub(crate) enum RemotePackageSource { + LuarocksServer(Url), + RockspecContent(String), + #[cfg(test)] + Test, +} + +impl Display for RemotePackageSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self { + RemotePackageSource::LuarocksServer(url) => format!("luarocks{}{}", PLUS, url).fmt(f), + RemotePackageSource::RockspecContent(content) => { + format!("rockspec{}{}", PLUS, content).fmt(f) + } + #[cfg(test)] + RemotePackageSource::Test => "test+foo_bar".fmt(f), + } + } +} + +impl Serialize for RemotePackageSource { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + format!("{}", self).serialize(serializer) + } +} + +#[derive(Error, Debug)] +pub enum RemotePackageSourceError { + #[error("error parsing remote source URL {0}. Missing URL.")] + MissingUrl(String), + #[error("error parsing remote source URL {0}. Expected +.")] + MissingSeparator(String), + #[error("error parsing remote source type {0}. Expected 'luarocks' or 'rockspec'.")] + UnknownRemoteSourceType(String), + #[error(transparent)] + Url(#[from] url::ParseError), +} + +impl TryFrom for RemotePackageSource { + type Error = RemotePackageSourceError; + + fn try_from(value: String) -> Result { + if let Some(pos) = value.find(PLUS) { + if let Some(str) = value.get(pos + 1..) { + let remote_source_type = value[..pos].into(); + match remote_source_type { + "luarocks" => Ok(RemotePackageSource::LuarocksServer(Url::parse(str)?)), + "rockspec" => Ok(RemotePackageSource::RockspecContent(str.into())), + _ => Err(RemotePackageSourceError::UnknownRemoteSourceType( + remote_source_type.into(), + )), + } + } else { + Err(RemotePackageSourceError::MissingUrl(value)) + } + } else { + Err(RemotePackageSourceError::MissingSeparator(value)) + } + } +} + +impl<'de> Deserialize<'de> for RemotePackageSource { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let value = String::deserialize(deserializer)?; + Self::try_from(value).map_err(de::Error::custom) + } +} + +#[cfg(test)] +mod test { + use super::*; + + const LUAROCKS_ROCKSPEC: &str = " +rockspec_format = \"3.0\" +package = 'luarocks' +version = '3.11.1-1' +source = { + url = 'git+https://github.com/luarocks/luarocks', + tag = 'v3.11.1' +} +"; + + #[test] + fn luarocks_source_roundtrip() { + let source = + RemotePackageSource::LuarocksServer(Url::parse("https://luarocks.org/").unwrap()); + let roundtripped = RemotePackageSource::try_from(format!("{}", source)).unwrap(); + assert_eq!(source, roundtripped) + } + + #[test] + fn rockspec_source_roundtrip() { + let source = RemotePackageSource::RockspecContent(LUAROCKS_ROCKSPEC.into()); + let roundtripped = RemotePackageSource::try_from(format!("{}", source)).unwrap(); + assert_eq!(source, roundtripped) + } +} diff --git a/rocks-lib/src/tree/mod.rs b/rocks-lib/src/tree/mod.rs index 25b80074..deceb706 100644 --- a/rocks-lib/src/tree/mod.rs +++ b/rocks-lib/src/tree/mod.rs @@ -4,7 +4,7 @@ use crate::{ variables::{self, HasVariables}, }, config::LuaVersion, - lockfile::{LocalPackage, Lockfile}, + lockfile::{LocalPackage, LocalPackageId, Lockfile}, package::PackageReq, }; use std::{io, path::PathBuf}; @@ -138,7 +138,7 @@ impl Tree { .iter() .rev() .filter(|package| req.version_req().matches(package.version())) - .cloned() + .map(|package| package.id()) .collect_vec(); Ok(match found_packages.len() { @@ -163,7 +163,7 @@ impl Tree { .filter(|package| { req.version_req().matches(package.version()) && filter(package) }) - .cloned() + .map(|package| package.id()) .collect_vec(); Ok(match found_packages.len() { @@ -255,8 +255,8 @@ impl mlua::UserData for Tree { #[derive(Clone, Debug)] pub enum RockMatches { NotFound(PackageReq), - Single(LocalPackage), - Many(Vec), + Single(LocalPackageId), + Many(Vec), } // Loosely mimic the Option functions. @@ -326,6 +326,7 @@ mod tests { config::LuaVersion, lockfile::{LocalPackage, LocalPackageHashes, LockConstraint}, package::{PackageName, PackageSpec, PackageVersion}, + remote_package_source::RemotePackageSource, tree::RockLayout, }; @@ -354,6 +355,7 @@ mod tests { let package = LocalPackage::from( &PackageSpec::parse("neorg".into(), "8.0.0-1".into()).unwrap(), LockConstraint::Unconstrained, + RemotePackageSource::Test, mock_hashes.clone(), ); @@ -377,6 +379,7 @@ mod tests { let package = LocalPackage::from( &PackageSpec::parse("lua-cjson".into(), "2.1.0-1".into()).unwrap(), LockConstraint::Unconstrained, + RemotePackageSource::Test, mock_hashes.clone(), ); @@ -452,6 +455,7 @@ mod tests { .rock(&LocalPackage::from( &PackageSpec::parse("neorg".into(), "8.0.0-1-1".into()).unwrap(), LockConstraint::Unconstrained, + RemotePackageSource::Test, mock_hashes.clone(), )) .unwrap(); diff --git a/rocks-lib/src/upload/mod.rs b/rocks-lib/src/upload/mod.rs index 15b3c5b9..9e062712 100644 --- a/rocks-lib/src/upload/mod.rs +++ b/rocks-lib/src/upload/mod.rs @@ -11,6 +11,7 @@ use reqwest::{ use serde::Deserialize; use serde_enum_str::Serialize_enum_str; use thiserror::Error; +use url::Url; /// A rocks package uploader, providing fine-grained control /// over how a package should be uploaded. @@ -62,6 +63,8 @@ pub struct VersionCheckResponse { #[derive(Error, Debug)] pub enum ToolCheckError { + #[error("error parsing tool check URL: {0}")] + ParseError(#[from] url::ParseError), #[error(transparent)] Request(#[from] reqwest::Error), #[error("`rocks` is out of date with {0}'s expected tool version! `rocks` is at version {TOOL_VERSION}, server is at {server_version}", server_version = _1.version)] @@ -70,6 +73,8 @@ pub enum ToolCheckError { #[derive(Error, Debug)] pub enum UserCheckError { + #[error("error parsing user check URL: {0}")] + ParseError(#[from] url::ParseError), #[error(transparent)] Request(#[from] reqwest::Error), #[error("invalid API key provided")] @@ -78,16 +83,23 @@ pub enum UserCheckError { #[derive(Error, Debug)] #[error("could not check rock status on server: {0}")] -pub struct RockCheckError(#[from] reqwest::Error); +pub enum RockCheckError { + #[error(transparent)] + ParseError(#[from] url::ParseError), + #[error(transparent)] + Request(#[from] reqwest::Error), +} #[derive(Error, Debug)] #[error(transparent)] pub enum UploadError { + #[error("error parsing upload URL: {0}")] + ParseError(#[from] url::ParseError), Lua(#[from] mlua::Error), Request(#[from] reqwest::Error), RockCheck(#[from] RockCheckError), #[error("rock already exists on server: {0}")] - RockExists(String), + RockExists(Url), #[error("unable to read rockspec: {0}")] RockspecRead(#[from] std::io::Error), #[error("{0}.\nHINT: If you'd like to skip the signing step supply `--sign-protocol none` to the CLI")] @@ -231,7 +243,7 @@ async fn upload_from_project( }; client - .post(helpers::url_for_method(config.server(), api_key, "upload")) + .post(helpers::url_for_method(config.server(), api_key, "upload")?) .multipart(multipart) .send() .await?; @@ -245,23 +257,28 @@ mod helpers { use crate::upload::RockCheckError; use crate::upload::{ToolCheckError, UserCheckError}; use reqwest::Client; + use url::Url; - pub(crate) fn url_for_method(server: &str, api_key: &ApiKey, endpoint: &str) -> String { - format!( - "{}{}/{}/{}", - server, - "api/1", - unsafe { api_key.get() }, - endpoint - ) + pub(crate) fn url_for_method( + server_url: &Url, + api_key: &ApiKey, + endpoint: &str, + ) -> Result { + let api_key = unsafe { api_key.get() }; + server_url + .join("api/1") + .expect("error constructing 'api/1' path") + .join(api_key)? + .join(endpoint) } pub(crate) async fn ensure_tool_version( client: &Client, - server: &str, + server_url: &Url, ) -> Result<(), ToolCheckError> { + let url = server_url.join("api/tool_version")?; let response: VersionCheckResponse = client - .post(format!("{}{}", server, "api/tool_version")) + .post(url) .json(&("current", TOOL_VERSION)) .send() .await? @@ -271,17 +288,20 @@ mod helpers { if response.version == TOOL_VERSION { Ok(()) } else { - Err(ToolCheckError::ToolOutdated(server.to_string(), response)) + Err(ToolCheckError::ToolOutdated( + server_url.to_string(), + response, + )) } } pub(crate) async fn ensure_user_exists( client: &Client, api_key: &ApiKey, - server: &str, + server_url: &Url, ) -> Result<(), UserCheckError> { client - .get(url_for_method(server, api_key, "status")) + .get(url_for_method(server_url, api_key, "status")?) .send() .await? .error_for_status()?; @@ -294,10 +314,10 @@ mod helpers { api_key: &ApiKey, name: &PackageName, version: &PackageVersion, - server: &str, + server: &Url, ) -> Result { Ok(client - .get(url_for_method(server, api_key, "check_rockspec")) + .get(url_for_method(server, api_key, "check_rockspec")?) .query(&( ("package", name.to_string()), ("version", version.to_string()), diff --git a/rocks-lib/tests/luarocks_installation.rs b/rocks-lib/tests/luarocks_installation.rs index 646c42f3..d3682c11 100644 --- a/rocks-lib/tests/luarocks_installation.rs +++ b/rocks-lib/tests/luarocks_installation.rs @@ -16,6 +16,7 @@ async fn luarocks_make() { let dir = TempDir::new().unwrap(); let config = ConfigBuilder::new() .tree(Some(dir.path().into())) + .luarocks_tree(Some(TempDir::new().unwrap().path().into())) .build() .unwrap(); let luarocks = LuaRocksInstallation::new(&config).unwrap();