mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-28 19:28:11 -07:00
global: snapshot
This commit is contained in:
@@ -13,12 +13,14 @@ use crate::{
|
||||
get_node_fields, get_pattern_instance_base, to_camel_case, to_pascal_case,
|
||||
};
|
||||
|
||||
/// Generate JavaScript + JSDoc client from metadata and OpenAPI endpoints
|
||||
/// Generate JavaScript + JSDoc client from metadata and OpenAPI endpoints.
|
||||
///
|
||||
/// `output_path` is the full path to the output file (e.g., "modules/brk-client/index.js").
|
||||
pub fn generate_javascript_client(
|
||||
metadata: &ClientMetadata,
|
||||
endpoints: &[Endpoint],
|
||||
schemas: &TypeSchemas,
|
||||
output_dir: &Path,
|
||||
output_path: &Path,
|
||||
) -> io::Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -44,7 +46,7 @@ pub fn generate_javascript_client(
|
||||
// Generate the main client class with tree and API methods
|
||||
generate_main_client(&mut output, &metadata.catalog, metadata, endpoints);
|
||||
|
||||
fs::write(output_dir.join("client.js"), output)?;
|
||||
fs::write(output_path, output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -446,13 +448,21 @@ fn generate_structural_patterns(
|
||||
// Generate factory function
|
||||
writeln!(output, "/**").unwrap();
|
||||
writeln!(output, " * Create a {} pattern node", pattern.name).unwrap();
|
||||
if pattern.is_generic {
|
||||
writeln!(output, " * @template T").unwrap();
|
||||
}
|
||||
writeln!(output, " * @param {{BrkClientBase}} client").unwrap();
|
||||
if is_parameterizable {
|
||||
writeln!(output, " * @param {{string}} acc - Accumulated metric name").unwrap();
|
||||
} else {
|
||||
writeln!(output, " * @param {{string}} basePath").unwrap();
|
||||
}
|
||||
writeln!(output, " * @returns {{{}}}", pattern.name).unwrap();
|
||||
let return_type = if pattern.is_generic {
|
||||
format!("{}<T>", pattern.name)
|
||||
} else {
|
||||
pattern.name.clone()
|
||||
};
|
||||
writeln!(output, " * @returns {{{}}}", return_type).unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
|
||||
let param_name = if is_parameterizable {
|
||||
@@ -613,11 +623,17 @@ fn field_to_js_type_with_generic_value(
|
||||
};
|
||||
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
// Check if this pattern is generic and we have a value type
|
||||
if metadata.is_pattern_generic(&field.rust_type)
|
||||
&& let Some(vt) = generic_value_type
|
||||
{
|
||||
return format!("{}<{}>", field.rust_type, vt);
|
||||
// Check if this pattern is generic
|
||||
if metadata.is_pattern_generic(&field.rust_type) {
|
||||
if let Some(vt) = generic_value_type {
|
||||
return format!("{}<{}>", field.rust_type, vt);
|
||||
} else if is_generic {
|
||||
// Propagate T when inside a generic pattern
|
||||
return format!("{}<T>", field.rust_type);
|
||||
} else {
|
||||
// Generic pattern without known type - use unknown
|
||||
return format!("{}<unknown>", field.rust_type);
|
||||
}
|
||||
}
|
||||
field.rust_type.clone()
|
||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||
@@ -629,7 +645,6 @@ fn field_to_js_type_with_generic_value(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Generate tree typedefs
|
||||
fn generate_tree_typedefs(output: &mut String, catalog: &TreeNode, metadata: &ClientMetadata) {
|
||||
writeln!(output, "// Catalog tree typedefs\n").unwrap();
|
||||
@@ -666,7 +681,8 @@ fn generate_tree_typedef(
|
||||
.collect();
|
||||
|
||||
// Skip if this matches a pattern (already generated)
|
||||
if pattern_lookup.contains_key(&fields) && pattern_lookup.get(&fields) != Some(&name.to_string())
|
||||
if pattern_lookup.contains_key(&fields)
|
||||
&& pattern_lookup.get(&fields) != Some(&name.to_string())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -680,15 +696,13 @@ fn generate_tree_typedef(
|
||||
writeln!(output, " * @typedef {{Object}} {}", name).unwrap();
|
||||
|
||||
for (field, child_fields) in &fields_with_child_info {
|
||||
// Look up type parameter for generic patterns
|
||||
let generic_value_type = child_fields
|
||||
.as_ref()
|
||||
.and_then(|cf| metadata.get_generic_value_type(&field.rust_type, cf));
|
||||
let js_type = field_to_js_type_with_generic_value(
|
||||
field,
|
||||
metadata,
|
||||
false,
|
||||
generic_value_type.as_deref(),
|
||||
);
|
||||
.and_then(|cf| metadata.get_type_param(cf))
|
||||
.map(String::as_str);
|
||||
let js_type =
|
||||
field_to_js_type_with_generic_value(field, metadata, false, generic_value_type);
|
||||
writeln!(
|
||||
output,
|
||||
" * @property {{{}}} {}",
|
||||
@@ -899,6 +913,11 @@ fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
if let Some(summary) = &endpoint.summary {
|
||||
writeln!(output, " * {}", summary).unwrap();
|
||||
}
|
||||
if let Some(desc) = &endpoint.description
|
||||
&& endpoint.summary.as_ref() != Some(desc)
|
||||
{
|
||||
writeln!(output, " * @description {}", desc).unwrap();
|
||||
}
|
||||
|
||||
for param in &endpoint.path_params {
|
||||
let desc = param.description.as_deref().unwrap_or("");
|
||||
|
||||
@@ -27,14 +27,57 @@
|
||||
//! 2. **Schema collection** - Merges OpenAPI schemas with schemars-generated type schemas
|
||||
//!
|
||||
//! 3. **Code generation** - Produces language-specific clients:
|
||||
//! - Rust: Uses `brk_types` directly, generates structs with lifetimes
|
||||
//! - Rust: Uses `brk_types` directly, generates structs with Arc-based sharing
|
||||
//! - JavaScript: Generates JSDoc-typed ES modules with factory functions
|
||||
//! - Python: Generates typed classes with TypedDict and Generic support
|
||||
|
||||
use std::{collections::btree_map::Entry, fs::create_dir_all, io, path::Path};
|
||||
use std::{collections::btree_map::Entry, fs::create_dir_all, io, path::PathBuf};
|
||||
|
||||
use brk_query::Vecs;
|
||||
|
||||
/// Output path configuration for each language client.
|
||||
///
|
||||
/// Each path should be the full path to the output file, not just a directory.
|
||||
/// Parent directories will be created automatically if they don't exist.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let paths = ClientOutputPaths::new()
|
||||
/// .rust("crates/brk_client/src/lib.rs")
|
||||
/// .javascript("modules/brk-client/index.js")
|
||||
/// .python("packages/brk_client/__init__.py");
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ClientOutputPaths {
|
||||
/// Full path to Rust client file (e.g., "crates/brk_client/src/lib.rs")
|
||||
pub rust: Option<PathBuf>,
|
||||
/// Full path to JavaScript client file (e.g., "modules/brk-client/index.js")
|
||||
pub javascript: Option<PathBuf>,
|
||||
/// Full path to Python client file (e.g., "packages/brk_client/__init__.py")
|
||||
pub python: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ClientOutputPaths {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn rust(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.rust = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn javascript(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.javascript = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn python(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.python = Some(path.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
mod javascript;
|
||||
mod js;
|
||||
mod openapi;
|
||||
@@ -51,8 +94,25 @@ pub use types::*;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
|
||||
/// Generate all client libraries from the query vecs and OpenAPI JSON
|
||||
pub fn generate_clients(vecs: &Vecs, openapi_json: &str, output_dir: &Path) -> io::Result<()> {
|
||||
/// Generate all client libraries from the query vecs and OpenAPI JSON.
|
||||
///
|
||||
/// Uses `ClientOutputPaths` to specify the output file path for each language.
|
||||
/// Only languages with a configured path will be generated.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let paths = ClientOutputPaths::new()
|
||||
/// .rust("crates/brk_client/src/lib.rs")
|
||||
/// .javascript("modules/brk-client/index.js")
|
||||
/// .python("packages/brk_client/__init__.py");
|
||||
///
|
||||
/// generate_clients(&vecs, &openapi_json, &paths)?;
|
||||
/// ```
|
||||
pub fn generate_clients(
|
||||
vecs: &Vecs,
|
||||
openapi_json: &str,
|
||||
output_paths: &ClientOutputPaths,
|
||||
) -> io::Result<()> {
|
||||
let metadata = ClientMetadata::from_vecs(vecs);
|
||||
|
||||
// Parse OpenAPI spec
|
||||
@@ -71,19 +131,28 @@ pub fn generate_clients(vecs: &Vecs, openapi_json: &str, output_dir: &Path) -> i
|
||||
}
|
||||
|
||||
// Generate Rust client (uses real brk_types, no schema conversion needed)
|
||||
let rust_path = output_dir.join("rust");
|
||||
create_dir_all(&rust_path)?;
|
||||
generate_rust_client(&metadata, &endpoints, &rust_path)?;
|
||||
if let Some(rust_path) = &output_paths.rust {
|
||||
if let Some(parent) = rust_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
generate_rust_client(&metadata, &endpoints, rust_path)?;
|
||||
}
|
||||
|
||||
// Generate JavaScript client (needs schemas for type definitions)
|
||||
let js_path = output_dir.join("javascript");
|
||||
create_dir_all(&js_path)?;
|
||||
generate_javascript_client(&metadata, &endpoints, &schemas, &js_path)?;
|
||||
if let Some(js_path) = &output_paths.javascript {
|
||||
if let Some(parent) = js_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
generate_javascript_client(&metadata, &endpoints, &schemas, js_path)?;
|
||||
}
|
||||
|
||||
// Generate Python client (needs schemas for type definitions)
|
||||
let python_path = output_dir.join("python");
|
||||
create_dir_all(&python_path)?;
|
||||
generate_python_client(&metadata, &endpoints, &schemas, &python_path)?;
|
||||
if let Some(python_path) = &output_paths.python {
|
||||
if let Some(parent) = python_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
generate_python_client(&metadata, &endpoints, &schemas, python_path)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -17,8 +17,10 @@ pub struct Endpoint {
|
||||
pub path: String,
|
||||
/// Operation ID (e.g., "getBlockByHash")
|
||||
pub operation_id: Option<String>,
|
||||
/// Summary/description
|
||||
/// Short summary
|
||||
pub summary: Option<String>,
|
||||
/// Detailed description
|
||||
pub description: Option<String>,
|
||||
/// Tags for grouping
|
||||
pub tags: Vec<String>,
|
||||
/// Path parameters
|
||||
@@ -185,10 +187,8 @@ fn extract_endpoint(path: &str, method: &str, operation: &Operation) -> Option<E
|
||||
method: method.to_string(),
|
||||
path: path.to_string(),
|
||||
operation_id: operation.operation_id.clone(),
|
||||
summary: operation
|
||||
.summary
|
||||
.clone()
|
||||
.or_else(|| operation.description.clone()),
|
||||
summary: operation.summary.clone(),
|
||||
description: operation.description.clone(),
|
||||
tags: operation.tags.clone(),
|
||||
path_params,
|
||||
query_params,
|
||||
|
||||
@@ -13,12 +13,14 @@ use crate::{
|
||||
get_pattern_instance_base, is_enum_schema, to_pascal_case, to_snake_case,
|
||||
};
|
||||
|
||||
/// Generate Python client from metadata and OpenAPI endpoints
|
||||
/// Generate Python client from metadata and OpenAPI endpoints.
|
||||
///
|
||||
/// `output_path` is the full path to the output file (e.g., "packages/brk_client/__init__.py").
|
||||
pub fn generate_python_client(
|
||||
metadata: &ClientMetadata,
|
||||
endpoints: &[Endpoint],
|
||||
schemas: &TypeSchemas,
|
||||
output_dir: &Path,
|
||||
output_path: &Path,
|
||||
) -> io::Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -57,7 +59,7 @@ pub fn generate_python_client(
|
||||
// Generate main client with tree and API methods
|
||||
generate_main_client(&mut output, endpoints);
|
||||
|
||||
fs::write(output_dir.join("client.py"), output)?;
|
||||
fs::write(output_path, output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -720,14 +722,16 @@ fn generate_tree_class(
|
||||
for ((field, child_fields_opt), (child_name, child_node)) in
|
||||
fields_with_child_info.iter().zip(children.iter())
|
||||
{
|
||||
// Look up type parameter for generic patterns
|
||||
let generic_value_type = child_fields_opt
|
||||
.as_ref()
|
||||
.and_then(|cf| metadata.get_generic_value_type(&field.rust_type, cf));
|
||||
.and_then(|cf| metadata.get_type_param(cf))
|
||||
.map(String::as_str);
|
||||
let py_type = field_to_python_type_with_generic_value(
|
||||
field,
|
||||
metadata,
|
||||
false,
|
||||
generic_value_type.as_deref(),
|
||||
generic_value_type,
|
||||
);
|
||||
let field_name_py = to_snake_case(&field.name);
|
||||
|
||||
@@ -864,8 +868,19 @@ fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
.unwrap();
|
||||
|
||||
// Docstring
|
||||
if let Some(summary) = &endpoint.summary {
|
||||
writeln!(output, " \"\"\"{}\"\"\"", summary).unwrap();
|
||||
match (&endpoint.summary, &endpoint.description) {
|
||||
(Some(summary), Some(desc)) if summary != desc => {
|
||||
writeln!(output, " \"\"\"{}.", summary.trim_end_matches('.')).unwrap();
|
||||
writeln!(output).unwrap();
|
||||
writeln!(output, " {}\"\"\"", desc).unwrap();
|
||||
}
|
||||
(Some(summary), _) => {
|
||||
writeln!(output, " \"\"\"{}\"\"\"", summary).unwrap();
|
||||
}
|
||||
(None, Some(desc)) => {
|
||||
writeln!(output, " \"\"\"{}\"\"\"", desc).unwrap();
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
|
||||
// Build path
|
||||
|
||||
@@ -12,11 +12,13 @@ use crate::{
|
||||
to_pascal_case, to_snake_case,
|
||||
};
|
||||
|
||||
/// Generate Rust client from metadata and OpenAPI endpoints
|
||||
/// Generate Rust client from metadata and OpenAPI endpoints.
|
||||
///
|
||||
/// `output_path` is the full path to the output file (e.g., "crates/brk_client/src/lib.rs").
|
||||
pub fn generate_rust_client(
|
||||
metadata: &ClientMetadata,
|
||||
endpoints: &[Endpoint],
|
||||
output_dir: &Path,
|
||||
output_path: &Path,
|
||||
) -> io::Result<()> {
|
||||
let mut output = String::new();
|
||||
|
||||
@@ -47,7 +49,7 @@ pub fn generate_rust_client(
|
||||
// Generate main client with API methods
|
||||
generate_main_client(&mut output, endpoints);
|
||||
|
||||
fs::write(output_dir.join("client.rs"), output)?;
|
||||
fs::write(output_path, output)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -55,7 +57,7 @@ pub fn generate_rust_client(
|
||||
fn generate_imports(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"use std::marker::PhantomData;
|
||||
r#"use std::sync::Arc;
|
||||
use serde::de::DeserializeOwned;
|
||||
use brk_types::*;
|
||||
|
||||
@@ -88,14 +90,14 @@ pub type Result<T> = std::result::Result<T, BrkError>;
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrkClientOptions {{
|
||||
pub base_url: String,
|
||||
pub timeout_ms: u64,
|
||||
pub timeout_secs: u64,
|
||||
}}
|
||||
|
||||
impl Default for BrkClientOptions {{
|
||||
fn default() -> Self {{
|
||||
Self {{
|
||||
base_url: "http://localhost:3000".to_string(),
|
||||
timeout_ms: 30000,
|
||||
timeout_secs: 30,
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
@@ -104,36 +106,41 @@ impl Default for BrkClientOptions {{
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BrkClientBase {{
|
||||
base_url: String,
|
||||
client: reqwest::blocking::Client,
|
||||
timeout_secs: u64,
|
||||
}}
|
||||
|
||||
impl BrkClientBase {{
|
||||
/// Create a new client with the given base URL.
|
||||
pub fn new(base_url: impl Into<String>) -> Result<Self> {{
|
||||
let base_url = base_url.into();
|
||||
let client = reqwest::blocking::Client::new();
|
||||
Ok(Self {{ base_url, client }})
|
||||
pub fn new(base_url: impl Into<String>) -> Self {{
|
||||
Self {{
|
||||
base_url: base_url.into(),
|
||||
timeout_secs: 30,
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Create a new client with options.
|
||||
pub fn with_options(options: BrkClientOptions) -> Result<Self> {{
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.timeout(std::time::Duration::from_millis(options.timeout_ms))
|
||||
.build()
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})?;
|
||||
Ok(Self {{
|
||||
pub fn with_options(options: BrkClientOptions) -> Self {{
|
||||
Self {{
|
||||
base_url: options.base_url,
|
||||
client,
|
||||
}})
|
||||
timeout_secs: options.timeout_secs,
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Make a GET request.
|
||||
pub fn get<T: DeserializeOwned>(&self, path: &str) -> Result<T> {{
|
||||
let url = format!("{{}}{{}}", self.base_url, path);
|
||||
self.client
|
||||
.get(&url)
|
||||
let response = minreq::get(&url)
|
||||
.with_timeout(self.timeout_secs)
|
||||
.send()
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})?
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})?;
|
||||
|
||||
if response.status_code >= 400 {{
|
||||
return Err(BrkError {{
|
||||
message: format!("HTTP {{}}", response.status_code),
|
||||
}});
|
||||
}}
|
||||
|
||||
response
|
||||
.json()
|
||||
.map_err(|e| BrkError {{ message: e.to_string() }})
|
||||
}}
|
||||
@@ -148,18 +155,18 @@ fn generate_metric_node(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"/// A metric node that can fetch data for different indexes.
|
||||
pub struct MetricNode<'a, T> {{
|
||||
client: &'a BrkClientBase,
|
||||
pub struct MetricNode<T> {{
|
||||
client: Arc<BrkClientBase>,
|
||||
path: String,
|
||||
_marker: PhantomData<T>,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<'a, T: DeserializeOwned> MetricNode<'a, T> {{
|
||||
pub fn new(client: &'a BrkClientBase, path: String) -> Self {{
|
||||
impl<T: DeserializeOwned> MetricNode<T> {{
|
||||
pub fn new(client: Arc<BrkClientBase>, path: String) -> Self {{
|
||||
Self {{
|
||||
client,
|
||||
path,
|
||||
_marker: PhantomData,
|
||||
_marker: std::marker::PhantomData,
|
||||
}}
|
||||
}}
|
||||
|
||||
@@ -168,7 +175,7 @@ impl<'a, T: DeserializeOwned> MetricNode<'a, T> {{
|
||||
self.client.get(&self.path)
|
||||
}}
|
||||
|
||||
/// Fetch data points within a date range.
|
||||
/// Fetch data points within a range.
|
||||
pub fn get_range(&self, from: &str, to: &str) -> Result<Vec<T>> {{
|
||||
let path = format!("{{}}?from={{}}&to={{}}", self.path, from, to);
|
||||
self.client.get(&path)
|
||||
@@ -195,26 +202,20 @@ fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern]) {
|
||||
pattern.indexes.len()
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, "pub struct {}<'a, T> {{", pattern.name).unwrap();
|
||||
writeln!(output, "pub struct {}<T> {{", pattern.name).unwrap();
|
||||
|
||||
for index in &pattern.indexes {
|
||||
let field_name = index_to_field_name(index);
|
||||
writeln!(output, " pub {}: MetricNode<'a, T>,", field_name).unwrap();
|
||||
writeln!(output, " pub {}: MetricNode<T>,", field_name).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, " _marker: PhantomData<T>,").unwrap();
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate impl block with constructor
|
||||
writeln!(output, "impl<T: DeserializeOwned> {}<T> {{", pattern.name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"impl<'a, T: DeserializeOwned> {}<'a, T> {{",
|
||||
pattern.name
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" pub fn new(client: &'a BrkClientBase, base_path: &str) -> Self {{"
|
||||
" pub fn new(client: Arc<BrkClientBase>, base_path: &str) -> Self {{"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " Self {{").unwrap();
|
||||
@@ -224,13 +225,12 @@ fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern]) {
|
||||
let path_segment = index.serialize_long();
|
||||
writeln!(
|
||||
output,
|
||||
" {}: MetricNode::new(client, format!(\"{{base_path}}/{}\")),",
|
||||
" {}: MetricNode::new(client.clone(), format!(\"{{base_path}}/{}\")),",
|
||||
field_name, path_segment
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, " _marker: PhantomData,").unwrap();
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output, " }}").unwrap();
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
@@ -256,11 +256,7 @@ fn generate_pattern_structs(
|
||||
|
||||
for pattern in patterns {
|
||||
let is_parameterizable = pattern.is_parameterizable();
|
||||
let generic_params = if pattern.is_generic {
|
||||
"<'a, T>"
|
||||
} else {
|
||||
"<'a>"
|
||||
};
|
||||
let generic_params = if pattern.is_generic { "<T>" } else { "" };
|
||||
|
||||
writeln!(output, "/// Pattern struct for repeated tree structure.").unwrap();
|
||||
writeln!(output, "pub struct {}{} {{", pattern.name, generic_params).unwrap();
|
||||
@@ -275,10 +271,15 @@ fn generate_pattern_structs(
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate impl block with constructor
|
||||
let impl_generic = if pattern.is_generic {
|
||||
"<T: DeserializeOwned>"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
writeln!(
|
||||
output,
|
||||
"impl{} {}{} {{",
|
||||
generic_params, pattern.name, generic_params
|
||||
impl_generic, pattern.name, generic_params
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -290,13 +291,13 @@ fn generate_pattern_structs(
|
||||
.unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" pub fn new(client: &'a BrkClientBase, acc: &str) -> Self {{"
|
||||
" pub fn new(client: Arc<BrkClientBase>, acc: &str) -> Self {{"
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" pub fn new(client: &'a BrkClientBase, base_path: &str) -> Self {{"
|
||||
" pub fn new(client: Arc<BrkClientBase>, base_path: &str) -> Self {{"
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -340,7 +341,7 @@ fn generate_parameterized_rust_field(
|
||||
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, {}),",
|
||||
" {}: {}::new(client.clone(), {}),",
|
||||
field_name, field.rust_type, child_acc
|
||||
)
|
||||
.unwrap();
|
||||
@@ -363,14 +364,14 @@ fn generate_parameterized_rust_field(
|
||||
let accessor = metadata.find_index_set_pattern(&field.indexes).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, &{}),",
|
||||
" {}: {}::new(client.clone(), &{}),",
|
||||
field_name, accessor.name, metric_expr
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: MetricNode::new(client, {}),",
|
||||
" {}: MetricNode::new(client.clone(), {}),",
|
||||
field_name, metric_expr
|
||||
)
|
||||
.unwrap();
|
||||
@@ -388,7 +389,7 @@ fn generate_tree_path_rust_field(
|
||||
if metadata.is_pattern_type(&field.rust_type) {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, &format!(\"{{base_path}}/{}\")),",
|
||||
" {}: {}::new(client.clone(), &format!(\"{{base_path}}/{}\")),",
|
||||
field_name, field.rust_type, field.name
|
||||
)
|
||||
.unwrap();
|
||||
@@ -396,14 +397,14 @@ fn generate_tree_path_rust_field(
|
||||
let accessor = metadata.find_index_set_pattern(&field.indexes).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, &format!(\"{{base_path}}/{}\")),",
|
||||
" {}: {}::new(client.clone(), &format!(\"{{base_path}}/{}\")),",
|
||||
field_name, accessor.name, field.name
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: MetricNode::new(client, format!(\"{{base_path}}/{}\")),",
|
||||
" {}: MetricNode::new(client.clone(), format!(\"{{base_path}}/{}\")),",
|
||||
field_name, field.name
|
||||
)
|
||||
.unwrap();
|
||||
@@ -441,15 +442,16 @@ fn field_to_type_annotation_with_generic(
|
||||
if metadata.is_pattern_generic(&field.rust_type)
|
||||
&& let Some(vt) = generic_value_type
|
||||
{
|
||||
return format!("{}<'a, {}>", field.rust_type, vt);
|
||||
return format!("{}<{}>", field.rust_type, vt);
|
||||
}
|
||||
format!("{}<'a>", field.rust_type)
|
||||
// Non-generic pattern has no type params
|
||||
field.rust_type.clone()
|
||||
} else if let Some(accessor) = metadata.find_index_set_pattern(&field.indexes) {
|
||||
// Leaf with a reusable accessor pattern
|
||||
format!("{}<'a, {}>", accessor.name, value_type)
|
||||
format!("{}<{}>", accessor.name, value_type)
|
||||
} else {
|
||||
// Leaf with unique index set - use MetricNode directly
|
||||
format!("MetricNode<'a, {}>", value_type)
|
||||
format!("MetricNode<{}>", value_type)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -501,29 +503,27 @@ fn generate_tree_node(
|
||||
generated.insert(name.to_string());
|
||||
|
||||
writeln!(output, "/// Catalog tree node.").unwrap();
|
||||
writeln!(output, "pub struct {}<'a> {{", name).unwrap();
|
||||
writeln!(output, "pub struct {} {{", name).unwrap();
|
||||
|
||||
for (field, child_fields) in &fields_with_child_info {
|
||||
let field_name = to_snake_case(&field.name);
|
||||
// Look up type parameter for generic patterns
|
||||
let generic_value_type = child_fields
|
||||
.as_ref()
|
||||
.and_then(|cf| metadata.get_generic_value_type(&field.rust_type, cf));
|
||||
let type_annotation = field_to_type_annotation_with_generic(
|
||||
field,
|
||||
metadata,
|
||||
false,
|
||||
generic_value_type.as_deref(),
|
||||
);
|
||||
.and_then(|cf| metadata.get_type_param(cf))
|
||||
.map(String::as_str);
|
||||
let type_annotation =
|
||||
field_to_type_annotation_with_generic(field, metadata, false, generic_value_type);
|
||||
writeln!(output, " pub {}: {},", field_name, type_annotation).unwrap();
|
||||
}
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
// Generate impl block
|
||||
writeln!(output, "impl<'a> {}<'a> {{", name).unwrap();
|
||||
writeln!(output, "impl {} {{", name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" pub fn new(client: &'a BrkClientBase, base_path: &str) -> Self {{"
|
||||
" pub fn new(client: Arc<BrkClientBase>, base_path: &str) -> Self {{"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, " Self {{").unwrap();
|
||||
@@ -538,14 +538,14 @@ fn generate_tree_node(
|
||||
let metric_base = get_pattern_instance_base(child_node, child_name);
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, \"{}\"),",
|
||||
" {}: {}::new(client.clone(), \"{}\"),",
|
||||
field_name, field.rust_type, metric_base
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, &format!(\"{{base_path}}/{}\")),",
|
||||
" {}: {}::new(client.clone(), &format!(\"{{base_path}}/{}\")),",
|
||||
field_name, field.rust_type, field.name
|
||||
)
|
||||
.unwrap();
|
||||
@@ -560,14 +560,14 @@ fn generate_tree_node(
|
||||
if metric_path.contains("{base_path}") {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, &format!(\"{}\")),",
|
||||
" {}: {}::new(client.clone(), &format!(\"{}\")),",
|
||||
field_name, accessor.name, metric_path
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: {}::new(client, \"{}\"),",
|
||||
" {}: {}::new(client.clone(), \"{}\"),",
|
||||
field_name, accessor.name, metric_path
|
||||
)
|
||||
.unwrap();
|
||||
@@ -581,14 +581,14 @@ fn generate_tree_node(
|
||||
if metric_path.contains("{base_path}") {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: MetricNode::new(client, format!(\"{}\")),",
|
||||
" {}: MetricNode::new(client.clone(), format!(\"{}\")),",
|
||||
field_name, metric_path
|
||||
)
|
||||
.unwrap();
|
||||
} else {
|
||||
writeln!(
|
||||
output,
|
||||
" {}: MetricNode::new(client, \"{}\".to_string()),",
|
||||
" {}: MetricNode::new(client.clone(), \"{}\".to_string()),",
|
||||
field_name, metric_path
|
||||
)
|
||||
.unwrap();
|
||||
@@ -625,27 +625,28 @@ fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
||||
output,
|
||||
r#"/// Main BRK client with catalog tree and API methods.
|
||||
pub struct BrkClient {{
|
||||
base: BrkClientBase,
|
||||
base: Arc<BrkClientBase>,
|
||||
tree: CatalogTree,
|
||||
}}
|
||||
|
||||
impl BrkClient {{
|
||||
/// Create a new client with the given base URL.
|
||||
pub fn new(base_url: impl Into<String>) -> Result<Self> {{
|
||||
Ok(Self {{
|
||||
base: BrkClientBase::new(base_url)?,
|
||||
}})
|
||||
pub fn new(base_url: impl Into<String>) -> Self {{
|
||||
let base = Arc::new(BrkClientBase::new(base_url));
|
||||
let tree = CatalogTree::new(base.clone(), "");
|
||||
Self {{ base, tree }}
|
||||
}}
|
||||
|
||||
/// Create a new client with options.
|
||||
pub fn with_options(options: BrkClientOptions) -> Result<Self> {{
|
||||
Ok(Self {{
|
||||
base: BrkClientBase::with_options(options)?,
|
||||
}})
|
||||
pub fn with_options(options: BrkClientOptions) -> Self {{
|
||||
let base = Arc::new(BrkClientBase::with_options(options));
|
||||
let tree = CatalogTree::new(base.clone(), "");
|
||||
Self {{ base, tree }}
|
||||
}}
|
||||
|
||||
/// Get the catalog tree for navigating metrics.
|
||||
pub fn tree(&self) -> CatalogTree<'_> {{
|
||||
CatalogTree::new(&self.base, "")
|
||||
pub fn tree(&self) -> &CatalogTree {{
|
||||
&self.tree
|
||||
}}
|
||||
"#
|
||||
)
|
||||
@@ -678,6 +679,12 @@ fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
endpoint.summary.as_deref().unwrap_or(&method_name)
|
||||
)
|
||||
.unwrap();
|
||||
if let Some(desc) = &endpoint.description
|
||||
&& endpoint.summary.as_ref() != Some(desc)
|
||||
{
|
||||
writeln!(output, " ///").unwrap();
|
||||
writeln!(output, " /// {}", desc).unwrap();
|
||||
}
|
||||
|
||||
// Build method signature
|
||||
let params = build_method_params(endpoint);
|
||||
|
||||
@@ -39,14 +39,16 @@ pub struct ClientMetadata {
|
||||
/// Index set patterns - sets of indexes that appear together on metrics
|
||||
pub index_set_patterns: Vec<IndexSetPattern>,
|
||||
/// Maps concrete field signatures to pattern names
|
||||
pub concrete_to_pattern: HashMap<Vec<PatternField>, String>,
|
||||
concrete_to_pattern: HashMap<Vec<PatternField>, String>,
|
||||
/// Maps concrete field signatures to their type parameter (for generic patterns)
|
||||
concrete_to_type_param: HashMap<Vec<PatternField>, String>,
|
||||
}
|
||||
|
||||
impl ClientMetadata {
|
||||
/// Extract metadata from brk_query::Vecs.
|
||||
pub fn from_vecs(vecs: &Vecs) -> Self {
|
||||
let catalog = vecs.catalog().clone();
|
||||
let (structural_patterns, concrete_to_pattern) =
|
||||
let (structural_patterns, concrete_to_pattern, concrete_to_type_param) =
|
||||
patterns::detect_structural_patterns(&catalog);
|
||||
let (used_indexes, index_set_patterns) = tree::detect_index_patterns(&catalog);
|
||||
|
||||
@@ -56,6 +58,7 @@ impl ClientMetadata {
|
||||
used_indexes,
|
||||
index_set_patterns,
|
||||
concrete_to_pattern,
|
||||
concrete_to_type_param,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,19 +84,9 @@ impl ClientMetadata {
|
||||
self.find_pattern(name).is_some_and(|p| p.is_generic)
|
||||
}
|
||||
|
||||
/// Extract the value type from concrete fields for a generic pattern.
|
||||
pub fn get_generic_value_type(
|
||||
&self,
|
||||
pattern_name: &str,
|
||||
fields: &[PatternField],
|
||||
) -> Option<String> {
|
||||
if !self.is_pattern_generic(pattern_name) {
|
||||
return None;
|
||||
}
|
||||
fields
|
||||
.iter()
|
||||
.find(|f| f.is_leaf())
|
||||
.map(|f| extract_inner_type(&f.rust_type))
|
||||
/// Get the type parameter for a generic pattern given its concrete fields.
|
||||
pub fn get_type_param(&self, fields: &[PatternField]) -> Option<&String> {
|
||||
self.concrete_to_type_param.get(fields)
|
||||
}
|
||||
|
||||
/// Build a lookup map from field signatures to pattern names.
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
//! Pattern detection for structural patterns in the metric tree.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
use brk_types::TreeNode;
|
||||
|
||||
use super::{
|
||||
case::to_pascal_case, schema::schema_to_json_type, FieldNamePosition, PatternField,
|
||||
StructuralPattern,
|
||||
FieldNamePosition, PatternField, StructuralPattern, case::to_pascal_case,
|
||||
schema::schema_to_json_type,
|
||||
tree::{get_first_leaf_name, get_node_fields},
|
||||
};
|
||||
|
||||
/// Detect structural patterns in the tree using a bottom-up approach.
|
||||
/// Returns (patterns, concrete_to_pattern_mapping).
|
||||
/// Returns (patterns, concrete_to_pattern, concrete_to_type_param).
|
||||
pub fn detect_structural_patterns(
|
||||
tree: &TreeNode,
|
||||
) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
|
||||
) -> (
|
||||
Vec<StructuralPattern>,
|
||||
HashMap<Vec<PatternField>, String>,
|
||||
HashMap<Vec<PatternField>, String>,
|
||||
) {
|
||||
let mut signature_to_pattern: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
let mut signature_counts: HashMap<Vec<PatternField>, usize> = HashMap::new();
|
||||
let mut normalized_to_name: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
@@ -29,8 +34,9 @@ pub fn detect_structural_patterns(
|
||||
&mut name_counts,
|
||||
);
|
||||
|
||||
// Identify generic patterns
|
||||
let (generic_patterns, generic_mappings) = detect_generic_patterns(&signature_to_pattern);
|
||||
// Identify generic patterns (also extracts type params)
|
||||
let (generic_patterns, generic_mappings, type_mappings) =
|
||||
detect_generic_patterns(&signature_to_pattern);
|
||||
|
||||
// Build non-generic patterns: signatures appearing 2+ times that weren't merged into generics
|
||||
let mut patterns: Vec<StructuralPattern> = signature_to_pattern
|
||||
@@ -64,33 +70,43 @@ pub fn detect_structural_patterns(
|
||||
analyze_pattern_field_positions(tree, &mut patterns, &pattern_lookup);
|
||||
|
||||
patterns.sort_by(|a, b| b.fields.len().cmp(&a.fields.len()));
|
||||
(patterns, concrete_to_pattern)
|
||||
(patterns, concrete_to_pattern, type_mappings)
|
||||
}
|
||||
|
||||
/// Detect generic patterns by grouping signatures by their normalized form.
|
||||
/// Returns (patterns, concrete_to_pattern, concrete_to_type_param).
|
||||
fn detect_generic_patterns(
|
||||
signature_to_pattern: &HashMap<Vec<PatternField>, String>,
|
||||
) -> (Vec<StructuralPattern>, HashMap<Vec<PatternField>, String>) {
|
||||
let mut normalized_groups: HashMap<Vec<PatternField>, Vec<(Vec<PatternField>, String)>> =
|
||||
HashMap::new();
|
||||
) -> (
|
||||
Vec<StructuralPattern>,
|
||||
HashMap<Vec<PatternField>, String>,
|
||||
HashMap<Vec<PatternField>, String>,
|
||||
) {
|
||||
// Group by normalized form, tracking the extracted type for each concrete signature
|
||||
let mut normalized_groups: HashMap<
|
||||
Vec<PatternField>,
|
||||
Vec<(Vec<PatternField>, String, String)>,
|
||||
> = HashMap::new();
|
||||
|
||||
for (fields, name) in signature_to_pattern {
|
||||
if let Some(normalized) = normalize_fields_for_generic(fields) {
|
||||
if let Some((normalized, extracted_type)) = normalize_fields_for_generic(fields) {
|
||||
normalized_groups
|
||||
.entry(normalized)
|
||||
.or_default()
|
||||
.push((fields.clone(), name.clone()));
|
||||
.push((fields.clone(), name.clone(), extracted_type));
|
||||
}
|
||||
}
|
||||
|
||||
let mut patterns = Vec::new();
|
||||
let mut mappings: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
let mut pattern_mappings: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
let mut type_mappings: HashMap<Vec<PatternField>, String> = HashMap::new();
|
||||
|
||||
for (normalized_fields, group) in normalized_groups {
|
||||
if group.len() >= 2 {
|
||||
let generic_name = group[0].1.clone();
|
||||
for (concrete_fields, _) in &group {
|
||||
mappings.insert(concrete_fields.clone(), generic_name.clone());
|
||||
for (concrete_fields, _, extracted_type) in &group {
|
||||
pattern_mappings.insert(concrete_fields.clone(), generic_name.clone());
|
||||
type_mappings.insert(concrete_fields.clone(), extracted_type.clone());
|
||||
}
|
||||
patterns.push(StructuralPattern {
|
||||
name: generic_name,
|
||||
@@ -101,11 +117,12 @@ fn detect_generic_patterns(
|
||||
}
|
||||
}
|
||||
|
||||
(patterns, mappings)
|
||||
(patterns, pattern_mappings, type_mappings)
|
||||
}
|
||||
|
||||
/// Normalize fields by replacing concrete value types with "T".
|
||||
fn normalize_fields_for_generic(fields: &[PatternField]) -> Option<Vec<PatternField>> {
|
||||
/// Returns (normalized_fields, extracted_type) where extracted_type is the concrete type replaced.
|
||||
fn normalize_fields_for_generic(fields: &[PatternField]) -> Option<(Vec<PatternField>, String)> {
|
||||
let leaf_types: Vec<&str> = fields
|
||||
.iter()
|
||||
.filter(|f| f.is_leaf())
|
||||
@@ -137,7 +154,7 @@ fn normalize_fields_for_generic(fields: &[PatternField]) -> Option<Vec<PatternFi
|
||||
})
|
||||
.collect();
|
||||
|
||||
Some(normalized)
|
||||
Some((normalized, super::extract_inner_type(first_type)))
|
||||
}
|
||||
|
||||
/// Recursively resolve branch patterns bottom-up.
|
||||
@@ -266,7 +283,7 @@ fn collect_pattern_instances(
|
||||
return;
|
||||
};
|
||||
|
||||
let fields = get_node_fields_for_analysis(children, pattern_lookup);
|
||||
let fields = get_node_fields(children, pattern_lookup);
|
||||
if let Some(pattern_name) = pattern_lookup.get(&fields) {
|
||||
for (field_name, child_node) in children {
|
||||
if let TreeNode::Leaf(leaf) = child_node {
|
||||
@@ -283,7 +300,7 @@ fn collect_pattern_instances(
|
||||
let child_accumulated = match child_node {
|
||||
TreeNode::Leaf(leaf) => leaf.name().to_string(),
|
||||
TreeNode::Branch(_) => {
|
||||
if let Some(desc_leaf_name) = get_descendant_leaf_name(child_node) {
|
||||
if let Some(desc_leaf_name) = get_first_leaf_name(child_node) {
|
||||
infer_accumulated_name(accumulated_name, field_name, &desc_leaf_name)
|
||||
} else if accumulated_name.is_empty() {
|
||||
field_name.clone()
|
||||
@@ -296,13 +313,6 @@ fn collect_pattern_instances(
|
||||
}
|
||||
}
|
||||
|
||||
fn get_descendant_leaf_name(node: &TreeNode) -> Option<String> {
|
||||
match node {
|
||||
TreeNode::Leaf(leaf) => Some(leaf.name().to_string()),
|
||||
TreeNode::Branch(children) => children.values().find_map(get_descendant_leaf_name),
|
||||
}
|
||||
}
|
||||
|
||||
fn infer_accumulated_name(parent_acc: &str, field_name: &str, descendant_leaf: &str) -> String {
|
||||
if let Some(pos) = descendant_leaf.find(field_name) {
|
||||
if pos == 0 {
|
||||
@@ -324,40 +334,6 @@ fn infer_accumulated_name(parent_acc: &str, field_name: &str, descendant_leaf: &
|
||||
}
|
||||
}
|
||||
|
||||
fn get_node_fields_for_analysis(
|
||||
children: &BTreeMap<String, TreeNode>,
|
||||
pattern_lookup: &HashMap<Vec<PatternField>, String>,
|
||||
) -> Vec<PatternField> {
|
||||
let mut fields: Vec<PatternField> = children
|
||||
.iter()
|
||||
.map(|(name, node)| {
|
||||
let (rust_type, json_type, indexes) = match node {
|
||||
TreeNode::Leaf(leaf) => (
|
||||
leaf.value_type().to_string(),
|
||||
schema_to_json_type(&leaf.schema),
|
||||
leaf.indexes().clone(),
|
||||
),
|
||||
TreeNode::Branch(grandchildren) => {
|
||||
let child_fields = get_node_fields_for_analysis(grandchildren, pattern_lookup);
|
||||
let pattern_name = pattern_lookup
|
||||
.get(&child_fields)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
(pattern_name.clone(), pattern_name, BTreeSet::new())
|
||||
}
|
||||
};
|
||||
PatternField {
|
||||
name: name.clone(),
|
||||
rust_type,
|
||||
json_type,
|
||||
indexes,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
fields.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
fields
|
||||
}
|
||||
|
||||
fn analyze_field_positions_from_instances(
|
||||
instances: &[(String, String, String)],
|
||||
) -> HashMap<String, FieldNamePosition> {
|
||||
|
||||
@@ -59,42 +59,6 @@ pub fn get_node_fields(
|
||||
fields
|
||||
}
|
||||
|
||||
/// Like get_node_fields but takes a parent name for generating child pattern names.
|
||||
pub fn get_node_fields_with_parent(
|
||||
children: &BTreeMap<String, TreeNode>,
|
||||
parent_name: &str,
|
||||
pattern_lookup: &HashMap<Vec<PatternField>, String>,
|
||||
) -> Vec<PatternField> {
|
||||
let mut fields: Vec<PatternField> = children
|
||||
.iter()
|
||||
.map(|(name, node)| {
|
||||
let (rust_type, json_type, indexes) = match node {
|
||||
TreeNode::Leaf(leaf) => (
|
||||
leaf.value_type().to_string(),
|
||||
schema_to_json_type(&leaf.schema),
|
||||
leaf.indexes().clone(),
|
||||
),
|
||||
TreeNode::Branch(grandchildren) => {
|
||||
let child_fields = get_node_fields(grandchildren, pattern_lookup);
|
||||
let pattern_name = pattern_lookup
|
||||
.get(&child_fields)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| format!("{}_{}", parent_name, to_pascal_case(name)));
|
||||
(pattern_name.clone(), pattern_name, BTreeSet::new())
|
||||
}
|
||||
};
|
||||
PatternField {
|
||||
name: name.clone(),
|
||||
rust_type,
|
||||
json_type,
|
||||
indexes,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
fields.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
fields
|
||||
}
|
||||
|
||||
/// Get fields with child field information for generic pattern lookup.
|
||||
/// Returns (field, child_fields) pairs where child_fields is Some for branches.
|
||||
pub fn get_fields_with_child_info(
|
||||
|
||||
Reference in New Issue
Block a user