use once_cell::sync::Lazy;
use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::json;
use thiserror::Error;
use url::Url;
mod rig_helpers;
pub use rig_helpers::{
RigValidationResult, rig_is_valid, rig_validate, rig_validate_json, rig_validate_text,
};
#[derive(Error, Debug)]
pub enum LinkedInUrlError {
#[error("[INVALID_URL_FORMAT] The provided URL is not a valid URL: {0}")]
InvalidUrl(String),
#[error("[NOT_LINKEDIN_DOMAIN] The URL is not from linkedin.com or www.linkedin.com domain")]
NotLinkedInUrl,
#[error(
"[NOT_PROFILE_URL] The URL is not a LinkedIn profile URL (expected format: /in/username)"
)]
NotProfileUrl,
#[error("[NETWORK_ERROR] Failed to connect to LinkedIn: {0}")]
NetworkError(#[from] reqwest::Error),
#[error("[PROFILE_NOT_FOUND] The LinkedIn profile does not exist (404)")]
ProfileNotFound,
#[error(
"[AUTH_REQUIRED] LinkedIn requires authentication to verify this profile - cannot determine if profile exists"
)]
AuthenticationRequired,
#[error("[CLIENT_BUILD_ERROR] Failed to create HTTP client: {0}")]
ClientBuildError(String),
}
pub struct LinkedInValidator {
client: reqwest::blocking::Client,
}
impl LinkedInValidator {
pub fn new() -> Result<Self, LinkedInUrlError> {
let client = reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| LinkedInUrlError::ClientBuildError(e.to_string()))?;
Ok(Self { client })
}
pub fn is_valid_linkedin_profile_url(&self, url_str: &str) -> Result<bool, LinkedInUrlError> {
let url = Url::parse(url_str).map_err(|e| LinkedInUrlError::InvalidUrl(e.to_string()))?;
if !is_linkedin_domain(&url) {
return Err(LinkedInUrlError::NotLinkedInUrl);
}
if !is_profile_path(&url) {
return Err(LinkedInUrlError::NotProfileUrl);
}
self.check_profile_exists(url_str)?;
Ok(true)
}
fn check_profile_exists(&self, url: &str) -> Result<(), LinkedInUrlError> {
let mut response = self.client.get(url).send()?;
if response.status().as_u16() == 999 {
response = self.client.get(url).header("Cookie", "sl=v=1&1").send()?;
}
let final_url = response.url().to_string();
if final_url.contains("/404/") || final_url.contains("linkedin.com/404") {
return Err(LinkedInUrlError::ProfileNotFound);
}
let body = response.text()?;
if body.contains("/authwall") || body.contains("sessionRedirect") {
return Err(LinkedInUrlError::AuthenticationRequired);
}
if body.contains("This page doesn't exist")
|| body.contains("This page doesn't exist") || body.contains("This page doesn't exist") || body.contains("This page doesn't exist") || body.contains("Page not found")
|| body.contains("Check the URL or return to LinkedIn home")
|| body.contains("Check your URL or return to LinkedIn home") || body.contains("return to LinkedIn home")
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
{
return Err(LinkedInUrlError::ProfileNotFound);
}
Ok(())
}
}
fn is_linkedin_domain(url: &Url) -> bool {
matches!(url.domain(), Some(domain) if domain == "linkedin.com" || domain == "www.linkedin.com")
}
static PROFILE_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^/in/[a-zA-Z0-9\-\.]+/?$")
.expect("[INTERNAL_ERROR] Failed to compile profile regex pattern")
});
fn is_profile_path(url: &Url) -> bool {
let path = url.path();
PROFILE_REGEX.is_match(path)
}
impl LinkedInValidator {
#[must_use]
pub fn new_unchecked() -> Self {
Self::new().expect("[INTERNAL_ERROR] Failed to create LinkedIn validator")
}
}
pub async fn validate_linkedin_url_async(url: &str) -> Result<bool, LinkedInUrlError> {
let url_parsed = Url::parse(url).map_err(|e| LinkedInUrlError::InvalidUrl(e.to_string()))?;
if !is_linkedin_domain(&url_parsed) {
return Err(LinkedInUrlError::NotLinkedInUrl);
}
if !is_profile_path(&url_parsed) {
return Err(LinkedInUrlError::NotProfileUrl);
}
let client = reqwest::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(10))
.build()
.map_err(|e| LinkedInUrlError::ClientBuildError(e.to_string()))?;
let mut response = client.get(url).send().await?;
if response.status().as_u16() == 999 {
response = client.get(url).header("Cookie", "sl=v=1&1").send().await?;
}
let final_url = response.url().to_string();
if final_url.contains("/404/") || final_url.contains("linkedin.com/404") {
return Err(LinkedInUrlError::ProfileNotFound);
}
let body = response.text().await?;
if body.contains("/authwall") || body.contains("sessionRedirect") {
return Err(LinkedInUrlError::AuthenticationRequired);
}
if body.contains("This page doesn't exist")
|| body.contains("This page doesn't exist") || body.contains("This page doesn't exist") || body.contains("This page doesn't exist") || body.contains("Page not found")
|| body.contains("Check the URL or return to LinkedIn home")
|| body.contains("Check your URL or return to LinkedIn home") || body.contains("return to LinkedIn home")
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
|| (body.contains("Go to your feed") && body.contains("doesn't exist"))
{
return Err(LinkedInUrlError::ProfileNotFound);
}
Ok(true)
}
pub fn validate_for_llm(url: &str) -> String {
let mut result = String::new();
result.push_str("=== LINKEDIN PROFILE VALIDATION REPORT ===\n\n");
result.push_str(&format!("TIMESTAMP: {}\n", chrono::Utc::now().to_rfc3339()));
result.push_str(&format!("INPUT_URL: {url}\n"));
result.push('\n');
let validator = match LinkedInValidator::new() {
Ok(v) => v,
Err(e) => {
result.push_str("VALIDATION_RESULT: ERROR\n");
result.push_str("VALIDATION_STATUS: FAILED\n");
result.push_str("ERROR_TYPE: VALIDATOR_INITIALIZATION_FAILED\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: CRITICAL\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str(
"The HTTP client required for LinkedIn validation could not be initialized. ",
);
result.push_str("This is typically caused by system resource constraints or TLS configuration issues.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Check available system memory and ensure sufficient resources\n");
result.push_str("2. Verify TLS/SSL libraries are properly installed on the system\n");
result
.push_str("3. Check for any system-level network restrictions or firewall rules\n");
result.push_str("4. Try restarting the application or service\n");
result.push_str(
"5. If problem persists, check system logs for more detailed error information\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Resolve system-level issues before attempting validation again\n");
result.push_str("\n=== END OF VALIDATION REPORT ===\n");
return result;
}
};
match validator.is_valid_linkedin_profile_url(url) {
Ok(_) => {
result.push_str("VALIDATION_RESULT: SUCCESS\n");
result.push_str("VALIDATION_STATUS: PASSED\n");
result.push_str("PROFILE_EXISTS: TRUE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("PROFILE_ACCESSIBLE: TRUE\n");
if let Ok(parsed_url) = url::Url::parse(url) {
if let Some(mut path_segments) = parsed_url.path_segments() {
if let Some(username) = path_segments.next_back() {
result.push_str(&format!(
"LINKEDIN_USERNAME: {}\n",
username.trim_end_matches('/')
));
}
}
}
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The provided URL has been successfully validated. The LinkedIn profile exists and is accessible. ");
result.push_str("The URL follows the correct LinkedIn profile format and the domain has been verified as authentic. ");
result.push_str("The profile page returned a successful response, confirming the profile is active and publicly viewable.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Proceed with profile data extraction using LinkedIn API or web scraping tools\n");
result.push_str("2. Cache this validation result to avoid repeated network requests\n");
result.push_str(
"3. Store the profile URL in your database as a verified LinkedIn profile\n",
);
result.push_str(
"4. Consider extracting additional profile metadata (name, headline, etc.)\n",
);
result.push_str(
"5. Set up monitoring to periodically re-validate the profile existence\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Extract profile data using appropriate LinkedIn data extraction methods\n");
}
Err(e) => {
result.push_str("VALIDATION_RESULT: ERROR\n");
result.push_str("VALIDATION_STATUS: FAILED\n");
match e {
LinkedInUrlError::InvalidUrl(ref msg) => {
result.push_str("ERROR_TYPE: INVALID_URL_FORMAT\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str(&format!("ERROR_DETAILS: {msg}\n"));
result.push_str("ERROR_SEVERITY: HIGH\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("URL_FORMAT: INVALID\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The provided string is not a valid URL. The URL parser failed to interpret the input as a properly formatted URL. ");
result.push_str("Common causes include missing protocol (http/https), invalid characters, or malformed structure.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Ensure the URL starts with 'https://' or 'http://'\n");
result.push_str("2. Check for special characters that need URL encoding\n");
result.push_str("3. Verify there are no spaces or line breaks in the URL\n");
result.push_str(
"4. Confirm the URL follows standard format: protocol://domain/path\n",
);
result.push_str(
"5. Try URL encoding the input if it contains special characters\n",
);
result.push_str(
"6. Example valid format: https://www.linkedin.com/in/username\n",
);
result.push_str(
"\nRECOMMENDED_NEXT_STEP: Fix the URL format and retry validation\n",
);
}
LinkedInUrlError::NotLinkedInUrl => {
result.push_str("ERROR_TYPE: NOT_LINKEDIN_DOMAIN\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: NOT_APPLICABLE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: FALSE\n");
if let Ok(parsed_url) = url::Url::parse(url) {
if let Some(domain) = parsed_url.domain() {
result.push_str(&format!("ACTUAL_DOMAIN: {domain}\n"));
}
}
result.push_str("\nDETAILED_EXPLANATION:\n");
result
.push_str("The URL is properly formatted but does not point to LinkedIn. ");
result.push_str("Only URLs from 'linkedin.com' or 'www.linkedin.com' domains are accepted for LinkedIn profile validation. ");
result.push_str("The provided URL points to a different domain.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Verify the URL is meant to be a LinkedIn profile URL\n");
result.push_str("2. Check if the URL was copied correctly from LinkedIn\n");
result
.push_str("3. Ensure the domain is 'linkedin.com' or 'www.linkedin.com'\n");
result.push_str("4. Look for the correct LinkedIn profile URL in the user's social media links\n");
result.push_str(
"5. Ask the user to provide their LinkedIn profile URL directly\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Obtain the correct LinkedIn profile URL from the user or source\n");
}
LinkedInUrlError::NotProfileUrl => {
result.push_str("ERROR_TYPE: NOT_PROFILE_URL\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: NOT_APPLICABLE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("URL_TYPE: NON_PROFILE_LINKEDIN_URL\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result
.push_str("The URL points to LinkedIn but is not a personal profile URL. ");
result.push_str(
"It might be a company page, job posting, or other LinkedIn content. ",
);
result.push_str(
"Valid profile URLs follow the pattern: linkedin.com/in/username\n",
);
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str(
"1. Check if this is a company page URL (contains '/company/')\n",
);
result.push_str("2. Verify if this is a job posting URL (contains '/jobs/')\n");
result.push_str(
"3. Look for the '/in/' segment that indicates a personal profile\n",
);
result
.push_str("4. Navigate to the person's actual profile page on LinkedIn\n");
result.push_str("5. Use LinkedIn search to find the correct profile URL\n");
result.push_str("\nRECOMMENDED_NEXT_STEP: Navigate to the personal profile section of LinkedIn\n");
}
LinkedInUrlError::ProfileNotFound => {
result.push_str("ERROR_TYPE: PROFILE_NOT_FOUND\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: LOW\n");
result.push_str("PROFILE_EXISTS: FALSE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("HTTP_STATUS: 404\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The URL format is correct and points to LinkedIn, but the profile does not exist. ");
result
.push_str("LinkedIn returned a 404 error or redirected to an error page. ");
result.push_str("This means the username in the URL does not correspond to any active LinkedIn profile.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Double-check the username/URL for typos\n");
result.push_str(
"2. Verify if the profile might have been deleted or deactivated\n",
);
result.push_str("3. Check if the user might have changed their LinkedIn URL\n");
result.push_str("4. Search for the person on LinkedIn using their name\n");
result.push_str(
"5. Contact the person to get their current LinkedIn profile URL\n",
);
result.push_str("6. Remove this URL from your database if it's stored\n");
result.push_str("\nRECOMMENDED_NEXT_STEP: Search for the correct profile or mark as invalid in your system\n");
}
LinkedInUrlError::AuthenticationRequired => {
result.push_str("ERROR_TYPE: AUTH_REQUIRED\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("LINKEDIN_RESPONSE: AUTHENTICATION_WALL\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("LinkedIn is requiring authentication to view this profile. ");
result.push_str("This typically happens when LinkedIn detects automated access patterns or when accessing from certain IP ranges. ");
result.push_str("The profile format is valid and the profile LIKELY EXISTS, but LinkedIn is preventing automated verification.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str(
"1. ACCEPT AS VALID: The URL format is correct and points to LinkedIn - treat as valid profile URL\n",
);
result.push_str(
"2. RETRY LATER: Wait 30-60 seconds before trying to validate this URL again\n",
);
result.push_str("3. CONTINUE SEARCH: This URL is likely correct, but continue searching for alternative profiles if needed\n");
result.push_str("4. CONFIDENCE: HIGH - LinkedIn auth walls usually indicate real profiles\n");
result.push_str("5. FOR AI AGENTS: You can safely return this URL as a valid LinkedIn profile\n");
result.push_str(
"6. VERIFICATION: The URL follows pattern https://linkedin.com/in/USERNAME which is correct\n",
);
result.push_str(
"7. RECOMMENDATION: Use this URL - it's almost certainly a valid profile\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: ACCEPT this URL as valid - LinkedIn authentication requirements typically indicate the profile exists\n");
}
LinkedInUrlError::NetworkError(_) => {
result.push_str("ERROR_TYPE: NETWORK_ERROR\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: HIGH\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("NETWORK_STATUS: FAILED\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("Failed to establish a network connection to LinkedIn. ");
result.push_str("This could be due to network connectivity issues, DNS resolution problems, ");
result.push_str(
"firewall restrictions, or LinkedIn being temporarily unavailable.\n",
);
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Check internet connectivity with a simple ping test\n");
result.push_str("2. Verify DNS resolution for linkedin.com\n");
result.push_str("3. Check firewall settings for outbound HTTPS connections\n");
result.push_str("4. Test if LinkedIn is accessible from a web browser\n");
result.push_str("5. Implement retry logic with exponential backoff\n");
result.push_str("6. Check for any proxy configuration requirements\n");
result.push_str("7. Monitor LinkedIn's status page for any outages\n");
result.push_str("\nRECOMMENDED_NEXT_STEP: Diagnose and resolve network connectivity issues\n");
}
LinkedInUrlError::ClientBuildError(ref msg) => {
result.push_str("ERROR_TYPE: CLIENT_BUILD_ERROR\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str(&format!("ERROR_DETAILS: {msg}\n"));
result.push_str("ERROR_SEVERITY: CRITICAL\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("Failed to build the HTTP client needed for validation. ");
result.push_str("This is an internal error that prevents any network requests from being made. ");
result.push_str("Common causes include TLS configuration issues or system resource constraints.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Check system TLS/SSL library installation\n");
result.push_str("2. Verify sufficient memory is available\n");
result.push_str("3. Check for any security software blocking connections\n");
result.push_str("4. Review system logs for detailed error information\n");
result.push_str("5. Restart the application or service\n");
result.push_str("6. Update system libraries and dependencies\n");
result.push_str("\nRECOMMENDED_NEXT_STEP: Resolve system configuration issues before retry\n");
}
}
}
}
result.push_str("\n=== END OF VALIDATION REPORT ===\n");
result
}
pub async fn validate_for_llm_async(url: &str) -> String {
let mut result = String::new();
result.push_str("=== LINKEDIN PROFILE VALIDATION REPORT ===\n\n");
result.push_str(&format!("TIMESTAMP: {}\n", chrono::Utc::now().to_rfc3339()));
result.push_str(&format!("INPUT_URL: {url}\n"));
result.push('\n');
match validate_linkedin_url_async(url).await {
Ok(_) => {
result.push_str("VALIDATION_RESULT: SUCCESS\n");
result.push_str("VALIDATION_STATUS: PASSED\n");
result.push_str("PROFILE_EXISTS: TRUE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("PROFILE_ACCESSIBLE: TRUE\n");
if let Ok(parsed_url) = url::Url::parse(url) {
if let Some(mut path_segments) = parsed_url.path_segments() {
if let Some(username) = path_segments.next_back() {
result.push_str(&format!(
"LINKEDIN_USERNAME: {}\n",
username.trim_end_matches('/')
));
}
}
}
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The provided URL has been successfully validated. The LinkedIn profile exists and is accessible. ");
result.push_str("The URL follows the correct LinkedIn profile format and the domain has been verified as authentic. ");
result.push_str("The profile page returned a successful response, confirming the profile is active and publicly viewable.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Proceed with profile data extraction using LinkedIn API or web scraping tools\n");
result.push_str("2. Cache this validation result to avoid repeated network requests\n");
result.push_str(
"3. Store the profile URL in your database as a verified LinkedIn profile\n",
);
result.push_str(
"4. Consider extracting additional profile metadata (name, headline, etc.)\n",
);
result.push_str(
"5. Set up monitoring to periodically re-validate the profile existence\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Extract profile data using appropriate LinkedIn data extraction methods\n");
}
Err(e) => {
result.push_str("VALIDATION_RESULT: ERROR\n");
result.push_str("VALIDATION_STATUS: FAILED\n");
match e {
LinkedInUrlError::InvalidUrl(ref msg) => {
result.push_str("ERROR_TYPE: INVALID_URL_FORMAT\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str(&format!("ERROR_DETAILS: {msg}\n"));
result.push_str("ERROR_SEVERITY: HIGH\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("URL_FORMAT: INVALID\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The provided string is not a valid URL. The URL parser failed to interpret the input as a properly formatted URL. ");
result.push_str("Common causes include missing protocol (http/https), invalid characters, or malformed structure.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Ensure the URL starts with 'https://' or 'http://'\n");
result.push_str("2. Check for special characters that need URL encoding\n");
result.push_str("3. Verify there are no spaces or line breaks in the URL\n");
result.push_str(
"4. Confirm the URL follows standard format: protocol://domain/path\n",
);
result.push_str(
"5. Try URL encoding the input if it contains special characters\n",
);
result.push_str(
"6. Example valid format: https://www.linkedin.com/in/username\n",
);
result.push_str(
"\nRECOMMENDED_NEXT_STEP: Fix the URL format and retry validation\n",
);
}
LinkedInUrlError::NotLinkedInUrl => {
result.push_str("ERROR_TYPE: NOT_LINKEDIN_DOMAIN\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: NOT_APPLICABLE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: FALSE\n");
if let Ok(parsed_url) = url::Url::parse(url) {
if let Some(domain) = parsed_url.domain() {
result.push_str(&format!("ACTUAL_DOMAIN: {domain}\n"));
}
}
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The URL is valid but points to a domain other than LinkedIn. This validator specifically checks LinkedIn profile URLs. ");
result.push_str("The domain must be either 'linkedin.com' or 'www.linkedin.com' to be considered valid.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str(
"1. Replace the domain with 'linkedin.com' or 'www.linkedin.com'\n",
);
result.push_str(
"2. Verify you have the correct URL from the LinkedIn platform\n",
);
result.push_str(
"3. Check if the URL was copied correctly without modification\n",
);
result.push_str("4. If this is a different social media profile, use appropriate validators\n");
result.push_str(
"5. Ensure the URL is not from a LinkedIn clone or phishing site\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Obtain the correct LinkedIn profile URL from the official LinkedIn website\n");
}
LinkedInUrlError::NotProfileUrl => {
result.push_str("ERROR_TYPE: NOT_PROFILE_URL\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: NOT_APPLICABLE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("URL_TYPE: NON_PROFILE_LINKEDIN_URL\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The URL is from LinkedIn but does not point to a user profile. It may be a company page, job listing, or other LinkedIn content. ");
result.push_str("This validator specifically checks for personal profile URLs that follow the pattern '/in/username'.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Navigate to the person's LinkedIn profile and copy the URL from there\n");
result.push_str(
"2. Look for URLs that contain '/in/' followed by the username\n",
);
result.push_str("3. If this is a company page, note that company validation requires different logic\n");
result.push_str(
"4. Check if you need to be logged in to LinkedIn to access the profile\n",
);
result.push_str(
"5. Verify the profile URL format: https://linkedin.com/in/[username]\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Find the personal profile URL that includes '/in/' in the path\n");
}
LinkedInUrlError::ProfileNotFound => {
result.push_str("ERROR_TYPE: PROFILE_NOT_FOUND\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: HIGH\n");
result.push_str("PROFILE_EXISTS: FALSE\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("HTTP_STATUS: 404\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("The URL format is correct and points to LinkedIn, but the profile does not exist. ");
result.push_str("This could mean the profile was deleted, the username was changed, or there's a typo in the username. ");
result.push_str(
"LinkedIn returned a 404 (Not Found) response for this profile URL.\n",
);
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str(
"1. Double-check the username for typos or case sensitivity issues\n",
);
result.push_str("2. Search for the person on LinkedIn using their name instead of direct URL\n");
result.push_str("3. Ask the person for their current LinkedIn profile URL\n");
result.push_str(
"4. Check if the profile might have been recently deleted or deactivated\n",
);
result.push_str(
"5. Try variations of the username (with/without hyphens, numbers, etc.)\n",
);
result.push_str(
"6. Verify if the person has privacy settings that hide their profile\n",
);
result.push_str(
"7. Consider that the profile might have moved to a different username\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Search for the person on LinkedIn by name or request their current profile URL\n");
}
LinkedInUrlError::AuthenticationRequired => {
result.push_str("ERROR_TYPE: AUTH_REQUIRED\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str("ERROR_SEVERITY: MEDIUM\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("URL_FORMAT: VALID\n");
result.push_str("DOMAIN_VERIFIED: TRUE\n");
result.push_str("HTTP_STATUS: 999\n");
result.push_str("LINKEDIN_RESPONSE: AUTHENTICATION_WALL\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("LinkedIn is requiring authentication to verify this profile. This is a protective measure LinkedIn uses to prevent automated access. ");
result.push_str("The HTTP 999 status code is LinkedIn's custom response for requests that appear to be automated. ");
result.push_str("The profile format is valid and the profile LIKELY EXISTS, but LinkedIn is preventing automated verification.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str(
"1. ACCEPT AS VALID: The URL format is correct and points to LinkedIn - treat as valid profile URL\n",
);
result.push_str(
"2. RETRY LATER: Wait 30-60 seconds before trying to validate this URL again\n",
);
result.push_str("3. CONTINUE SEARCH: This URL is likely correct, but continue searching for alternative profiles if needed\n");
result.push_str("4. CONFIDENCE: HIGH - LinkedIn auth walls usually indicate real profiles\n");
result.push_str("5. FOR AI AGENTS: You can safely return this URL as a valid LinkedIn profile\n");
result.push_str(
"6. VERIFICATION: The URL follows pattern https://linkedin.com/in/USERNAME which is correct\n",
);
result.push_str(
"7. RECOMMENDATION: Use this URL - it's almost certainly a valid profile\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: ACCEPT this URL as valid - LinkedIn authentication requirements typically indicate the profile exists\n");
}
LinkedInUrlError::NetworkError(ref network_err) => {
result.push_str("ERROR_TYPE: NETWORK_ERROR\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str(&format!("NETWORK_ERROR_DETAILS: {network_err}\n"));
result.push_str("ERROR_SEVERITY: HIGH\n");
result.push_str("PROFILE_EXISTS: UNKNOWN\n");
result.push_str("NETWORK_STATUS: FAILED\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("A network error occurred while trying to reach LinkedIn. This could be due to connectivity issues, ");
result.push_str("DNS resolution problems, timeouts, or LinkedIn being temporarily unavailable. ");
result.push_str("The validation could not be completed due to this network-level failure.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Check your internet connection and try again\n");
result.push_str("2. Verify DNS resolution for linkedin.com is working\n");
result
.push_str("3. Check if LinkedIn is accessible from your network/region\n");
result.push_str(
"4. Retry the request after a short delay (exponential backoff)\n",
);
result
.push_str("5. Check for firewall or proxy settings blocking the request\n");
result.push_str("6. Verify SSL/TLS certificates are up to date\n");
result.push_str(
"7. Consider using a different network or VPN if the issue persists\n",
);
result.push_str("\nRECOMMENDED_NEXT_STEP: Diagnose network connectivity and retry the validation\n");
}
LinkedInUrlError::ClientBuildError(ref msg) => {
result.push_str("ERROR_TYPE: CLIENT_BUILD_ERROR\n");
result.push_str(&format!("ERROR_MESSAGE: {e}\n"));
result.push_str(&format!("ERROR_DETAILS: {msg}\n"));
result.push_str("ERROR_SEVERITY: CRITICAL\n");
result.push_str("VALIDATION_STATUS: FAILED\n");
result.push_str("\nDETAILED_EXPLANATION:\n");
result.push_str("Failed to create the HTTP client needed for validation. This is typically caused by system resource constraints ");
result.push_str("or TLS configuration issues. The validation cannot proceed without a working HTTP client.\n");
result.push_str("\nSUGGESTED_ACTIONS:\n");
result.push_str("1. Check available system memory and resources\n");
result.push_str("2. Verify TLS/SSL libraries are properly installed\n");
result.push_str("3. Check for system-level network restrictions\n");
result.push_str("4. Restart the application or service\n");
result.push_str("5. Review system logs for more detailed error information\n");
result.push_str("\nRECOMMENDED_NEXT_STEP: Resolve system-level issues before attempting validation\n");
}
}
}
}
result.push_str("\n=== END OF VALIDATION REPORT ===\n");
result
}
#[must_use]
pub fn is_valid_linkedin_profile_format(url: &str) -> bool {
let Ok(url_parsed) = Url::parse(url) else {
return false;
};
is_linkedin_domain(&url_parsed) && is_profile_path(&url_parsed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_profile_format() {
assert!(is_valid_linkedin_profile_format(
"https://www.linkedin.com/in/hamze/"
));
assert!(is_valid_linkedin_profile_format(
"https://www.linkedin.com/in/hamzeghalebi/"
));
assert!(is_valid_linkedin_profile_format(
"https://www.linkedin.com/in/johndoe"
));
assert!(is_valid_linkedin_profile_format(
"https://linkedin.com/in/jane-doe"
));
assert!(is_valid_linkedin_profile_format(
"https://www.linkedin.com/in/john-doe-123/"
));
}
#[test]
fn test_invalid_profile_format() {
assert!(!is_valid_linkedin_profile_format(
"https://www.google.com/in/johndoe"
));
assert!(!is_valid_linkedin_profile_format(
"https://linkedin.com/company/microsoft"
));
assert!(!is_valid_linkedin_profile_format("https://linkedin.com/"));
assert!(!is_valid_linkedin_profile_format("not-a-url"));
}
#[test]
fn test_real_valid_profile() {
let validator = match LinkedInValidator::new() {
Ok(v) => v,
Err(e) => {
eprintln!("[TEST_SETUP_ERROR] Failed to create validator: {e}");
return;
}
};
match validator.is_valid_linkedin_profile_url("https://www.linkedin.com/in/hamze/") {
Ok(true) => (),
Ok(false) => eprintln!("[TEST_FAILED] Expected profile to be valid but got false"),
Err(LinkedInUrlError::AuthenticationRequired) => {
println!(
"[AUTH_REQUIRED] LinkedIn requires authentication - cannot verify profile existence"
);
}
Err(e) => eprintln!(
"[TEST_FAILED] Expected profile to be valid or require auth, got error: {e}"
),
}
}
#[test]
fn test_real_invalid_profile() {
let validator = match LinkedInValidator::new() {
Ok(v) => v,
Err(e) => {
eprintln!("[TEST_SETUP_ERROR] Failed to create validator: {e}");
return;
}
};
match validator.is_valid_linkedin_profile_url("https://www.linkedin.com/in/hamzeghalebi/") {
Ok(_) => {
println!(
"[WARNING] LinkedIn allowed access to profile page - cannot determine if profile actually exists"
);
}
Err(LinkedInUrlError::ProfileNotFound) => (),
Err(LinkedInUrlError::AuthenticationRequired) => {
println!(
"[AUTH_REQUIRED] LinkedIn requires authentication - cannot verify profile existence"
);
}
Err(e) => eprintln!(
"[TEST_FAILED] Expected ProfileNotFound or AuthenticationRequired error, got: {e}"
),
}
}
#[tokio::test]
async fn test_async_valid_profile() {
match validate_linkedin_url_async("https://www.linkedin.com/in/hamze/").await {
Ok(true) => (),
Ok(false) => eprintln!("[TEST_FAILED] Expected profile to be valid but got false"),
Err(LinkedInUrlError::AuthenticationRequired) => {
println!(
"[AUTH_REQUIRED] LinkedIn requires authentication - cannot verify profile existence"
);
}
Err(e) => eprintln!(
"[TEST_FAILED] Expected profile to be valid or require auth, got error: {e}"
),
}
}
#[tokio::test]
async fn test_async_invalid_profile() {
match validate_linkedin_url_async("https://www.linkedin.com/in/hamzeghalebi/").await {
Ok(_) => {
println!(
"[WARNING] LinkedIn allowed access to profile page - cannot determine if profile actually exists"
);
}
Err(LinkedInUrlError::ProfileNotFound) => (),
Err(LinkedInUrlError::AuthenticationRequired) => {
println!(
"[AUTH_REQUIRED] LinkedIn requires authentication - cannot verify profile existence"
);
}
Err(e) => eprintln!(
"[TEST_FAILED] Expected ProfileNotFound or AuthenticationRequired error, got: {e}"
),
}
}
#[test]
fn test_validate_for_llm_success() {
let result = validate_for_llm("https://www.linkedin.com/in/valid-format");
assert!(result.contains("VALIDATION_RESULT:"));
assert!(result.contains("SUGGESTED_ACTIONS:"));
assert!(result.contains("=== LINKEDIN PROFILE VALIDATION REPORT ==="));
assert!(result.contains("TIMESTAMP:"));
assert!(result.contains("INPUT_URL:"));
}
#[test]
fn test_validate_for_llm_invalid_url() {
let result = validate_for_llm("not-a-url");
assert!(result.contains("VALIDATION_RESULT: ERROR"));
assert!(result.contains("ERROR_TYPE: INVALID_URL_FORMAT"));
assert!(result.contains("SUGGESTED_ACTIONS:"));
assert!(result.contains("ERROR_SEVERITY: HIGH"));
assert!(result.contains("DETAILED_EXPLANATION:"));
}
#[test]
fn test_validate_for_llm_not_linkedin() {
let result = validate_for_llm("https://www.google.com/in/someone");
assert!(result.contains("VALIDATION_RESULT: ERROR"));
assert!(result.contains("ERROR_TYPE: NOT_LINKEDIN_DOMAIN"));
assert!(result.contains("SUGGESTED_ACTIONS:"));
assert!(result.contains("ERROR_SEVERITY: MEDIUM"));
assert!(result.contains("ACTUAL_DOMAIN: www.google.com"));
}
#[test]
fn test_validate_for_llm_not_profile() {
let result = validate_for_llm("https://www.linkedin.com/company/microsoft");
assert!(result.contains("VALIDATION_RESULT: ERROR"));
assert!(result.contains("ERROR_TYPE: NOT_PROFILE_URL"));
assert!(result.contains("SUGGESTED_ACTIONS:"));
assert!(result.contains("URL_TYPE: NON_PROFILE_LINKEDIN_URL"));
assert!(result.contains("RECOMMENDED_NEXT_STEP:"));
}
#[tokio::test]
async fn test_validate_for_llm_async() {
let result = validate_for_llm_async("https://www.linkedin.com/in/test-user").await;
assert!(result.contains("VALIDATION_RESULT:"));
assert!(result.contains("SUGGESTED_ACTIONS:"));
assert!(result.contains("=== LINKEDIN PROFILE VALIDATION REPORT ==="));
assert!(result.contains("TIMESTAMP:"));
assert!(result.contains("=== END OF VALIDATION REPORT ==="));
}
#[test]
#[ignore = "Debug test to inspect LinkedIn response"]
fn debug_linkedin_response() {
let client = match reqwest::blocking::Client::builder()
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
.timeout(std::time::Duration::from_secs(10))
.build()
{
Ok(c) => c,
Err(e) => {
eprintln!("[CLIENT_BUILD_ERROR] Failed to build client: {e}");
return;
}
};
let url = "https://www.linkedin.com/in/hamzeghalebi/";
let response = match client.get(url).send() {
Ok(r) => r,
Err(e) => {
eprintln!("[NETWORK_ERROR] Failed to send request: {e}");
return;
}
};
println!("Status: {}", response.status());
println!("Final URL: {}", response.url());
match response.text() {
Ok(body) => {
println!("Body length: {}", body.len());
println!(
"First 2000 chars:\n{}",
&body.chars().take(2000).collect::<String>()
);
}
Err(e) => eprintln!("[RESPONSE_ERROR] Failed to read response body: {e}"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AIValidationResult {
pub is_valid: bool,
pub confidence: f32,
pub decision: AIDecision,
pub username: Option<String>,
pub reason: String,
pub metadata: ValidationMetadata,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum AIDecision {
Accept,
Retry,
Reject,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationMetadata {
pub url_format_valid: bool,
pub domain_verified: bool,
pub profile_pattern_matched: bool,
pub http_status: Option<u16>,
pub error_type: Option<String>,
pub timestamp: String,
}
pub fn ai_validate(url: &str) -> AIValidationResult {
let timestamp = chrono::Utc::now().to_rfc3339();
let parsed_url = match Url::parse(url) {
Ok(u) => u,
Err(e) => {
return AIValidationResult {
is_valid: false,
confidence: 1.0,
decision: AIDecision::Reject,
username: None,
reason: format!("Invalid URL format: {e}"),
metadata: ValidationMetadata {
url_format_valid: false,
domain_verified: false,
profile_pattern_matched: false,
http_status: None,
error_type: Some("INVALID_URL".to_string()),
timestamp,
},
};
}
};
let domain_valid = is_linkedin_domain(&parsed_url);
if !domain_valid {
return AIValidationResult {
is_valid: false,
confidence: 1.0,
decision: AIDecision::Reject,
username: None,
reason: "Not a LinkedIn URL".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: false,
profile_pattern_matched: false,
http_status: None,
error_type: Some("WRONG_DOMAIN".to_string()),
timestamp,
},
};
}
let is_profile = is_profile_path(&parsed_url);
if !is_profile {
return AIValidationResult {
is_valid: false,
confidence: 0.95,
decision: AIDecision::Reject,
username: None,
reason: "LinkedIn URL but not a profile (might be company page)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: false,
http_status: None,
error_type: Some("NOT_PROFILE".to_string()),
timestamp,
},
};
}
let username = parsed_url
.path_segments()
.and_then(|mut segments| {
if segments.next() == Some("in") {
segments.next()
} else {
None
}
})
.filter(|u| !u.is_empty())
.map(|u| u.trim_end_matches('/').to_string());
let validator = match LinkedInValidator::new() {
Ok(v) => v,
Err(_) => {
return AIValidationResult {
is_valid: true,
confidence: 0.7,
decision: AIDecision::Accept,
username,
reason: "URL format is valid (network check unavailable)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: None,
error_type: Some("VALIDATOR_ERROR".to_string()),
timestamp,
},
};
}
};
match validator.is_valid_linkedin_profile_url(url) {
Ok(_) => AIValidationResult {
is_valid: true,
confidence: 1.0,
decision: AIDecision::Accept,
username,
reason: "Verified LinkedIn profile exists".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(200),
error_type: None,
timestamp,
},
},
Err(LinkedInUrlError::AuthenticationRequired) => {
AIValidationResult {
is_valid: true,
confidence: 0.9,
decision: AIDecision::Accept,
username,
reason: "LinkedIn profile likely exists (auth required)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(999),
error_type: Some("AUTH_REQUIRED".to_string()),
timestamp,
},
}
}
Err(LinkedInUrlError::ProfileNotFound) => AIValidationResult {
is_valid: false,
confidence: 0.95,
decision: AIDecision::Reject,
username,
reason: "LinkedIn profile does not exist (404)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(404),
error_type: Some("NOT_FOUND".to_string()),
timestamp,
},
},
Err(LinkedInUrlError::NetworkError(_)) => AIValidationResult {
is_valid: true,
confidence: 0.6,
decision: AIDecision::Retry,
username,
reason: "Network error - retry later".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: None,
error_type: Some("NETWORK_ERROR".to_string()),
timestamp,
},
},
Err(e) => AIValidationResult {
is_valid: false,
confidence: 0.2,
decision: AIDecision::Reject,
username,
reason: format!("Validation error: {e}"),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: None,
error_type: Some("OTHER_ERROR".to_string()),
timestamp,
},
},
}
}
pub async fn ai_validate_async(url: &str) -> AIValidationResult {
let timestamp = chrono::Utc::now().to_rfc3339();
let parsed_url = match Url::parse(url) {
Ok(u) => u,
Err(e) => {
return AIValidationResult {
is_valid: false,
confidence: 1.0,
decision: AIDecision::Reject,
username: None,
reason: format!("Invalid URL format: {e}"),
metadata: ValidationMetadata {
url_format_valid: false,
domain_verified: false,
profile_pattern_matched: false,
http_status: None,
error_type: Some("INVALID_URL".to_string()),
timestamp,
},
};
}
};
let domain_valid = is_linkedin_domain(&parsed_url);
if !domain_valid {
return AIValidationResult {
is_valid: false,
confidence: 1.0,
decision: AIDecision::Reject,
username: None,
reason: "Not a LinkedIn URL".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: false,
profile_pattern_matched: false,
http_status: None,
error_type: Some("WRONG_DOMAIN".to_string()),
timestamp,
},
};
}
let is_profile = is_profile_path(&parsed_url);
if !is_profile {
return AIValidationResult {
is_valid: false,
confidence: 0.95,
decision: AIDecision::Reject,
username: None,
reason: "LinkedIn URL but not a profile (might be company page)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: false,
http_status: None,
error_type: Some("NOT_PROFILE".to_string()),
timestamp,
},
};
}
let username = parsed_url
.path_segments()
.and_then(|mut segments| {
if segments.next() == Some("in") {
segments.next()
} else {
None
}
})
.filter(|u| !u.is_empty())
.map(|u| u.trim_end_matches('/').to_string());
match validate_linkedin_url_async(url).await {
Ok(_) => AIValidationResult {
is_valid: true,
confidence: 1.0,
decision: AIDecision::Accept,
username,
reason: "Verified LinkedIn profile exists".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(200),
error_type: None,
timestamp,
},
},
Err(LinkedInUrlError::AuthenticationRequired) => {
AIValidationResult {
is_valid: true,
confidence: 0.9,
decision: AIDecision::Accept,
username,
reason: "LinkedIn profile likely exists (auth required)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(999),
error_type: Some("AUTH_REQUIRED".to_string()),
timestamp,
},
}
}
Err(LinkedInUrlError::ProfileNotFound) => AIValidationResult {
is_valid: false,
confidence: 0.95,
decision: AIDecision::Reject,
username,
reason: "LinkedIn profile does not exist (404)".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: Some(404),
error_type: Some("NOT_FOUND".to_string()),
timestamp,
},
},
Err(LinkedInUrlError::NetworkError(_)) => AIValidationResult {
is_valid: true,
confidence: 0.6,
decision: AIDecision::Retry,
username,
reason: "Network error - retry later".to_string(),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: None,
error_type: Some("NETWORK_ERROR".to_string()),
timestamp,
},
},
Err(e) => AIValidationResult {
is_valid: false,
confidence: 0.2,
decision: AIDecision::Reject,
username,
reason: format!("Validation error: {e}"),
metadata: ValidationMetadata {
url_format_valid: true,
domain_verified: true,
profile_pattern_matched: true,
http_status: None,
error_type: Some("OTHER_ERROR".to_string()),
timestamp,
},
},
}
}
pub fn ai_validate_json(url: &str) -> String {
let result = ai_validate(url);
serde_json::to_string_pretty(&result).unwrap_or_else(|_| {
json!({
"error": "Failed to serialize result",
"is_valid": false
})
.to_string()
})
}
pub async fn ai_validate_json_async(url: &str) -> String {
let result = ai_validate_async(url).await;
serde_json::to_string_pretty(&result).unwrap_or_else(|_| {
json!({
"error": "Failed to serialize result",
"is_valid": false
})
.to_string()
})
}