use crate::snippets::types::{Language, Snippet};
use std::collections::HashMap;
#[derive(Debug, PartialEq, Eq)]
pub(super) enum SessionClaim<'a> {
Unclaimed,
Claimed(&'a str),
Ambiguous(Vec<&'a str>),
}
pub(super) fn resolve_session_claim<'a, T>(
snippet: &Snippet,
entries: &'a HashMap<String, T>,
language_of: impl Fn(&T) -> Language,
) -> SessionClaim<'a> {
if let Some(target) = snippet.metadata.target.as_deref() {
let normalized = Language::normalize_session_target(target);
return match entries.get_key_value(normalized.as_str()) {
Some((key, _)) => SessionClaim::Claimed(key.as_str()),
None => SessionClaim::Unclaimed,
};
}
let mut candidates: Vec<&str> = entries
.iter()
.filter(|(_, value)| language_of(value) == snippet.language)
.map(|(key, _)| key.as_str())
.collect();
candidates.sort_unstable();
match candidates.as_slice() {
[] => SessionClaim::Unclaimed,
[only] => SessionClaim::Claimed(only),
_ => SessionClaim::Ambiguous(candidates),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::snippets::session::ValidationSession;
use crate::snippets::types::{SnippetMetadata, SourceOrigin};
use std::path::PathBuf;
fn snippet(language: Language, target: Option<&str>) -> Snippet {
Snippet {
id: None,
path: PathBuf::from("example.md"),
language,
title: None,
code: String::new(),
start_line: 1,
block_index: 0,
annotation: None,
metadata: SnippetMetadata {
target: target.map(str::to_string),
..SnippetMetadata::default()
},
source_origin: SourceOrigin {
path: PathBuf::from("example.md"),
line: 1,
block_index: 0,
},
}
}
fn session(language: Language) -> ValidationSession {
ValidationSession {
language,
working_directory: PathBuf::new(),
manifest: None,
fingerprint: "fingerprint".into(),
env: Default::default(),
include_paths: Vec::new(),
rust_features: Vec::new(),
rust_dependencies: Default::default(),
}
}
#[test]
fn a_single_same_language_session_claims_a_target_less_snippet_regardless_of_its_name() {
let sessions = HashMap::from([("node".to_string(), session(Language::TypeScript))]);
let snippet = snippet(Language::TypeScript, None);
assert_eq!(
resolve_session_claim(&snippet, &sessions, |session| session.language),
SessionClaim::Claimed("node")
);
}
#[test]
fn two_same_language_sessions_with_no_explicit_target_are_ambiguous() {
let sessions = HashMap::from([
("typescript".to_string(), session(Language::TypeScript)),
("wasm".to_string(), session(Language::TypeScript)),
]);
let snippet = snippet(Language::TypeScript, None);
assert_eq!(
resolve_session_claim(&snippet, &sessions, |session| session.language),
SessionClaim::Ambiguous(vec!["typescript", "wasm"])
);
}
#[test]
fn an_explicit_target_resolves_an_otherwise_ambiguous_language() {
let sessions = HashMap::from([
("typescript".to_string(), session(Language::TypeScript)),
("wasm".to_string(), session(Language::TypeScript)),
]);
let snippet = snippet(Language::TypeScript, Some("wasm"));
assert_eq!(
resolve_session_claim(&snippet, &sessions, |session| session.language),
SessionClaim::Claimed("wasm")
);
}
#[test]
fn an_explicit_target_naming_no_session_does_not_fall_back_to_a_language_match() {
let sessions = HashMap::from([("node".to_string(), session(Language::TypeScript))]);
let snippet = snippet(Language::TypeScript, Some("wasm"));
assert_eq!(
resolve_session_claim(&snippet, &sessions, |session| session.language),
SessionClaim::Unclaimed
);
}
#[test]
fn no_configured_session_for_the_language_is_unclaimed() {
let sessions = HashMap::from([("python".to_string(), session(Language::Python))]);
let snippet = snippet(Language::TypeScript, None);
assert_eq!(
resolve_session_claim(&snippet, &sessions, |session| session.language),
SessionClaim::Unclaimed
);
}
}