1use std::collections::{HashMap, HashSet};
9
10use crate::{Cursor, Error, ServerBlock, ServerBlockKey, Token};
11
12pub struct Directive<'a, C> {
14 pub name: &'a str,
15 pub once: bool,
16 pub setup: fn(&mut Cursor<'_>, &mut BlockContext<C>) -> Result<(), Error>,
17}
18
19#[derive(Debug)]
21pub struct BlockContext<C> {
22 keys: Vec<ServerBlockKey>,
23 pub config: C,
24 pub view: bool,
26 pub listen_addresses: Vec<String>,
28}
29
30impl<C> BlockContext<C> {
31 pub fn keys(&self) -> &[ServerBlockKey] {
33 &self.keys
34 }
35}
36
37pub fn configure<C: Default>(
39 blocks: &[ServerBlock],
40 registry: &[Directive<'_, C>],
41) -> Result<Vec<BlockContext<C>>, Error> {
42 let mut configured = Vec::with_capacity(blocks.len());
43 for block in blocks {
44 for (name, tokens) in &block.directives {
45 if tokens.is_empty() {
46 return Err(Error {
47 file: String::new(),
48 line: 0,
49 message: format!("Directive '{name}' has no tokens"),
50 });
51 }
52 if !registry.iter().any(|directive| directive.name == name) {
53 let token = &tokens[0];
54 return Err(Error {
55 file: token.file.clone(),
56 line: token.line,
57 message: format!("Unknown directive '{name}'"),
58 });
59 }
60 }
61 let mut context = BlockContext {
62 keys: block.normalize_keys()?,
63 config: C::default(),
64 view: false,
65 listen_addresses: vec![String::new()],
66 };
67 for directive in registry {
68 let Some(tokens) = block.directives.get(directive.name) else {
69 continue;
70 };
71 if directive.once
72 && let Some(token) = second_occurrence(tokens)
73 {
74 return Err(Error {
75 file: token.file.clone(),
76 line: token.line,
77 message: format!(
78 "{}: this plugin can only be used once per Server Block",
79 directive.name
80 ),
81 });
82 }
83 let mut cursor = Cursor::new(&tokens[0].file, tokens);
84 (directive.setup)(&mut cursor, &mut context)?;
85 }
86 configured.push(context);
87 }
88 validate_overlaps(blocks, &configured)?;
89 Ok(configured)
90}
91
92fn validate_overlaps<C>(
93 blocks: &[ServerBlock],
94 configured: &[BlockContext<C>],
95) -> Result<(), Error> {
96 let mut registered: HashSet<(ServerBlockKey, String)> = HashSet::new();
97 let mut unbound: HashMap<ServerBlockKey, String> = HashMap::new();
98 for (block, context) in blocks.iter().zip(configured) {
99 for (key, source) in block.normalized_key_sources()? {
100 for address in &context.listen_addresses {
101 let exact = registered.contains(&(key.clone(), address.clone()));
102 let overlap = if exact {
103 None
104 } else if address.is_empty() {
105 unbound.get(&key).map(String::as_str)
106 } else if registered.contains(&(key.clone(), String::new())) {
107 Some("")
108 } else {
109 None
110 };
111 let current = format_address(&key, address);
112 if exact && !context.view {
113 return Err(Error {
114 file: source.file.clone(),
115 line: source.line,
116 message: format!("cannot serve {current} - it is already defined"),
117 });
118 }
119 if let Some(previous) = overlap {
120 return Err(Error {
121 file: source.file.clone(),
122 line: source.line,
123 message: format!(
124 "cannot serve {current} - zone overlap listener capacity with {}",
125 format_address(&key, previous)
126 ),
127 });
128 }
129 if !context.view {
130 registered.insert((key.clone(), address.clone()));
131 unbound.insert(key.clone(), address.clone());
132 }
133 }
134 }
135 }
136 Ok(())
137}
138
139fn format_address(key: &ServerBlockKey, address: &str) -> String {
140 let mut value = format!("{}://{}:{}", key.transport, key.domain, key.port);
141 if !address.is_empty() {
142 value.push_str(&format!(" on {address}"));
143 }
144 value
145}
146
147fn second_occurrence(tokens: &[Token]) -> Option<&Token> {
148 let mut nesting = 0usize;
149 for (index, token) in tokens.iter().enumerate() {
150 if index > 0 && nesting == 0 {
151 let previous = &tokens[index - 1];
152 if crate::parser::new_line(previous, token) {
153 return Some(token);
154 }
155 }
156 match token.text.as_str() {
157 "{" => nesting += 1,
158 "}" => nesting = nesting.saturating_sub(1),
159 _ => {}
160 }
161 }
162 None
163}