Skip to content

Commit 2f6fb72

Browse files
Copilotazerpas
andauthored
Replace regex compilation unwrap() with lazy_static pattern (#70)
* Initial plan * Use lazy_static for OTP params regex to handle compilation errors at startup Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> * Apply lazy_static pattern to all regex compilation in mod.rs Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> * Replace unwrap() with proper error handling in extract_token and extract_user_contact Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> * Remove all remaining unwrap() calls in extraction functions Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> * Add documentation comments to lazy_static regex constants Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> * Make error handling consistent across all extraction functions Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: azerpas <19282069+azerpas@users.noreply.github.com>
1 parent 1d2ab92 commit 2f6fb72

1 file changed

Lines changed: 66 additions & 32 deletions

File tree

  • src/bourso_api/src/client

src/bourso_api/src/client/mod.rs

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,28 @@ use self::config::{extract_brs_config, Config};
2121

2222
use super::constants::BASE_URL;
2323

24+
lazy_static::lazy_static! {
25+
/// Regex to extract OTP parameters from the authentication payload.
26+
/// Matches: data-strong-authentication-payload="{...}">
27+
static ref OTP_PARAMS_REGEX: Regex = Regex::new(r#"data-strong-authentication-payload="(\{.*?\})">"#)
28+
.expect("Failed to compile OTP parameters regex");
29+
30+
/// Regex to extract the __brs_mit cookie value from the response.
31+
/// Matches: __brs_mit=<value>;
32+
static ref BRS_MIT_COOKIE_REGEX: Regex = Regex::new(r"(?m)__brs_mit=(?P<brs_mit_cookie>.*?);")
33+
.expect("Failed to compile __brs_mit cookie regex");
34+
35+
/// Regex to extract the form token from the login page.
36+
/// Matches: form[_token]" ... value="<token>" >
37+
static ref TOKEN_REGEX: Regex = Regex::new(r#"(?ms)form\[_token\]"(.*?)value="(?P<token>.*?)"\s*>"#)
38+
.expect("Failed to compile form token regex");
39+
40+
/// Regex to extract the user contact information from the response.
41+
/// Matches: userContact&quot;:&quot;<contact>&quot;
42+
static ref USER_CONTACT_REGEX: Regex = Regex::new(r"(?m)userContact&quot;:&quot;(?P<contact_user>.*?)&quot;")
43+
.expect("Failed to compile user contact regex");
44+
}
45+
2446
pub struct BoursoWebClient {
2547
/// The client used to make requests to the Bourso website.
2648
client: reqwest::Client,
@@ -513,24 +535,29 @@ impl BoursoWebClient {
513535
///
514536
/// The __brs_mit cookie as a string.
515537
fn extract_brs_mit_cookie(res: &str) -> Result<String> {
516-
let regex = Regex::new(r"(?m)__brs_mit=(?P<brs_mit_cookie>.*?);").unwrap();
517-
let captures = regex.captures(&res);
518-
519-
if captures.is_none() {
520-
error!("{}", res);
521-
bail!("Could not extract brs mit cookie");
522-
}
523-
524-
let brs_mit_cookie = captures.unwrap().name("brs_mit_cookie").unwrap();
525-
526-
Ok(brs_mit_cookie.as_str().to_string())
538+
let brs_mit_cookie = BRS_MIT_COOKIE_REGEX
539+
.captures(&res)
540+
.and_then(|c| c.name("brs_mit_cookie"))
541+
.map(|m| m.as_str().to_string())
542+
.ok_or_else(|| {
543+
error!("{}", res);
544+
anyhow::anyhow!("Could not extract brs mit cookie")
545+
})?;
546+
547+
Ok(brs_mit_cookie)
527548
}
528549

529550
fn extract_token(res: &str) -> Result<String> {
530-
let regex = Regex::new(r#"(?ms)form\[_token\]"(.*?)value="(?P<token>.*?)"\s*>"#).unwrap();
531-
let token = regex.captures(&res).unwrap().name("token").unwrap();
532-
533-
Ok(token.as_str().trim().to_string())
551+
let token = TOKEN_REGEX
552+
.captures(&res)
553+
.and_then(|c| c.name("token"))
554+
.map(|m| m.as_str().trim().to_string())
555+
.ok_or_else(|| {
556+
error!("{}", res);
557+
anyhow::anyhow!("Could not extract form token")
558+
})?;
559+
560+
Ok(token)
534561
}
535562

536563
/// Extract OTP parameters from the response string.
@@ -540,19 +567,20 @@ fn extract_token(res: &str) -> Result<String> {
540567
/// # Returns
541568
/// A tuple containing the resource ID and form state as strings.
542569
fn extract_otp_params(res: &str) -> Result<(String, String)> {
543-
let regex = Regex::new(r#"data-strong-authentication-payload="(\{.*?\})">"#);
544-
545-
let captures = regex.unwrap().captures(&res);
546-
547-
let challenge_json = if let Some(captures) = captures {
548-
let challenge_str = captures.get(1).unwrap().as_str();
549-
// HTML decode the JSON string (replace &quot; with ")
550-
let decoded = challenge_str.replace("&quot;", "\"");
551-
serde_json::from_str::<serde_json::Value>(&decoded)?
552-
} else {
553-
error!("{}", res);
554-
bail!("Could not extract authentication challenge parameters");
555-
};
570+
let challenge_json = OTP_PARAMS_REGEX
571+
.captures(&res)
572+
.and_then(|c| c.get(1))
573+
.map(|m| m.as_str())
574+
.ok_or_else(|| {
575+
error!("{}", res);
576+
anyhow::anyhow!("Could not extract authentication challenge parameters")
577+
})
578+
.and_then(|challenge_str| {
579+
// HTML decode the JSON string (replace &quot; with ")
580+
let decoded = challenge_str.replace("&quot;", "\"");
581+
serde_json::from_str::<serde_json::Value>(&decoded)
582+
.map_err(|e| anyhow::anyhow!("Could not parse authentication challenge JSON: {}", e))
583+
})?;
556584

557585
Ok((
558586
challenge_json["challenges"][0]["parameters"]["formScreen"]["actions"]["check"]["api"]
@@ -575,10 +603,16 @@ fn extract_otp_params(res: &str) -> Result<(String, String)> {
575603
}
576604

577605
fn extract_user_contact(res: &str) -> Result<String> {
578-
let regex = Regex::new(r"(?m)userContact&quot;:&quot;(?P<contact_user>.*?)&quot;").unwrap();
579-
let contact_user = regex.captures(&res).unwrap().name("contact_user").unwrap();
580-
581-
Ok(contact_user.as_str().trim().to_string())
606+
let contact_user = USER_CONTACT_REGEX
607+
.captures(&res)
608+
.and_then(|c| c.name("contact_user"))
609+
.map(|m| m.as_str().trim().to_string())
610+
.ok_or_else(|| {
611+
error!("{}", res);
612+
anyhow::anyhow!("Could not extract user contact")
613+
})?;
614+
615+
Ok(contact_user)
582616
}
583617

584618
#[cfg(test)]

0 commit comments

Comments
 (0)