Bootstrap from registry - part 1

This commit is contained in:
mdecimus
2026-01-24 19:49:37 +01:00
parent fa25da8da3
commit 1c2c96b89d
56 changed files with 1328 additions and 2038 deletions

View File

@@ -4,20 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use std::{cmp::Ordering, fmt::Display};
use compact_str::{CompactString, ToCompactString, format_compact};
use hyper::StatusCode;
use trc::EvalEvent;
use crate::Server;
use super::{
BinaryOperator, Constant, Expression, ExpressionItem, Setting, StringCow, UnaryOperator,
Variable,
functions::{FUNCTIONS, ResolveVariable},
if_block::IfBlock,
};
use crate::Server;
use compact_str::{CompactString, ToCompactString, format_compact};
use hyper::StatusCode;
use std::{cmp::Ordering, fmt::Display};
use trc::EvalEvent;
impl Server {
pub async fn eval_if<'x, R: TryFrom<Variable<'x>>, V: ResolveVariable>(
@@ -214,21 +211,10 @@ impl<'x, V: ResolveVariable> EvalContext<'x, V, Expression, &mut Vec<CompactStri
Setting::Hostname => {
stack.push(self.core.core.network.server_name.as_str().into())
}
Setting::ReportDomain => {
Setting::Domain => {
stack.push(self.core.core.network.report_domain.as_str().into())
}
Setting::NodeId => stack.push(self.core.core.network.node_id.into()),
Setting::Other(key) => stack.push(
self.core
.core
.storage
.config
.get(key)
.await?
.unwrap_or_default()
.to_compact_string()
.into(),
),
},
ExpressionItem::UnaryOperator(op) => {
let value = stack.pop().unwrap_or_default();

View File

@@ -4,16 +4,20 @@
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
use compact_str::CompactString;
use utils::config::{Config, utils::AsKey};
use crate::expr::{Constant, Expression};
use super::{
ConstantValue, ExpressionItem,
parser::ExpressionParser,
tokenizer::{TokenMap, Tokenizer},
};
use crate::{
expr::{Constant, Expression},
manager::bootstrap::Bootstrap,
};
use compact_str::CompactString;
use registry::{
schema::{prelude::Property, structs},
types::id::Id,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfThen {
@@ -23,37 +27,34 @@ pub struct IfThen {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IfBlock {
pub key: String,
pub property: Property,
pub if_then: Vec<IfThen>,
pub default: Expression,
}
impl IfBlock {
pub fn new<T: ConstantValue>(
key: impl Into<String>,
if_thens: impl IntoIterator<Item = (&'static str, &'static str)>,
default: impl AsRef<str>,
) -> Self {
pub fn new_default<T: ConstantValue>(property: Property, expr: structs::Expression) -> Self {
let token_map = TokenMap::default()
.with_all_variables()
.with_constants::<T>();
Self {
key: key.into(),
if_then: if_thens
property,
if_then: expr
.match_
.into_iter()
.map(|(if_, then)| IfThen {
expr: Expression::parse(&token_map, if_),
then: Expression::parse(&token_map, then),
.map(|match_| IfThen {
expr: Expression::parse(&token_map, &match_.if_),
then: Expression::parse(&token_map, &match_.then),
})
.collect(),
default: Expression::parse(&token_map, default.as_ref()),
default: Expression::parse(&token_map, &expr.else_),
}
}
pub fn empty(key: impl Into<String>) -> Self {
pub fn empty(property: Property) -> Self {
Self {
key: key.into(),
property,
if_then: Default::default(),
default: Expression {
items: Default::default(),
@@ -67,24 +68,6 @@ impl IfBlock {
}
impl Expression {
pub fn try_parse(
config: &mut Config,
key: impl AsKey,
token_map: &TokenMap,
) -> Option<Expression> {
if let Some(expr) = config.value(key.as_key()) {
match ExpressionParser::new(Tokenizer::new(expr, token_map)).parse() {
Ok(expr) => Some(expr),
Err(err) => {
config.new_parse_error(key, err);
None
}
}
} else {
None
}
}
fn parse(token_map: &TokenMap, expr: &str) -> Self {
ExpressionParser::new(Tokenizer::new(expr, token_map))
.parse()
@@ -94,129 +77,96 @@ impl Expression {
impl IfBlock {
pub fn try_parse(
config: &mut Config,
prefix: impl AsKey,
bp: &mut Bootstrap,
id: Id,
property: Property,
expr: structs::Expression,
token_map: &TokenMap,
) -> Option<IfBlock> {
let key = prefix.as_key();
// Parse conditions
let mut if_block = IfBlock {
key,
if_then: Default::default(),
property,
if_then: Vec::with_capacity(expr.match_.len()),
default: Expression {
items: Default::default(),
},
};
// Try first with a single value
if config.contains_key(if_block.key.as_str()) {
if_block.default = Expression::try_parse(config, &if_block.key, token_map)?;
return Some(if_block);
if expr.else_.is_empty() {
if !expr.match_.is_empty() {
bp.invalid_property(id, property, "Missing 'else' block in 'if' expression");
}
return None;
}
// Collect prefixes
let prefix = prefix.as_prefix();
let keys = config
.keys
.keys()
.filter(|k| k.starts_with(&prefix))
.cloned()
.collect::<Vec<_>>();
let mut found_if = false;
let mut found_else = "";
let mut found_then = false;
let mut last_array_pos = "";
if expr
.match_
.iter()
.any(|m| m.if_.is_empty() || m.then.is_empty())
{
bp.invalid_property(id, property, "All 'if' and 'then' blocks must be non-empty");
return None;
}
for item in &keys {
let suffix_ = item.strip_prefix(&prefix).unwrap();
if let Some((array_pos, suffix)) = suffix_.split_once('.') {
let if_key = suffix.split_once('.').map(|(v, _)| v).unwrap_or(suffix);
if if_key == "if" {
if array_pos != last_array_pos {
if !last_array_pos.is_empty() && !found_then {
config.new_parse_error(
if_block.key,
format!(
"Missing 'then' in 'if' condition {}.",
last_array_pos.parse().unwrap_or(0) + 1,
),
);
return None;
}
if_block.if_then.push(IfThen {
expr: Expression::try_parse(config, item, token_map)?,
then: Expression::default(),
});
found_then = false;
last_array_pos = array_pos;
}
found_if = true;
} else if if_key == "else" {
if found_else.is_empty() {
if found_if {
if_block.default = Expression::try_parse(config, item, token_map)?;
found_else = array_pos;
} else {
config.new_parse_error(if_block.key, "Found 'else' before 'if'");
return None;
}
} else if array_pos != found_else {
config.new_parse_error(if_block.key, "Multiple 'else' found");
return None;
}
} else if if_key == "then" {
if found_else.is_empty() {
if array_pos == last_array_pos {
if !found_then {
if_block.if_then.last_mut().unwrap().then =
Expression::try_parse(config, item, token_map)?;
found_then = true;
}
} else {
config.new_parse_error(if_block.key, "Found 'then' without 'if'");
return None;
}
} else {
config.new_parse_error(if_block.key, "Found 'then' in 'else' block");
return None;
}
}
} else {
config.new_parse_error(
if_block.key,
format!("Invalid property {item:?} found in 'if' block."),
match ExpressionParser::new(Tokenizer::new(&expr.else_, token_map)).parse() {
Ok(expr) => {
if_block.default = expr;
}
Err(err) => {
bp.invalid_property(
id,
property,
&format!("Error parsing 'else' expression: {}", err),
);
return None;
}
}
if !found_if {
None
} else if !found_then {
config.new_parse_error(
if_block.key,
format!(
"Missing 'then' in 'if' condition {}",
last_array_pos.parse().unwrap_or(0) + 1,
),
);
None
} else if found_else.is_empty() {
config.new_parse_error(if_block.key, "Missing 'else'");
None
} else {
Some(if_block)
for (num, match_) in expr.match_.into_iter().enumerate() {
match ExpressionParser::new(Tokenizer::new(&match_.if_, token_map)).parse() {
Ok(if_expr) => {
match ExpressionParser::new(Tokenizer::new(&match_.then, token_map)).parse() {
Ok(then_expr) => {
if_block.if_then.push(IfThen {
expr: if_expr,
then: then_expr,
});
}
Err(err) => {
bp.invalid_property(
id,
property,
&format!(
"Error parsing 'then' expression in condition #{}: {}",
num + 1,
err
),
);
return None;
}
}
}
Err(err) => {
bp.invalid_property(
id,
property,
&format!(
"Error parsing 'if' expression in condition #{}: {}",
num + 1,
err
),
);
return None;
}
}
}
Some(if_block)
}
pub fn into_default(self, key: impl Into<String>) -> IfBlock {
pub fn into_default(self, property: Property) -> IfBlock {
IfBlock {
key: key.into(),
property,
if_then: Default::default(),
default: self.default,
}

View File

@@ -233,20 +233,8 @@ pub enum Token {
#[derive(Debug, Clone)]
pub enum Setting {
Hostname,
ReportDomain,
Domain,
NodeId,
Other(CompactString),
}
impl From<CompactString> for Setting {
fn from(value: CompactString) -> Self {
match value.as_str() {
"server.hostname" => Setting::Hostname,
"report.domain" => Setting::ReportDomain,
"cluster.node-id" => Setting::NodeId,
_ => Setting::Other(value),
}
}
}
impl From<usize> for Variable<'_> {

View File

@@ -89,24 +89,40 @@ impl<'x> Tokenizer<'x> {
self.buf.push(ch);
}
_ => {
let (prev_token, ch) = if ch == b'(' && self.buf.eq(b"matches") {
// Parse regular expressions
let stop_ch = self.find_char(b"\"'")?;
let regex_str = self.parse_string(stop_ch)?;
let regex = Regex::new(&regex_str).map_err(|e| {
format!("Invalid regular expression {:?}: {}", regex_str, e)
})?;
self.has_alpha = false;
self.buf.clear();
self.find_char(b",")?;
(Token::Regex(regex).into(), b'(')
} else if ch == b'(' && self.buf.eq(b"config_get") {
// Parse setting
let stop_ch = self.find_char(b"\"'")?;
let setting_str = self.parse_string(stop_ch)?;
self.has_alpha = false;
self.buf.clear();
(Token::Setting(Setting::from(setting_str)).into(), b'(')
let (prev_token, ch) = if ch == b'(' && !self.buf.is_empty() {
match self.buf.as_slice() {
b"matches" => {
// Parse regular expressions
let stop_ch = self.find_char(b"\"'")?;
let regex_str = self.parse_string(stop_ch)?;
let regex = Regex::new(&regex_str).map_err(|e| {
format!("Invalid regular expression {:?}: {}", regex_str, e)
})?;
self.has_alpha = false;
self.buf.clear();
self.find_char(b",")?;
(Token::Regex(regex).into(), b'(')
}
b"default_domain" => {
self.has_alpha = false;
self.buf.clear();
(Token::Setting(Setting::Domain).into(), b'(')
}
b"node_hostname" => {
self.has_alpha = false;
self.buf.clear();
(Token::Setting(Setting::Hostname).into(), b'(')
}
b"node_id" => {
self.has_alpha = false;
self.buf.clear();
(Token::Setting(Setting::NodeId).into(), b'(')
}
_ => {
self.is_start = false;
(self.parse_buf()?.into(), ch)
}
}
} else if !self.buf.is_empty() {
self.is_start = false;
(self.parse_buf()?.into(), ch)