clients: snapshot

This commit is contained in:
nym21
2026-01-14 01:20:25 +01:00
parent 3a836ab0f4
commit 25a0ebe51e
10 changed files with 7218 additions and 3920 deletions

View File

@@ -120,6 +120,9 @@ pub struct PatternBaseResult {
/// Whether an outlier child was excluded to find the pattern.
/// If true, pattern factory should not be used.
pub has_outlier: bool,
/// Whether this instance uses suffix mode (common prefix) or prefix mode (common suffix).
/// Used to check compatibility with the pattern's mode.
pub is_suffix_mode: bool,
}
/// Get the metric base for a pattern instance by analyzing direct children.
@@ -137,12 +140,17 @@ pub fn get_pattern_instance_base(node: &TreeNode) -> PatternBaseResult {
return PatternBaseResult {
base: String::new(),
has_outlier: false,
is_suffix_mode: true, // default
};
}
// Try to find common base from leaf names
if let Some((base, has_outlier)) = try_find_base(&child_names, false) {
return PatternBaseResult { base, has_outlier };
if let Some(result) = try_find_base(&child_names, false) {
return PatternBaseResult {
base: result.base,
has_outlier: result.has_outlier,
is_suffix_mode: result.is_suffix_mode,
};
}
// If no common pattern found and we have enough children, try excluding outliers
@@ -155,10 +163,11 @@ pub fn get_pattern_instance_base(node: &TreeNode) -> PatternBaseResult {
.map(|(_, v)| v.clone())
.collect();
if let Some((base, _)) = try_find_base(&filtered, true) {
if let Some(result) = try_find_base(&filtered, true) {
return PatternBaseResult {
base,
base: result.base,
has_outlier: true,
is_suffix_mode: result.is_suffix_mode,
};
}
}
@@ -169,24 +178,40 @@ pub fn get_pattern_instance_base(node: &TreeNode) -> PatternBaseResult {
PatternBaseResult {
base: String::new(),
has_outlier: false,
is_suffix_mode: true, // default
}
}
/// Result of try_find_base: base name, has_outlier flag, and is_suffix_mode flag.
struct FindBaseResult {
base: String,
has_outlier: bool,
is_suffix_mode: bool,
}
/// Try to find a common base from child names using prefix/suffix detection.
/// Returns Some((base, has_outlier)) if found.
fn try_find_base(child_names: &[(String, String)], is_outlier_attempt: bool) -> Option<(String, bool)> {
/// Returns Some(FindBaseResult) if found.
fn try_find_base(child_names: &[(String, String)], is_outlier_attempt: bool) -> Option<FindBaseResult> {
let leaf_names: Vec<&str> = child_names.iter().map(|(_, n)| n.as_str()).collect();
// Try common prefix first (suffix mode)
if let Some(prefix) = find_common_prefix(&leaf_names) {
let base = prefix.trim_end_matches('_').to_string();
return Some((base, is_outlier_attempt));
return Some(FindBaseResult {
base,
has_outlier: is_outlier_attempt,
is_suffix_mode: true,
});
}
// Try common suffix (prefix mode)
if let Some(suffix) = find_common_suffix(&leaf_names) {
let base = suffix.trim_start_matches('_').to_string();
return Some((base, is_outlier_attempt));
return Some(FindBaseResult {
base,
has_outlier: is_outlier_attempt,
is_suffix_mode: false,
});
}
None
@@ -409,4 +434,64 @@ mod tests {
assert_eq!(result.base, "sopr");
assert!(result.has_outlier); // Pattern factory should NOT be used (inline instead)
}
#[test]
fn test_get_pattern_instance_base_suffix_mode_price_ago() {
// Simulates price_ago pattern: price_1d_ago, price_1w_ago, price_10y_ago
// Common prefix is "price_", so this is suffix mode
let tree = make_branch(vec![
("_1d", make_leaf("price_1d_ago")),
("_1w", make_leaf("price_1w_ago")),
("_1m", make_leaf("price_1m_ago")),
("_10y", make_leaf("price_10y_ago")),
]);
let result = get_pattern_instance_base(&tree);
assert_eq!(result.base, "price");
assert!(result.is_suffix_mode); // Suffix mode: _m(base, "1d_ago")
assert!(!result.has_outlier);
}
#[test]
fn test_get_pattern_instance_base_prefix_mode_price_returns() {
// Simulates price_returns pattern: 1d_price_returns, 1w_price_returns, 10y_price_returns
// Common suffix is "_price_returns", so this is prefix mode
let tree = make_branch(vec![
("_1d", make_leaf("1d_price_returns")),
("_1w", make_leaf("1w_price_returns")),
("_1m", make_leaf("1m_price_returns")),
("_10y", make_leaf("10y_price_returns")),
]);
let result = get_pattern_instance_base(&tree);
assert_eq!(result.base, "price_returns");
assert!(!result.is_suffix_mode); // Prefix mode: _p("1d_", base)
assert!(!result.has_outlier);
}
#[test]
fn test_mode_detection_distinguishes_similar_structures() {
// Two patterns with identical structure but different naming conventions
// should have different modes detected
// Suffix mode pattern
let suffix_tree = make_branch(vec![
("_1y", make_leaf("lump_sum_1y")),
("_2y", make_leaf("lump_sum_2y")),
("_5y", make_leaf("lump_sum_5y")),
]);
let suffix_result = get_pattern_instance_base(&suffix_tree);
assert_eq!(suffix_result.base, "lump_sum");
assert!(suffix_result.is_suffix_mode);
// Prefix mode pattern (same structure, different naming)
let prefix_tree = make_branch(vec![
("_1y", make_leaf("1y_returns")),
("_2y", make_leaf("2y_returns")),
("_5y", make_leaf("5y_returns")),
]);
let prefix_result = get_pattern_instance_base(&prefix_tree);
assert_eq!(prefix_result.base, "returns");
assert!(!prefix_result.is_suffix_mode);
}
}

View File

@@ -55,12 +55,17 @@ pub fn prepare_tree_node<'a>(
.map(|(f, _)| f.clone())
.collect();
// Skip if this matches a parameterizable pattern AND has no outlier
// Skip if this matches a parameterizable pattern AND has no outlier AND mode matches
let base_result = get_pattern_instance_base(node);
let mode_matches = pattern_lookup
.get(&fields)
.and_then(|name| metadata.find_pattern(name))
.is_none_or(|p| p.is_suffix_mode() == base_result.is_suffix_mode);
if let Some(pattern_name) = pattern_lookup.get(&fields)
&& pattern_name != name
&& metadata.is_parameterizable(pattern_name)
&& !base_result.has_outlier
&& mode_matches
{
return None;
}
@@ -84,9 +89,16 @@ pub fn prepare_tree_node<'a>(
.as_ref()
.is_some_and(|cf| metadata.matches_pattern(cf));
// Check if the pattern mode matches the instance mode
let mode_matches = child_fields
.as_ref()
.and_then(|cf| metadata.find_pattern_by_fields(cf))
.is_none_or(|p| p.is_suffix_mode() == base_result.is_suffix_mode);
// should_inline determines if we generate an inline struct type
// We inline only if it's a branch AND doesn't match any pattern
let should_inline = !is_leaf && !matches_any_pattern;
// We inline if: it's a branch AND (doesn't match any pattern OR mode doesn't match OR has outlier)
let should_inline =
!is_leaf && (!matches_any_pattern || !mode_matches || base_result.has_outlier);
// Inline type name (only used when should_inline is true)
let inline_type_name = if should_inline {

View File

@@ -6,9 +6,8 @@ use std::fmt::Write;
use brk_types::TreeNode;
use crate::{
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField,
generate_leaf_field, get_first_leaf_name, get_node_fields, get_pattern_instance_base,
infer_accumulated_name, prepare_tree_node, to_camel_case,
ClientMetadata, Endpoint, GenericSyntax, JavaScriptSyntax, PatternField, generate_leaf_field,
prepare_tree_node, to_camel_case,
};
use super::api::generate_api_methods;
@@ -121,15 +120,36 @@ pub fn generate_main_client(
writeln!(output, " */").unwrap();
writeln!(output, " _buildTree(basePath) {{").unwrap();
writeln!(output, " return {{").unwrap();
generate_tree_initializer(output, catalog, "", 3, &pattern_lookup, metadata);
let mut generated = HashSet::new();
generate_tree_initializer(
output,
catalog,
"MetricsTree",
3,
&pattern_lookup,
metadata,
&mut generated,
);
writeln!(output, " }};").unwrap();
writeln!(output, " }}\n").unwrap();
writeln!(output, " /**").unwrap();
writeln!(output, " * Create a dynamic metric endpoint builder for any metric/index combination.").unwrap();
writeln!(
output,
" * Create a dynamic metric endpoint builder for any metric/index combination."
)
.unwrap();
writeln!(output, " *").unwrap();
writeln!(output, " * Use this for programmatic access when the metric name is determined at runtime.").unwrap();
writeln!(output, " * For type-safe access, use the `metrics` tree instead.").unwrap();
writeln!(
output,
" * Use this for programmatic access when the metric name is determined at runtime."
)
.unwrap();
writeln!(
output,
" * For type-safe access, use the `metrics` tree instead."
)
.unwrap();
writeln!(output, " *").unwrap();
writeln!(output, " * @param {{string}} metric - The metric name").unwrap();
writeln!(output, " * @param {{Index}} index - The index name").unwrap();
@@ -149,66 +169,55 @@ pub fn generate_main_client(
fn generate_tree_initializer(
output: &mut String,
node: &TreeNode,
accumulated_name: &str,
name: &str,
indent: usize,
pattern_lookup: &std::collections::HashMap<Vec<PatternField>, String>,
metadata: &ClientMetadata,
generated: &mut HashSet<String>,
) {
let indent_str = " ".repeat(indent);
let Some(ctx) = prepare_tree_node(node, name, pattern_lookup, metadata, generated) else {
return;
};
let syntax = JavaScriptSyntax;
if let TreeNode::Branch(children) = node {
for (child_name, child_node) in children.iter() {
match child_node {
TreeNode::Leaf(leaf) => {
// Use shared helper for leaf fields
generate_leaf_field(
output,
&syntax,
"this",
child_name,
leaf,
metadata,
&indent_str,
);
}
TreeNode::Branch(grandchildren) => {
let field_name = to_camel_case(child_name);
let child_fields = get_node_fields(grandchildren, pattern_lookup);
// Use pattern factory if ANY pattern matches (not just parameterizable)
let pattern_name = pattern_lookup.get(&child_fields);
for child in &ctx.children {
let field_name = to_camel_case(child.name);
let base_result = get_pattern_instance_base(child_node);
// Use pattern factory only if no outlier was detected
if let Some(pattern_name) = pattern_name.filter(|_| !base_result.has_outlier) {
writeln!(
output,
"{}{}: create{}(this, '{}'),",
indent_str, field_name, pattern_name, base_result.base
)
.unwrap();
} else {
let child_acc =
infer_child_accumulated_name(child_node, accumulated_name, child_name);
writeln!(output, "{}{}: {{", indent_str, field_name).unwrap();
generate_tree_initializer(
output,
child_node,
&child_acc,
indent + 1,
pattern_lookup,
metadata,
);
writeln!(output, "{}}},", indent_str).unwrap();
}
}
if child.is_leaf {
if let TreeNode::Leaf(leaf) = child.node {
generate_leaf_field(
output,
&syntax,
"this",
child.name,
leaf,
metadata,
&indent_str,
);
}
} else if child.should_inline {
// Inline object
writeln!(output, "{}{}: {{", indent_str, field_name).unwrap();
generate_tree_initializer(
output,
child.node,
&child.inline_type_name,
indent + 1,
pattern_lookup,
metadata,
generated,
);
writeln!(output, "{}}},", indent_str).unwrap();
} else {
// Use pattern factory
writeln!(
output,
"{}{}: create{}(this, '{}'),",
indent_str, field_name, child.field.rust_type, child.base_result.base
)
.unwrap();
}
}
}
fn infer_child_accumulated_name(node: &TreeNode, parent_acc: &str, field_name: &str) -> String {
let leaf_name = get_first_leaf_name(node).unwrap_or_default();
infer_accumulated_name(parent_acc, field_name, &leaf_name)
}

View File

@@ -392,7 +392,12 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
writeln!(output, "impl<T: DeserializeOwned> {}<T> {{", by_name).unwrap();
for index in &pattern.indexes {
let method_name = index_to_field_name(index);
writeln!(output, " pub fn {}(&self) -> MetricEndpointBuilder<T> {{", method_name).unwrap();
writeln!(
output,
" pub fn {}(&self) -> MetricEndpointBuilder<T> {{",
method_name
)
.unwrap();
writeln!(
output,
" MetricEndpointBuilder::new(self.client.clone(), self.name.clone(), Index::{})",
@@ -425,7 +430,12 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
writeln!(output, " let name: Arc<str> = name.into();").unwrap();
writeln!(output, " Self {{").unwrap();
writeln!(output, " name: name.clone(),").unwrap();
writeln!(output, " by: {} {{ client, name, _marker: std::marker::PhantomData }}", by_name).unwrap();
writeln!(
output,
" by: {} {{ client, name, _marker: std::marker::PhantomData }}",
by_name
)
.unwrap();
writeln!(output, " }}").unwrap();
writeln!(output, " }}").unwrap();
writeln!(output).unwrap();
@@ -436,7 +446,12 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
writeln!(output, "}}\n").unwrap();
// Implement AnyMetricPattern trait
writeln!(output, "impl<T> AnyMetricPattern for {}<T> {{", pattern.name).unwrap();
writeln!(
output,
"impl<T> AnyMetricPattern for {}<T> {{",
pattern.name
)
.unwrap();
writeln!(output, " fn name(&self) -> &str {{").unwrap();
writeln!(output, " &self.name").unwrap();
writeln!(output, " }}").unwrap();
@@ -451,12 +466,26 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
writeln!(output, "}}\n").unwrap();
// Implement MetricPattern<T> trait
writeln!(output, "impl<T: DeserializeOwned> MetricPattern<T> for {}<T> {{", pattern.name).unwrap();
writeln!(output, " fn get(&self, index: Index) -> Option<MetricEndpointBuilder<T>> {{").unwrap();
writeln!(
output,
"impl<T: DeserializeOwned> MetricPattern<T> for {}<T> {{",
pattern.name
)
.unwrap();
writeln!(
output,
" fn get(&self, index: Index) -> Option<MetricEndpointBuilder<T>> {{"
)
.unwrap();
writeln!(output, " match index {{").unwrap();
for index in &pattern.indexes {
let method_name = index_to_field_name(index);
writeln!(output, " Index::{} => Some(self.by.{}()),", index, method_name).unwrap();
writeln!(
output,
" Index::{} => Some(self.by.{}()),",
index, method_name
)
.unwrap();
}
writeln!(output, " _ => None,").unwrap();
writeln!(output, " }}").unwrap();
@@ -486,8 +515,12 @@ pub fn generate_pattern_structs(
for field in &pattern.fields {
let field_name = to_snake_case(&field.name);
let type_annotation =
metadata.field_type_annotation(field, pattern.is_generic, None, GenericSyntax::RUST);
let type_annotation = metadata.field_type_annotation(
field,
pattern.is_generic,
None,
GenericSyntax::RUST,
);
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
}

View File

@@ -95,6 +95,14 @@ impl ClientMetadata {
|| self.structural_patterns.iter().any(|p| p.fields == fields)
}
/// Find a pattern by its fields.
pub fn find_pattern_by_fields(&self, fields: &[PatternField]) -> Option<&StructuralPattern> {
self.concrete_to_pattern
.get(fields)
.and_then(|name| self.find_pattern(name))
.or_else(|| self.structural_patterns.iter().find(|p| p.fields == fields))
}
/// Resolve the type name for a tree field.
/// If the field matches ANY pattern (parameterizable or not), returns pattern type.
/// Otherwise returns the inline type name (parent_child format).