trojan_rules/provider/
file.rs1use std::path::Path;
4
5use crate::error::RulesError;
6use crate::parser;
7use crate::rule::ParsedRule;
8
9#[derive(Debug)]
11pub struct FileProvider;
12
13impl FileProvider {
14 pub fn load(
20 path: &Path,
21 format: &str,
22 behavior: Option<&str>,
23 ) -> Result<Vec<ParsedRule>, RulesError> {
24 let content = std::fs::read_to_string(path)?;
25 Self::parse(&content, format, behavior)
26 }
27
28 pub fn parse(
30 content: &str,
31 format: &str,
32 behavior: Option<&str>,
33 ) -> Result<Vec<ParsedRule>, RulesError> {
34 match format {
35 "surge" => match behavior {
36 Some("domain-set") | Some("domain") => parser::parse_surge_domain_set(content),
37 _ => parser::parse_surge_ruleset(content),
38 },
39 "clash" => {
40 let behavior = behavior.ok_or_else(|| {
41 RulesError::Provider("behavior is required for clash format".into())
42 })?;
43 parser::parse_clash_provider(content, behavior)
44 }
45 _ => Err(RulesError::Provider(format!(
46 "unsupported format: {format}"
47 ))),
48 }
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn parse_surge_classical() {
58 let content = "DOMAIN,example.com\nDOMAIN-SUFFIX,test.com";
59 let rules = FileProvider::parse(content, "surge", Some("classical")).unwrap();
60 assert_eq!(rules.len(), 2);
61 }
62
63 #[test]
64 fn parse_surge_domain_set() {
65 let content = "example.com\n.test.com";
66 let rules = FileProvider::parse(content, "surge", Some("domain-set")).unwrap();
67 assert_eq!(rules.len(), 2);
68 }
69
70 #[test]
71 fn parse_clash_domain() {
72 let content = "payload:\n - 'example.com'\n - '+.test.com'";
73 let rules = FileProvider::parse(content, "clash", Some("domain")).unwrap();
74 assert_eq!(rules.len(), 2);
75 }
76
77 #[test]
78 fn unsupported_format() {
79 let result = FileProvider::parse("", "unknown", None);
80 result.unwrap_err();
81 }
82
83 #[test]
84 fn clash_missing_behavior() {
85 let result = FileProvider::parse("payload: []", "clash", None);
86 result.unwrap_err();
87 }
88
89 #[test]
90 fn parse_surge_domain_behavior() {
91 let content = "example.com\n.test.com";
93 let rules = FileProvider::parse(content, "surge", Some("domain")).unwrap();
94 assert_eq!(rules.len(), 2);
95 assert!(matches!(&rules[0], ParsedRule::Domain(d) if d == "example.com"));
96 assert!(matches!(&rules[1], ParsedRule::DomainSuffix(d) if d == "test.com"));
97 }
98}