1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema)]
5#[serde(rename_all = "lowercase")]
6pub enum Language {
7 Python,
8 Node,
9 Ruby,
10 Php,
11 Elixir,
12 Wasm,
13 Ffi,
14 Go,
15 Java,
16 Csharp,
17 R,
18 Rust,
19 Kotlin,
20 #[serde(rename = "kotlin_android", alias = "kotlin-android")]
21 KotlinAndroid,
22 Swift,
23 Dart,
24 Gleam,
25 Zig,
26 C,
28 #[serde(rename = "jni")]
31 Jni,
32}
33
34impl Language {
35 pub const ALL: [Language; 20] = [
39 Self::Python,
40 Self::Node,
41 Self::Ruby,
42 Self::Php,
43 Self::Elixir,
44 Self::Wasm,
45 Self::Ffi,
46 Self::Go,
47 Self::Java,
48 Self::Csharp,
49 Self::R,
50 Self::Rust,
51 Self::Kotlin,
52 Self::KotlinAndroid,
53 Self::Swift,
54 Self::Dart,
55 Self::Gleam,
56 Self::Zig,
57 Self::C,
58 Self::Jni,
59 ];
60}
61
62impl std::fmt::Display for Language {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::Python => write!(f, "python"),
66 Self::Node => write!(f, "node"),
67 Self::Ruby => write!(f, "ruby"),
68 Self::Php => write!(f, "php"),
69 Self::Elixir => write!(f, "elixir"),
70 Self::Wasm => write!(f, "wasm"),
71 Self::Ffi => write!(f, "ffi"),
72 Self::Go => write!(f, "go"),
73 Self::Java => write!(f, "java"),
74 Self::Csharp => write!(f, "csharp"),
75 Self::R => write!(f, "r"),
76 Self::Rust => write!(f, "rust"),
77 Self::Kotlin => write!(f, "kotlin"),
78 Self::KotlinAndroid => write!(f, "kotlin_android"),
79 Self::Swift => write!(f, "swift"),
80 Self::Dart => write!(f, "dart"),
81 Self::Gleam => write!(f, "gleam"),
82 Self::Zig => write!(f, "zig"),
83 Self::C => write!(f, "c"),
84 Self::Jni => write!(f, "jni"),
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
91#[serde(deny_unknown_fields)]
92pub struct AdapterParam {
93 pub name: String,
94 #[serde(rename = "type")]
95 pub ty: String,
96 #[serde(default)]
97 pub optional: bool,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
102#[serde(rename_all = "snake_case")]
103pub enum AdapterPattern {
104 SyncFunction,
105 AsyncMethod,
106 CallbackBridge,
107 Streaming,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
112#[serde(deny_unknown_fields)]
113pub struct AdapterConfig {
114 pub name: String,
115 pub pattern: AdapterPattern,
116 pub core_path: String,
118 #[serde(default)]
120 pub params: Vec<AdapterParam>,
121 pub returns: Option<String>,
123 pub error_type: Option<String>,
125 pub owner_type: Option<String>,
127 pub item_type: Option<String>,
129 #[serde(default)]
131 pub gil_release: bool,
132 #[serde(default)]
134 pub trait_name: Option<String>,
135 #[serde(default)]
137 pub trait_method: Option<String>,
138 #[serde(default)]
140 pub detect_async: bool,
141 #[serde(default)]
146 pub request_type: Option<String>,
147 #[serde(default)]
164 pub skip_languages: Vec<String>,
165}
166
167pub fn is_known_language(lang_str: &str) -> bool {
169 matches!(
170 lang_str,
171 "python"
172 | "node"
173 | "ruby"
174 | "php"
175 | "elixir"
176 | "wasm"
177 | "ffi"
178 | "go"
179 | "java"
180 | "csharp"
181 | "r"
182 | "rust"
183 | "kotlin"
184 | "kotlin_android"
185 | "swift"
186 | "dart"
187 | "gleam"
188 | "zig"
189 | "c"
190 | "jni"
191 )
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn adapter_config_skip_languages_deserializes() {
200 let toml_str = r#"
201 name = "crawl_stream"
202 pattern = "streaming"
203 core_path = "sample_crawler_core::crawl_stream"
204 owner_type = "CrawlEngine"
205 item_type = "CrawlResult"
206 skip_languages = ["wasm", "kotlin"]
207 "#;
208 let config: AdapterConfig = toml::from_str(toml_str).expect("deserialization failed");
209 assert_eq!(config.skip_languages, vec!["wasm", "kotlin"]);
210 assert_eq!(config.name, "crawl_stream");
211 }
212
213 #[test]
214 fn adapter_config_skip_languages_defaults_to_empty() {
215 let toml_str = r#"
216 name = "crawl_stream"
217 pattern = "streaming"
218 core_path = "sample_crawler_core::crawl_stream"
219 "#;
220 let config: AdapterConfig = toml::from_str(toml_str).expect("deserialization failed");
221 assert!(config.skip_languages.is_empty());
222 }
223
224 #[test]
225 fn is_known_language_accepts_all_canonical_names() {
226 for name in &[
227 "python",
228 "node",
229 "ruby",
230 "php",
231 "elixir",
232 "wasm",
233 "ffi",
234 "go",
235 "java",
236 "csharp",
237 "r",
238 "rust",
239 "kotlin",
240 "kotlin_android",
241 "swift",
242 "dart",
243 "gleam",
244 "zig",
245 "c",
246 "jni",
247 ] {
248 assert!(is_known_language(name), "{name} should be recognised");
249 }
250 }
251
252 #[test]
253 fn is_known_language_rejects_unknown() {
254 assert!(!is_known_language("wasm32"));
255 assert!(!is_known_language("kotlin-android"));
256 assert!(!is_known_language(""));
257 }
258
259 #[test]
260 fn all_lists_every_variant() {
261 for language in Language::ALL {
262 match language {
265 Language::Python
266 | Language::Node
267 | Language::Ruby
268 | Language::Php
269 | Language::Elixir
270 | Language::Wasm
271 | Language::Ffi
272 | Language::Go
273 | Language::Java
274 | Language::Csharp
275 | Language::R
276 | Language::Rust
277 | Language::Kotlin
278 | Language::KotlinAndroid
279 | Language::Swift
280 | Language::Dart
281 | Language::Gleam
282 | Language::Zig
283 | Language::C
284 | Language::Jni => {}
285 }
286 }
287 let distinct: std::collections::HashSet<String> = Language::ALL.iter().map(ToString::to_string).collect();
288 assert_eq!(
289 distinct.len(),
290 Language::ALL.len(),
291 "Language::ALL must not repeat a variant"
292 );
293 }
294}