A robust Rust library for validating LinkedIn profile URLs with AI-first design. Built for the era of AI agents and LLMs, Credify provides both traditional validation APIs and specialized functions optimized for AI tool calling, especially with frameworks like Rig.
🎯 New in v0.4.0: Enhanced LinkedIn 404 detection with comprehensive apostrophe encoding support and complete Rig framework integration examples with real-world patterns.
// Validate candidate profiles before outreach
let result = rig_validate_json("https://linkedin.com/in/john-doe").await;
// Returns: {"valid": true, "username": "john-doe", "confidence": 95, ...}
// Quick validation for scraped LinkedIn URLs
if rig_is_valid(potential_lead_url).await {
add_to_crm(potential_lead_url);
}
// Get human-readable validation for chat interfaces
let message = rig_validate_text(profile_url).await;
// Returns: "✅ Valid profile @jane-smith (90% confidence)"
// Batch validate URLs with confidence scoring
let results = urls.iter().map(|url| rig_validate(url)).collect().await;
// Filter by confidence level for data quality
- 🤖 AI-First Design - Multiple API levels from simple booleans to rich structured data
- 🎯 Rig Framework Optimized - Ergonomic helpers designed specifically for Rig tools
- ⚡ Async & Sync APIs - Full async support to prevent blocking runtime panics
- 📊 Structured Responses -
AIValidationResult
with confidence scores and decisions - 🔍 Smart Validation - Format checking, existence verification, and intelligent fallbacks
- 📝 Rich Error Context - Detailed explanations with actionable suggestions
- 🛡️ Never Panics - Comprehensive error handling throughout
- 🚀 High Performance - Optimized for concurrent operations
[dependencies]
credify = "0.4.0"
Or use cargo add:
cargo add credify
use credify::{rig_is_valid, rig_validate_text};
// Ultra-simple validation
if rig_is_valid("https://linkedin.com/in/johndoe").await {
println!("Valid LinkedIn profile!");
}
// Get a human-readable response
let message = rig_validate_text("https://linkedin.com/in/johndoe").await;
// Returns: "✅ Valid profile @johndoe (95% confidence)"
// Complete Rig tool implementation
#[derive(Deserialize)]
struct ValidateLinkedInArgs {
url: String,
}
#[derive(Deserialize, Serialize)]
struct LinkedInValidator;
#[async_trait]
impl Tool for LinkedInValidator {
const NAME: &'static str = "validate_linkedin_profile";
type Args = ValidateLinkedInArgs;
type Output = String;
type Error = ToolError;
async fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: Self::NAME.to_string(),
description: "Validates LinkedIn profile URLs and returns structured information".to_string(),
parameters: json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The LinkedIn profile URL to validate"
}
},
"required": ["url"]
}),
}
}
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
// Just one line! No runtime panics, perfect for Rig
Ok(credify::rig_validate_json(&args.url).await)
}
}
Function | Returns | Use Case |
---|---|---|
rig_is_valid() |
bool |
Quick true/false checks |
rig_validate_text() |
String |
One-line human-readable responses |
rig_validate_json() |
String |
Clean JSON for tool responses |
rig_validate() |
RigValidationResult |
Structured data with all details |
Function | Returns | Use Case |
---|---|---|
ai_validate() |
AIValidationResult |
Full structured data |
ai_validate_json() |
String |
JSON for AI consumption |
validate_for_llm() |
String |
Verbose text reports |
Function | Returns | Use Case |
---|---|---|
is_valid_linkedin_profile_format() |
bool |
Format checking only |
LinkedInValidator::is_valid_linkedin_profile_url() |
Result<bool> |
Full validation |
use credify::{rig_validate, RigValidationResult};
use rig::tool::Tool;
#[derive(Deserialize, Serialize)]
struct LinkedInChecker;
impl Tool for LinkedInChecker {
const NAME: &'static str = "linkedin_checker";
type Args = CheckArgs;
type Output = String;
type Error = MyError;
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
// One line - that's it!
Ok(credify::rig_validate_json(&args.url).await)
}
}
// Or use structured data
async fn check_with_details(url: &str) {
let result: RigValidationResult = credify::rig_validate(url).await;
println!("Valid: {}", result.valid);
println!("Status: {}", result.status);
println!("Action: {}", result.action);
println!("Confidence: {}%", result.confidence);
if let Some(username) = result.username {
println!("Username: @{}", username);
}
}
use credify::{ai_validate, AIDecision};
async fn validate_for_ai(url: &str) {
let result = ai_validate(url).await;
// Simple boolean check
if result.is_valid {
println!("Profile is valid!");
}
// Use confidence for nuanced decisions
if result.confidence >= 0.9 {
println!("High confidence validation");
}
// AI-friendly decision enum
match result.decision {
AIDecision::Accept => {
// Use the profile
println!("Accepted: {}", result.username.unwrap_or_default());
}
AIDecision::Retry => {
// Network issue, try again
println!("Temporary issue, retry in a moment");
}
AIDecision::Reject => {
// Invalid URL
println!("Invalid: {}", result.reason);
}
}
}
use credify::is_valid_linkedin_profile_format;
// Format check only (no network calls)
if is_valid_linkedin_profile_format("https://linkedin.com/in/johndoe") {
println!("Format is valid!");
}
// Full validation with network check
use credify::LinkedInValidator;
let validator = LinkedInValidator::new()?;
match validator.is_valid_linkedin_profile_url(url) {
Ok(true) => println!("Profile exists!"),
Ok(false) => println!("Profile not found"),
Err(e) => println!("Error: {}", e),
}
use credify::validate_linkedin_url_async;
// Async validation
let is_valid = validate_linkedin_url_async(url).await?;
// Async with AI response
let json = credify::ai_validate_json_async(url).await;
Credify is designed to work seamlessly with the Rig framework for building AI agents. Here's a complete guide to integrating LinkedIn validation into your Rig-powered AI system.
use credify::rig_validate_json;
use rig::{completion::ToolDefinition, tool::Tool};
use serde::{Deserialize, Serialize};
use serde_json::json;
#[derive(Debug, Deserialize)]
struct ValidateArgs {
url: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct LinkedInValidator;
#[async_trait::async_trait]
impl Tool for LinkedInValidator {
const NAME: &'static str = "validate_linkedin_profile";
type Args = ValidateArgs;
type Output = String;
type Error = Box<dyn std::error::Error + Send + Sync>;
async fn definition(&self) -> ToolDefinition {
ToolDefinition {
name: Self::NAME.to_string(),
description: "Validates LinkedIn profile URLs and returns structured data about the profile".to_string(),
parameters: json!({
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The LinkedIn profile URL to validate (e.g., https://linkedin.com/in/username)"
}
},
"required": ["url"]
}),
}
}
async fn call(&self, args: Self::Args) -> Result<Self::Output, Self::Error> {
Ok(rig_validate_json(&args.url).await)
}
}
use rig::providers::openai;
use rig::completion::{Prompt, ToolDefinition};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create the LinkedIn validator tool
let linkedin_tool = LinkedInValidator;
// Set up your AI client
let client = openai::Client::new(&std::env::var("OPENAI_API_KEY")?);
// Create an agent with the LinkedIn validation tool
let agent = client
.agent("gpt-4")
.preamble("You are a professional network analyst.")
.tool(linkedin_tool)
.build();
// Use the agent to validate profiles
let response = agent
.prompt("Check if https://linkedin.com/in/satya-nadella is a valid LinkedIn profile")
.await?;
println!("Agent response: {}", response);
Ok(())
}
use credify::{rig_is_valid, rig_validate, rig_validate_text};
// Quick boolean check for conditional logic
async fn quick_check(url: &str) -> bool {
rig_is_valid(url).await
}
// Human-readable response for chat interfaces
async fn chat_response(url: &str) -> String {
rig_validate_text(url).await
}
// Structured data for complex workflows
async fn detailed_check(url: &str) {
let result = rig_validate(url).await;
match result.confidence {
90..=100 => println!("High confidence: proceed with profile"),
70..=89 => println!("Medium confidence: verify manually"),
_ => println!("Low confidence: search for alternative")
}
}
#[derive(Debug, Serialize, Deserialize)]
struct RecruitingAssistant {
linkedin_validator: LinkedInValidator,
}
impl RecruitingAssistant {
async fn validate_candidate_profiles(&self, profiles: Vec<String>) -> Vec<CandidateStatus> {
let mut results = Vec::new();
for profile_url in profiles {
let validation = rig_validate(&profile_url).await;
results.push(CandidateStatus {
url: profile_url,
valid: validation.valid,
username: validation.username,
confidence: validation.confidence,
action: match validation.valid {
true => "Schedule interview",
false => "Request updated profile"
}.to_string(),
});
}
results
}
}
#[derive(Debug, Serialize)]
struct CandidateStatus {
url: String,
valid: bool,
username: Option<String>,
confidence: u8,
action: String,
}
// Credify's Rig helpers NEVER panic, making them safe for production
async fn safe_validation(url: &str) -> String {
// This will always return a valid JSON response, even on errors
let result = rig_validate_json(url).await;
// Parse the result to handle different scenarios
if let Ok(parsed) = serde_json::from_str::<RigValidationResult>(&result) {
if parsed.valid {
format!("Profile @{} is valid", parsed.username.unwrap_or_default())
} else {
format!("Invalid profile: {}", parsed.status)
}
} else {
"Validation service temporarily unavailable".to_string()
}
}
use futures::future::join_all;
async fn batch_validate(urls: Vec<String>) -> Vec<(String, bool)> {
let futures = urls.into_iter().map(|url| async move {
let valid = rig_is_valid(&url).await;
(url, valid)
});
join_all(futures).await
}
// Minimal implementation for basic needs
impl Tool for SimpleLinkedInChecker {
async fn call(&self, args: Args) -> Result<String, Error> {
if rig_is_valid(&args.url).await {
Ok("Valid LinkedIn profile".to_string())
} else {
Ok("Invalid LinkedIn profile".to_string())
}
}
}
// Rich responses for complex AI workflows
impl Tool for DetailedLinkedInAnalyzer {
async fn call(&self, args: Args) -> Result<String, Error> {
let result = rig_validate(&args.url).await;
Ok(json!({
"valid": result.valid,
"username": result.username,
"confidence": result.confidence,
"status": result.status,
"recommended_action": result.action,
"profile_url": if result.valid {
Some(format!("https://linkedin.com/in/{}",
result.username.as_ref().unwrap_or(&"unknown".to_string())))
} else {
None
}
}).to_string())
}
}
// Complex validation with fallback strategies
impl Tool for SmartLinkedInValidator {
async fn call(&self, args: Args) -> Result<String, Error> {
// First, try the provided URL
let result = rig_validate(&args.url).await;
if result.valid {
return Ok(rig_validate_json(&args.url).await);
}
// If invalid, try common variations
if let Some(username) = extract_username(&args.url) {
let variations = vec![
format!("https://linkedin.com/in/{}", username),
format!("https://www.linkedin.com/in/{}", username),
format!("https://linkedin.com/in/{}/", username),
];
for variant in variations {
if rig_is_valid(&variant).await {
return Ok(rig_validate_json(&variant).await);
}
}
}
// Return detailed error information
Ok(json!({
"valid": false,
"error": "Profile not found",
"suggestions": [
"Check the username spelling",
"Verify the profile hasn't been deleted",
"Try searching by name on LinkedIn"
]
}).to_string())
}
}
- Always use async: All Rig helpers are async to prevent blocking
- Never panics: Rig helpers return valid responses even on errors
- Structured responses: Use
rig_validate_json
for consistent AI parsing - Confidence scores: Use confidence levels to make nuanced decisions
- Batch wisely: Process multiple URLs concurrently for better performance
When using Credify in async contexts (like web servers or AI frameworks), always use the async versions to avoid runtime panics:
// ❌ WRONG - Can cause panic in async context
async fn my_tool() {
let result = credify::ai_validate_json(url); // Panic!
}
// ✅ CORRECT - Use async version
async fn my_tool() {
let result = credify::ai_validate_json_async(url).await; // Works!
}
// ✅ BEST - Use Rig helpers (always async)
async fn my_tool() {
let result = credify::rig_validate_json(url).await; // Perfect!
}
pub struct RigValidationResult {
pub valid: bool, // Simple pass/fail
pub username: Option<String>, // LinkedIn username if found
pub confidence: u8, // 0-100 percentage
pub status: String, // Human-readable status
pub action: String, // Suggested action for AI
}
pub struct AIValidationResult {
pub is_valid: bool,
pub confidence: f32, // 0.0 to 1.0
pub decision: AIDecision, // Accept/Retry/Reject
pub username: Option<String>,
pub reason: String,
pub metadata: ValidationMetadata,
}
Traditional validation returns simple true/false or error codes. AI agents need rich context to make intelligent decisions:
- Context-Rich Responses: Understand why validation failed
- Confidence Scores: Make nuanced decisions based on certainty
- Actionable Suggestions: Know what to do next
- Structured Data: Easy to parse and reason about
let validator = LinkedInValidator::new_with_user_agent(
"MyBot/1.0 (https://mybot.com)"
)?;
LinkedIn often returns AUTH_REQUIRED (999 status) for valid profiles. Credify intelligently handles this:
// AUTH_REQUIRED is treated as a valid profile
let result = rig_validate(url).await;
if result.valid && result.status.contains("auth required") {
println!("Profile likely exists but LinkedIn is blocking checks");
}
Check out the examples/
directory for:
basic.rs
- Simple validation examplesrig_ergonomic.rs
- Ergonomic Rig API showcaserig_integration.rs
- Full Rig framework integration with function callingrig_async_proper.rs
- Advanced async patterns for Rigbatch_validator.rs
- Validate multiple URLs concurrentlyllm_simple.rs
- LLM-friendly validationai_agent_demo.rs
- Complete AI agent implementation
// Define your tool
#[derive(Tool)]
#[tool(
name = "linkedin_validator",
description = "Validates LinkedIn profile URLs"
)]
struct LinkedInValidator;
// Implement the tool - just one line!
impl LinkedInValidator {
async fn validate(&self, url: String) -> String {
credify::rig_validate_json(&url).await
}
}
// Use with your AI agent
let agent = client
.agent("gpt-4")
.tool(LinkedInValidator)
.build();
let response = agent
.prompt("Check these LinkedIn profiles for our hiring pipeline")
.await?;
Run examples with:
cargo run --example rig_ergonomic
# Run all tests
cargo test
# Run with verbose output
cargo test -- --nocapture
Licensed under either of:
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Contributions are welcome! Please feel free to submit a Pull Request.
Built with ❤️ for the AI agent community. Special thanks to the Rig framework team for inspiring the ergonomic API design.