Skip to content

Commit df26184

Browse files
arglfiji-flo
andauthored
feature(cli): add content add-redirect (#21)
* move 'assume yes' cli option under individual content commands, added cli delete/remove option, scaffolded functionality for this * make cache settings of get_subfolders configurable * CLI info + logic * refactored wiki history management, more tests * fix history * utils refactored, redirects handling, tests * added dangling references check to delete * cleanup * move git code and nits * fix history * nit * added add-redirect command * depend on redirects module for some validation, fixes, tests * added console output, cli integration * fixed a bug in redirect logic, added some tests, make locale always pass by value in a few cases * removed early return for differing locales on target url validation, removed commented code --------- Co-authored-by: Florian Dieminger <me@fiji-flo.de>
1 parent 309f6bf commit df26184

File tree

6 files changed

+274
-53
lines changed

6 files changed

+274
-53
lines changed

crates/rari-cli/main.rs

+11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use rari_doc::pages::types::doc::Doc;
1818
use rari_doc::reader::read_docs_parallel;
1919
use rari_doc::search_index::build_search_index;
2020
use rari_doc::utils::TEMPL_RECORDER_SENDER;
21+
use rari_tools::add_redirect::add_redirect;
2122
use rari_tools::history::gather_history;
2223
use rari_tools::popularities::update_popularities;
2324
use rari_tools::r#move::r#move;
@@ -66,6 +67,7 @@ enum ContentSubcommand {
6667
/// Moves content from one slug to another
6768
Move(MoveArgs),
6869
Delete(DeleteArgs),
70+
AddRedirect(AddRedirectArgs),
6971
}
7072

7173
#[derive(Args)]
@@ -89,6 +91,12 @@ struct DeleteArgs {
8991
assume_yes: bool,
9092
}
9193

94+
#[derive(Args)]
95+
struct AddRedirectArgs {
96+
from_url: String,
97+
to_url: String,
98+
}
99+
92100
#[derive(Args)]
93101
struct UpdateArgs {
94102
#[arg(long)]
@@ -357,6 +365,9 @@ fn main() -> Result<(), Error> {
357365
args.assume_yes,
358366
)?;
359367
}
368+
ContentSubcommand::AddRedirect(args) => {
369+
add_redirect(&args.from_url, &args.to_url)?;
370+
}
360371
},
361372
Commands::Update(args) => update(args.version)?,
362373
}

crates/rari-tools/src/add_redirect.rs

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use std::borrow::Cow;
2+
3+
use console::Style;
4+
use rari_doc::resolve::{url_meta_from, UrlMeta};
5+
use rari_types::locale::Locale;
6+
7+
use crate::error::ToolError;
8+
use crate::redirects::add_redirects;
9+
10+
pub fn add_redirect(from_url: &str, to_url: &str) -> Result<(), ToolError> {
11+
do_add_redirect(from_url, to_url)?;
12+
13+
let green = Style::new().green();
14+
let bold = Style::new().bold();
15+
16+
println!(
17+
"{} {} {} {}",
18+
green.apply_to("Saved"),
19+
bold.apply_to(from_url),
20+
green.apply_to("→"),
21+
bold.apply_to(to_url),
22+
);
23+
24+
Ok(())
25+
}
26+
27+
fn do_add_redirect(from_url: &str, to_url: &str) -> Result<(), ToolError> {
28+
validate_args(from_url, to_url)?;
29+
let UrlMeta {
30+
locale: from_locale,
31+
..
32+
} = url_meta_from(from_url)?;
33+
if !to_url.starts_with("http") {
34+
if from_url == to_url {
35+
return Err(ToolError::InvalidRedirectToURL(format!(
36+
"redirect url is the same as the from url: {to_url}"
37+
)));
38+
}
39+
40+
let UrlMeta {
41+
locale: to_locale, ..
42+
} = url_meta_from(to_url)?;
43+
44+
// Enforce that the locales match or the target is in the default Locale
45+
if from_locale != to_locale && to_locale != Locale::EnUs {
46+
return Err(ToolError::InvalidRedirectToURL(format!(
47+
"redirect url locales do not match: {from_locale} != {to_locale}"
48+
)));
49+
}
50+
}
51+
add_redirects(from_locale, &[(from_url.to_owned(), to_url.to_owned())])?;
52+
Ok(())
53+
}
54+
55+
fn validate_args(from_url: &str, to_url: &str) -> Result<(), ToolError> {
56+
if from_url.is_empty() {
57+
return Err(ToolError::InvalidUrl(Cow::Borrowed(
58+
"from_url cannot be empty",
59+
)));
60+
}
61+
if to_url.is_empty() {
62+
return Err(ToolError::InvalidUrl(Cow::Borrowed(
63+
"to_url cannot be empty",
64+
)));
65+
}
66+
if from_url.contains("#") {
67+
return Err(ToolError::InvalidUrl(Cow::Borrowed(
68+
"from_url cannot contain '#'",
69+
)));
70+
}
71+
Ok(())
72+
}
73+
74+
// These tests use file system fixtures to simulate content and translated content.
75+
// The file system is a shared resource, so we force tests to be run serially,
76+
// to avoid concurrent fixture management issues.
77+
// Using `file_serial` as a synchonization lock, we run all tests using
78+
// the same `key` (here: file_fixtures) to be serialized across modules.
79+
#[cfg(test)]
80+
use serial_test::file_serial;
81+
#[cfg(test)]
82+
#[file_serial(file_fixtures)]
83+
mod test {
84+
use rari_types::locale::Locale;
85+
86+
use super::*;
87+
use crate::tests::fixtures::docs::DocFixtures;
88+
use crate::tests::fixtures::redirects::RedirectFixtures;
89+
use crate::utils::test_utils::get_redirects_map;
90+
91+
#[test]
92+
fn test_add_redirect() {
93+
let slugs = vec![
94+
"Web/API/ExampleOne".to_string(),
95+
"Web/API/SomethingElse".to_string(),
96+
];
97+
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
98+
let _redirects = RedirectFixtures::new(
99+
&vec![(
100+
"docs/Web/API/Something".to_string(),
101+
"docs/Web/API/SomethingElse".to_string(),
102+
)],
103+
Locale::EnUs,
104+
);
105+
106+
let result = do_add_redirect(
107+
"/en-US/docs/Web/API/ExampleGone",
108+
"/en-US/docs/Web/API/ExampleOne",
109+
);
110+
assert!(result.is_ok());
111+
112+
let redirects = get_redirects_map(Locale::EnUs);
113+
assert_eq!(redirects.len(), 2);
114+
assert!(redirects.contains_key("/en-US/docs/Web/API/ExampleGone"));
115+
assert_eq!(
116+
redirects.get("/en-US/docs/Web/API/ExampleGone").unwrap(),
117+
"/en-US/docs/Web/API/ExampleOne"
118+
);
119+
}
120+
121+
#[test]
122+
fn test_add_redirect_missing_target() {
123+
let slugs = vec!["Web/API/ExampleOne".to_string()];
124+
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
125+
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);
126+
127+
let result = do_add_redirect(
128+
"/en-US/docs/Web/API/ExampleGone",
129+
"/en-US/docs/Web/API/ExampleMissing",
130+
);
131+
assert!(result.is_err());
132+
}
133+
134+
#[test]
135+
fn test_add_redirect_differing_locales() {
136+
let slugs = vec!["Web/API/ExampleOne".to_string()];
137+
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
138+
let _docs_pt = DocFixtures::new(&slugs, Locale::PtBr);
139+
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);
140+
let _redirects_pt = RedirectFixtures::new(&vec![], Locale::PtBr);
141+
142+
// Locales do not match
143+
let result = do_add_redirect(
144+
"/en-US/docs/Web/API/ExampleGone",
145+
"/pt-BR/docs/Web/API/ExampleOne",
146+
);
147+
assert!(result.is_err());
148+
assert!(matches!(result, Err(ToolError::InvalidRedirectToURL(_))));
149+
150+
// Target is en-US, even if locales differ
151+
let result = do_add_redirect(
152+
"/pt-BR/docs/Web/API/ExampleGone",
153+
"/en-US/docs/Web/API/ExampleOne",
154+
);
155+
assert!(result.is_ok());
156+
157+
let redirects = get_redirects_map(Locale::PtBr);
158+
assert!(redirects.contains_key("/pt-BR/docs/Web/API/ExampleGone"));
159+
assert_eq!(
160+
redirects.get("/pt-BR/docs/Web/API/ExampleGone").unwrap(),
161+
"/en-US/docs/Web/API/ExampleOne"
162+
);
163+
}
164+
165+
#[test]
166+
fn test_add_redirect_external() {
167+
let slugs = vec!["Web/API/ExampleOne".to_string()];
168+
let _docs = DocFixtures::new(&slugs, Locale::EnUs);
169+
let _redirects = RedirectFixtures::new(&vec![], Locale::EnUs);
170+
171+
let result = do_add_redirect("/en-US/docs/Web/API/ExampleGone", "https://example.com/");
172+
assert!(result.is_ok());
173+
174+
let redirects = get_redirects_map(Locale::EnUs);
175+
assert!(redirects.contains_key("/en-US/docs/Web/API/ExampleGone"));
176+
assert_eq!(
177+
redirects.get("/en-US/docs/Web/API/ExampleGone").unwrap(),
178+
"https://example.com/"
179+
);
180+
}
181+
}

crates/rari-tools/src/error.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use thiserror::Error;
1010
pub enum ToolError {
1111
#[error("Invalid slug: {0}")]
1212
InvalidSlug(Cow<'static, str>),
13+
#[error("Invalid url: {0}")]
14+
InvalidUrl(Cow<'static, str>),
1315
#[error("Git error: {0}")]
1416
GitError(String),
1517

crates/rari-tools/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod add_redirect;
12
pub mod error;
23
pub mod git;
34
pub mod history;

crates/rari-tools/src/move.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ mod test {
318318
"Web/API/ExampleOne".to_string(),
319319
"Web/API/ExampleOne/SubExampleOne".to_string(),
320320
"Web/API/ExampleOne/SubExampleTwo".to_string(),
321+
"Web/API/SomethingElse".to_string(),
321322
];
322323
let redirects = vec![
323324
(
@@ -353,7 +354,7 @@ mod test {
353354
Locale::EnUs,
354355
false,
355356
);
356-
357+
println!("result: {:?}", result);
357358
assert!(result.is_ok());
358359
let result = result.unwrap();
359360
assert!(result.len() == 3);
@@ -428,6 +429,7 @@ mod test {
428429
"Web/API/ExampleOne".to_string(),
429430
"Web/API/ExampleOne/SubExampleOne".to_string(),
430431
"Web/API/ExampleOne/SubExampleTwo".to_string(),
432+
"Web/API/SomethingElse".to_string(),
431433
];
432434
let redirects = vec![
433435
(
@@ -463,7 +465,6 @@ mod test {
463465
Locale::PtBr,
464466
false,
465467
);
466-
467468
assert!(result.is_ok());
468469
let result = result.unwrap();
469470
assert!(result.len() == 3);

0 commit comments

Comments
 (0)