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

feature(cli): add content add-redirect #21

Merged
merged 21 commits into from
Oct 15, 2024
Merged
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
11 changes: 11 additions & 0 deletions crates/rari-cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use rari_doc::pages::types::doc::Doc;
use rari_doc::reader::read_docs_parallel;
use rari_doc::search_index::build_search_index;
use rari_doc::utils::TEMPL_RECORDER_SENDER;
use rari_tools::add_redirect::add_redirect;
use rari_tools::history::gather_history;
use rari_tools::popularities::update_popularities;
use rari_tools::r#move::r#move;
Expand Down Expand Up @@ -66,6 +67,7 @@ enum ContentSubcommand {
/// Moves content from one slug to another
Move(MoveArgs),
Delete(DeleteArgs),
AddRedirect(AddRedirectArgs),
}

#[derive(Args)]
Expand All @@ -89,6 +91,12 @@ struct DeleteArgs {
assume_yes: bool,
}

#[derive(Args)]
struct AddRedirectArgs {
from_url: String,
to_url: String,
}

#[derive(Args)]
struct UpdateArgs {
#[arg(long)]
Expand Down Expand Up @@ -357,6 +365,9 @@ fn main() -> Result<(), Error> {
args.assume_yes,
)?;
}
ContentSubcommand::AddRedirect(args) => {
add_redirect(&args.from_url, &args.to_url)?;
}
},
Commands::Update(args) => update(args.version)?,
}
Expand Down
181 changes: 181 additions & 0 deletions crates/rari-tools/src/add_redirect.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::borrow::Cow;

use console::Style;
use rari_doc::resolve::{url_meta_from, UrlMeta};
use rari_types::locale::Locale;

use crate::error::ToolError;
use crate::redirects::add_redirects;

pub fn add_redirect(from_url: &str, to_url: &str) -> Result<(), ToolError> {
do_add_redirect(from_url, to_url)?;

let green = Style::new().green();
let bold = Style::new().bold();

println!(
"{} {} {} {}",
green.apply_to("Saved"),
bold.apply_to(from_url),
green.apply_to("→"),
bold.apply_to(to_url),
);

Ok(())
}

fn do_add_redirect(from_url: &str, to_url: &str) -> Result<(), ToolError> {
validate_args(from_url, to_url)?;
let UrlMeta {
locale: from_locale,
..
} = url_meta_from(from_url)?;
if !to_url.starts_with("http") {
if from_url == to_url {
return Err(ToolError::InvalidRedirectToURL(format!(
"redirect url is the same as the from url: {to_url}"
)));
}

let UrlMeta {
locale: to_locale, ..
} = url_meta_from(to_url)?;

// Enforce that the locales match or the target is in the default Locale
if from_locale != to_locale && to_locale != Locale::EnUs {
return Err(ToolError::InvalidRedirectToURL(format!(
"redirect url locales do not match: {from_locale} != {to_locale}"
)));
}
}
add_redirects(from_locale, &[(from_url.to_owned(), to_url.to_owned())])?;
Ok(())
}

fn validate_args(from_url: &str, to_url: &str) -> Result<(), ToolError> {
if from_url.is_empty() {
return Err(ToolError::InvalidUrl(Cow::Borrowed(
"from_url cannot be empty",
)));
}
if to_url.is_empty() {
return Err(ToolError::InvalidUrl(Cow::Borrowed(
"to_url cannot be empty",
)));
}
if from_url.contains("#") {
return Err(ToolError::InvalidUrl(Cow::Borrowed(
"from_url cannot contain '#'",
)));
}
Ok(())
}

// These tests use file system fixtures to simulate content and translated content.
// The file system is a shared resource, so we force tests to be run serially,
// to avoid concurrent fixture management issues.
// Using `file_serial` as a synchonization lock, we run all tests using
// the same `key` (here: file_fixtures) to be serialized across modules.
#[cfg(test)]
use serial_test::file_serial;
#[cfg(test)]
#[file_serial(file_fixtures)]
mod test {
use rari_types::locale::Locale;

use super::*;
use crate::tests::fixtures::docs::DocFixtures;
use crate::tests::fixtures::redirects::RedirectFixtures;
use crate::utils::test_utils::get_redirects_map;

#[test]
fn test_add_redirect() {
let slugs = vec![
"Web/API/ExampleOne".to_string(),
"Web/API/SomethingElse".to_string(),
];
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
let _redirects = RedirectFixtures::new(
&vec![(
"docs/Web/API/Something".to_string(),
"docs/Web/API/SomethingElse".to_string(),
)],
Locale::EnUs,
);

let result = do_add_redirect(
"/en-US/docs/Web/API/ExampleGone",
"/en-US/docs/Web/API/ExampleOne",
);
assert!(result.is_ok());

let redirects = get_redirects_map(Locale::EnUs);
assert_eq!(redirects.len(), 2);
assert!(redirects.contains_key("/en-US/docs/Web/API/ExampleGone"));
assert_eq!(
redirects.get("/en-US/docs/Web/API/ExampleGone").unwrap(),
"/en-US/docs/Web/API/ExampleOne"
);
}

#[test]
fn test_add_redirect_missing_target() {
let slugs = vec!["Web/API/ExampleOne".to_string()];
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);

let result = do_add_redirect(
"/en-US/docs/Web/API/ExampleGone",
"/en-US/docs/Web/API/ExampleMissing",
);
assert!(result.is_err());
}

#[test]
fn test_add_redirect_differing_locales() {
let slugs = vec!["Web/API/ExampleOne".to_string()];
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
let _docs_pt = DocFixtures::new(&slugs, Locale::PtBr);
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);
let _redirects_pt = RedirectFixtures::new(&vec![], Locale::PtBr);

// Locales do not match
let result = do_add_redirect(
"/en-US/docs/Web/API/ExampleGone",
"/pt-BR/docs/Web/API/ExampleOne",
);
assert!(result.is_err());
assert!(matches!(result, Err(ToolError::InvalidRedirectToURL(_))));

// Target is en-US, even if locales differ
let result = do_add_redirect(
"/pt-BR/docs/Web/API/ExampleGone",
"/en-US/docs/Web/API/ExampleOne",
);
assert!(result.is_ok());

let redirects = get_redirects_map(Locale::PtBr);
assert!(redirects.contains_key("/pt-BR/docs/Web/API/ExampleGone"));
assert_eq!(
redirects.get("/pt-BR/docs/Web/API/ExampleGone").unwrap(),
"/en-US/docs/Web/API/ExampleOne"
);
}

#[test]
fn test_add_redirect_external() {
let slugs = vec!["Web/API/ExampleOne".to_string()];
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);

let result = do_add_redirect("/en-US/docs/Web/API/ExampleGone", "https://example.com/");
assert!(result.is_ok());

let redirects = get_redirects_map(Locale::EnUs);
assert!(redirects.contains_key("/en-US/docs/Web/API/ExampleGone"));
assert_eq!(
redirects.get("/en-US/docs/Web/API/ExampleGone").unwrap(),
"https://example.com/"
);
}
}
2 changes: 2 additions & 0 deletions crates/rari-tools/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use thiserror::Error;
pub enum ToolError {
#[error("Invalid slug: {0}")]
InvalidSlug(Cow<'static, str>),
#[error("Invalid url: {0}")]
InvalidUrl(Cow<'static, str>),
#[error("Git error: {0}")]
GitError(String),

Expand Down
1 change: 1 addition & 0 deletions crates/rari-tools/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod add_redirect;
pub mod error;
pub mod git;
pub mod history;
Expand Down
5 changes: 3 additions & 2 deletions crates/rari-tools/src/move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ mod test {
"Web/API/ExampleOne".to_string(),
"Web/API/ExampleOne/SubExampleOne".to_string(),
"Web/API/ExampleOne/SubExampleTwo".to_string(),
"Web/API/SomethingElse".to_string(),
];
let redirects = vec![
(
Expand Down Expand Up @@ -353,7 +354,7 @@ mod test {
Locale::EnUs,
false,
);

println!("result: {:?}", result);
assert!(result.is_ok());
let result = result.unwrap();
assert!(result.len() == 3);
Expand Down Expand Up @@ -428,6 +429,7 @@ mod test {
"Web/API/ExampleOne".to_string(),
"Web/API/ExampleOne/SubExampleOne".to_string(),
"Web/API/ExampleOne/SubExampleTwo".to_string(),
"Web/API/SomethingElse".to_string(),
];
let redirects = vec![
(
Expand Down Expand Up @@ -463,7 +465,6 @@ mod test {
Locale::PtBr,
false,
);

assert!(result.is_ok());
let result = result.unwrap();
assert!(result.len() == 3);
Expand Down
Loading