Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for the sparse-index RFC #158

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ crates/*/target
# file for saving processes from `persist`
/persist-dump.json

# miscellaneous ignored folder
/ignored

# alexandrie local testing folders
/app-data
/crate-index
Expand Down
6 changes: 5 additions & 1 deletion crates/alexandrie-index/src/index/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use semver::{Version, VersionReq};

use crate::error::Error;
use crate::tree::Tree;
use crate::{CrateVersion, Indexer};
use crate::{ConfigFile, CrateVersion, Indexer};

/// The 'command-line' crate index management strategy type.
///
Expand Down Expand Up @@ -39,6 +39,10 @@ impl Indexer for CommandLineIndex {
self.repo.commit_and_push(msg)
}

fn configuration(&self) -> Result<ConfigFile, Error> {
self.tree.configuration()
}

fn match_record(&self, name: &str, req: VersionReq) -> Result<CrateVersion, Error> {
self.tree.match_record(name, req)
}
Expand Down
6 changes: 5 additions & 1 deletion crates/alexandrie-index/src/index/git2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use semver::{Version, VersionReq};

use crate::error::Error;
use crate::tree::Tree;
use crate::{CrateVersion, Indexer};
use crate::{ConfigFile, CrateVersion, Indexer};

/// The 'git2' crate index management strategy type.
///
Expand Down Expand Up @@ -135,6 +135,10 @@ impl Indexer for Git2Index {
Ok(())
}

fn configuration(&self) -> Result<ConfigFile, Error> {
self.tree.configuration()
}

fn match_record(&self, name: &str, req: VersionReq) -> Result<CrateVersion, Error> {
self.tree.match_record(name, req)
}
Expand Down
31 changes: 27 additions & 4 deletions crates/alexandrie-index/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use semver::{Version, VersionReq};
use serde::{Deserialize, Serialize};

pub mod config;
pub mod error;
Expand All @@ -7,8 +8,8 @@ mod index;
mod models;
mod tree;

pub use index::*;
pub use models::{CrateDependency, CrateDependencyKind, CrateVersion};
pub use crate::index::*;
pub use crate::models::{CrateDependency, CrateDependencyKind, CrateVersion};

use crate::cli::CommandLineIndex;
use crate::error::Error;
Expand All @@ -28,20 +29,34 @@ pub enum Index {
Git2(Git2Index),
}

/// Represents the index's configuration file.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct ConfigFile {
/// The download link format for crates.
pub dl: String,
/// The base URL where the API for this index's registry can be found.
pub api: String,
/// The URLs to the other registries that crates in this index are allowed to have dependencies on.
pub allowed_registries: Vec<String>,
}

/// The required trait that any crate index management type must implement.
pub trait Indexer {
/// Gives back the URL of the managed crate index.
fn url(&self) -> Result<String, Error>;
/// Refreshes the managed crate index (in case another instance made modification to it).
fn refresh(&self) -> Result<(), Error>;
/// Commits and pushes changes upstream.
fn commit_and_push(&self, msg: &str) -> Result<(), Error>;
/// Retrieves the index's current configuration file.
fn configuration(&self) -> Result<ConfigFile, Error>;
/// Retrieves all the version records of a crate.
fn all_records(&self, name: &str) -> Result<Vec<CrateVersion>, Error>;
/// Retrieves the latest version record of a crate.
fn latest_record(&self, name: &str) -> Result<CrateVersion, Error>;
/// Retrieves the latest crate version record that matches the given name and version requirement.
fn match_record(&self, name: &str, req: VersionReq) -> Result<CrateVersion, Error>;
/// Commits and pushes changes upstream.
fn commit_and_push(&self, msg: &str) -> Result<(), Error>;
/// Adds a new crate record into the index.
fn add_record(&self, record: CrateVersion) -> Result<(), Error>;
/// Alters an index's crate version record with the passed-in function.
Expand Down Expand Up @@ -83,6 +98,14 @@ impl Indexer for Index {
}
}

fn configuration(&self) -> Result<ConfigFile, Error> {
match self {
Index::CommandLine(idx) => idx.configuration(),
#[cfg(feature = "git2")]
Index::Git2(idx) => idx.configuration(),
}
}

fn all_records(&self, name: &str) -> Result<Vec<CrateVersion>, Error> {
match self {
Index::CommandLine(idx) => idx.all_records(name),
Expand Down
21 changes: 12 additions & 9 deletions crates/alexandrie-index/src/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use semver::{Version, VersionReq};

use crate::error::IndexError;
use crate::models::CrateVersion;
use crate::Error;
use crate::{ConfigFile, Error};

#[derive(Debug, Clone, PartialEq)]
pub struct Tree {
Expand All @@ -28,6 +28,13 @@ impl Tree {
}
}

pub fn configuration(&self) -> Result<ConfigFile, Error> {
let path = self.path.join("config.json");
let bytes = fs::read(&path)?;
let config = json::from_slice(&bytes)?;
Ok(config)
}

pub fn match_record(&self, name: &str, req: VersionReq) -> Result<CrateVersion, Error> {
let path = self.compute_record_path(name);
let file = fs::File::open(path).map_err(|err| match err.kind() {
Expand Down Expand Up @@ -96,14 +103,10 @@ impl Tree {
}),
_ => Error::from(err),
})?;
let mut krates: Vec<CrateVersion> = {
let mut out = Vec::new();
for line in io::BufReader::new(file).lines() {
let krate = json::from_str(line?.as_str())?;
out.push(krate);
}
out
};
let mut krates: Vec<CrateVersion> = io::BufReader::new(file)
.lines()
.map(|line| Ok(json::from_str(line?.as_str())?))
.collect::<Result<_, Error>>()?;
let found = krates
.iter_mut()
.find(|krate| krate.vers == version)
Expand Down
2 changes: 2 additions & 0 deletions crates/alexandrie/src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ pub mod account;
pub mod categories;
/// Crate-related endpoints (eg. "/api/v1/crates/*").
pub mod crates;
/// Sparse index endpoints (eg. "/api/v1/sparse/se/rd/serde").
pub mod sparse;
62 changes: 62 additions & 0 deletions crates/alexandrie/src/api/sparse/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use std::sync::Arc;

use axum::extract::{Path, State};
use axum::Json;
use serde::{Deserialize, Serialize};

use alexandrie_index::{ConfigFile, Indexer};

use crate::config::AppState;
use crate::error::ApiError;

/// Response body for this route.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PathParams {
/// The crate's name.
pub fst: String,
/// The crate's description.
pub snd: String,
/// The crate's repository link.
#[serde(rename = "crate")]
pub krate: Option<String>,
}

/// Route to sparsly access index entries for a crate.
pub async fn get(
State(state): State<Arc<AppState>>,
Path(params): Path<PathParams>,
) -> Result<String, ApiError> {
let (fst, snd, krate) = match params.krate.as_deref() {
Some(krate) => (params.fst.as_str(), Some(params.snd.as_str()), krate),
None => (params.fst.as_str(), None, params.snd.as_str()),
};

let maybe_expected = match krate.len() {
0 => None,
1 => Some(("1", None)),
2 => Some(("2", None)),
3 => Some(("3", Some(&krate[..1]))),
_ => Some((&krate[..2], Some(&krate[2..4]))),
};

if maybe_expected.map_or(true, |it| it != (fst, snd)) {
return Err(ApiError::msg("the crate could not be found"));
}

let records = state.index.all_records(krate)?;

let mut output = String::default();
for record in records {
let record = json::to_string(&record)?;
output.push_str(&record);
output.push('\n');
}

Ok(output)
}

/// Route to sparsly access the index's configuration.
pub async fn get_config(State(state): State<Arc<AppState>>) -> Result<Json<ConfigFile>, ApiError> {
let config = state.index.configuration()?;
Ok(Json(config))
}
7 changes: 5 additions & 2 deletions crates/alexandrie/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ use clap::Parser;
use diesel_migrations::MigrationHarness;
use tower_http::trace::{self, TraceLayer};
use tracing::Level;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::EnvFilter;

#[cfg(feature = "frontend")]
use axum::http::StatusCode;
Expand All @@ -40,8 +42,6 @@ use tower_http::services::ServeDir;
use tower_sessions::cookie::SameSite;
#[cfg(feature = "frontend")]
use tower_sessions::SessionManagerLayer;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::EnvFilter;

/// API endpoints definitions.
pub mod api;
Expand Down Expand Up @@ -185,6 +185,9 @@ fn api_routes() -> Router<Arc<AppState>> {
"/crates/:name/:version/download",
get(api::crates::download::get),
)
.route("/sparse/:fst/:snd/:crate", get(api::sparse::get))
.route("/sparse/:fst/:snd", get(api::sparse::get))
.route("/sparse/config.json", get(api::sparse::get_config))
}

#[derive(Debug, Parser)]
Expand Down
Loading