use super::signals::PatternSignals;
use crate::types::ApiConventionPattern;
pub fn signals_to_pattern(
signals: &PatternSignals,
evidence_limit: usize,
) -> Option<ApiConventionPattern> {
let api_conventions = &signals.api_conventions;
if !api_conventions.has_signals() {
return None;
}
let confidence = api_conventions.calculate_confidence();
let framework = api_conventions.detect_framework();
let mut patterns = Vec::new();
if !api_conventions.fastapi_decorators.is_empty()
|| !api_conventions.flask_decorators.is_empty()
|| !api_conventions.express_routes.is_empty()
{
patterns.push("rest_crud".to_string());
}
if !api_conventions.restful_patterns.is_empty() {
patterns.push("restful_naming".to_string());
}
if !api_conventions.graphql_defs.is_empty() {
patterns.push("graphql".to_string());
}
let orm_usage = api_conventions.detect_orm();
let mut evidence = Vec::new();
evidence.extend(
api_conventions
.fastapi_decorators
.iter()
.take(evidence_limit)
.cloned(),
);
evidence.extend(
api_conventions
.flask_decorators
.iter()
.take(evidence_limit)
.cloned(),
);
evidence.extend(
api_conventions
.express_routes
.iter()
.take(evidence_limit)
.cloned(),
);
evidence.extend(
api_conventions
.restful_patterns
.iter()
.take(evidence_limit)
.cloned(),
);
evidence.extend(
api_conventions
.orm_models
.iter()
.take(evidence_limit)
.map(|(_, e)| e.clone()),
);
evidence.truncate(evidence_limit);
Some(ApiConventionPattern {
confidence,
framework,
patterns,
orm_usage,
evidence,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::Evidence;
#[test]
fn test_no_signals_returns_none() {
let signals = PatternSignals::default();
assert!(signals_to_pattern(&signals, 3).is_none());
}
#[test]
fn test_fastapi_detected() {
let mut signals = PatternSignals::default();
signals
.api_conventions
.fastapi_decorators
.push(Evidence::new("routes.py", 10, "@app.get('/users')"));
let pattern = signals_to_pattern(&signals, 3).unwrap();
assert_eq!(pattern.framework, Some("fastapi".to_string()));
assert!(pattern.patterns.contains(&"rest_crud".to_string()));
}
#[test]
fn test_flask_detected() {
let mut signals = PatternSignals::default();
signals.api_conventions.flask_decorators.push(Evidence::new(
"routes.py",
10,
"@app.route('/users', methods=['GET'])",
));
let pattern = signals_to_pattern(&signals, 3).unwrap();
assert_eq!(pattern.framework, Some("flask".to_string()));
}
#[test]
fn test_express_detected() {
let mut signals = PatternSignals::default();
signals.api_conventions.express_routes.push(Evidence::new(
"routes.ts",
10,
"app.get('/users', (req, res) => { ... })",
));
let pattern = signals_to_pattern(&signals, 3).unwrap();
assert_eq!(pattern.framework, Some("express".to_string()));
}
#[test]
fn test_orm_detected() {
let mut signals = PatternSignals::default();
signals.api_conventions.orm_models.push((
"sqlalchemy".to_string(),
Evidence::new("models.py", 5, "class User(Base):"),
));
let pattern = signals_to_pattern(&signals, 3).unwrap();
assert_eq!(pattern.orm_usage, Some("sqlalchemy".to_string()));
}
}