mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-30 20:28:11 -07:00
global: snapshot
This commit is contained in:
@@ -162,11 +162,21 @@ pub fn analyze_pattern_level(child_names: &[(String, String)]) -> PatternAnalysi
|
||||
positions.insert(field_name.clone(), FieldNamePosition::Identity);
|
||||
}
|
||||
|
||||
// Use the first name as base (they're all independent)
|
||||
let base = child_names
|
||||
.first()
|
||||
.map(|(_, n)| n.clone())
|
||||
.unwrap_or_default();
|
||||
// Check if all fields are "true Identity" (field_name == effective_name)
|
||||
// In that case, the base should be empty since metrics are accessed directly by field name
|
||||
let all_true_identity = child_names
|
||||
.iter()
|
||||
.all(|(field_name, effective)| field_name == effective);
|
||||
|
||||
let base = if all_true_identity {
|
||||
String::new()
|
||||
} else {
|
||||
// Use the first name as base (they're all independent but have different names)
|
||||
child_names
|
||||
.first()
|
||||
.map(|(_, n)| n.clone())
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
PatternAnalysis {
|
||||
common: CommonDenominator::None,
|
||||
|
||||
@@ -67,43 +67,128 @@ class BrkError extends Error {{
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricEndpoint
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} get - Fetch all data points
|
||||
* @property {{(start?: number, end?: number, onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} range - Fetch data in range
|
||||
* @typedef {{Object}} MetricEndpointBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} first - Fetch first n data points
|
||||
* @property {{(n: number) => RangeBuilder<T>}} last - Fetch last n data points
|
||||
* @property {{(start: number, end: number) => RangeBuilder<T>}} range - Set explicit range [start, end)
|
||||
* @property {{(start: number) => FromBuilder<T>}} from - Set start position, chain with take() or to()
|
||||
* @property {{(end: number) => ToBuilder<T>}} to - Set end position, chain with takeLast() or from()
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON (all data)
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV (all data)
|
||||
* @property {{string}} path - The endpoint path
|
||||
*/
|
||||
/** @typedef {{MetricEndpoint<unknown>}} AnyMetricEndpoint */
|
||||
/** @typedef {{MetricEndpointBuilder<unknown>}} AnyMetricEndpointBuilder */
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} FromBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} take - Take n items from start position
|
||||
* @property {{(end: number) => RangeBuilder<T>}} to - Set end position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} ToBuilder
|
||||
* @property {{(n: number) => RangeBuilder<T>}} takeLast - Take last n items before end position
|
||||
* @property {{(start: number) => RangeBuilder<T>}} from - Set start position
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} RangeBuilder
|
||||
* @property {{(onUpdate?: (value: MetricData<T>) => void) => Promise<MetricData<T>>}} json - Execute and return JSON
|
||||
* @property {{() => Promise<string>}} csv - Execute and return CSV
|
||||
*/
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @typedef {{Object}} MetricPattern
|
||||
* @property {{string}} name - The metric name
|
||||
* @property {{Partial<Record<Index, MetricEndpoint<T>>>}} by - Index endpoints (lazy getters)
|
||||
* @property {{Partial<Record<Index, MetricEndpointBuilder<T>>>}} by - Index endpoints (lazy getters)
|
||||
* @property {{() => Index[]}} indexes - Get the list of available indexes
|
||||
* @property {{(index: Index) => MetricEndpoint<T>|undefined}} get - Get an endpoint for a specific index
|
||||
* @property {{(index: Index) => MetricEndpointBuilder<T>|undefined}} get - Get an endpoint for a specific index
|
||||
*/
|
||||
|
||||
/** @typedef {{MetricPattern<unknown>}} AnyMetricPattern */
|
||||
|
||||
/**
|
||||
* Create an endpoint for a metric index.
|
||||
* Create a metric endpoint builder with typestate pattern.
|
||||
* @template T
|
||||
* @param {{BrkClientBase}} client
|
||||
* @param {{string}} name - The metric vec name
|
||||
* @param {{Index}} index - The index name
|
||||
* @returns {{MetricEndpoint<T>}}
|
||||
* @returns {{MetricEndpointBuilder<T>}}
|
||||
*/
|
||||
function _endpoint(client, name, index) {{
|
||||
const p = `/api/metric/${{name}}/${{index}}`;
|
||||
return {{
|
||||
get: (onUpdate) => client.getJson(p, onUpdate),
|
||||
range: (start, end, onUpdate) => {{
|
||||
const params = new URLSearchParams();
|
||||
if (start !== undefined) params.set('start', String(start));
|
||||
if (end !== undefined) params.set('end', String(end));
|
||||
const query = params.toString();
|
||||
return client.getJson(query ? `${{p}}?${{query}}` : p, onUpdate);
|
||||
|
||||
/**
|
||||
* @param {{number}} [start]
|
||||
* @param {{number}} [end]
|
||||
* @param {{string}} [format]
|
||||
* @returns {{string}}
|
||||
*/
|
||||
const buildPath = (start, end, format) => {{
|
||||
const params = new URLSearchParams();
|
||||
if (start !== undefined) params.set('start', String(start));
|
||||
if (end !== undefined) params.set('end', String(end));
|
||||
if (format) params.set('format', format);
|
||||
const query = params.toString();
|
||||
return query ? `${{p}}?${{query}}` : p;
|
||||
}};
|
||||
|
||||
/**
|
||||
* @param {{number}} [start]
|
||||
* @param {{number}} [end]
|
||||
* @returns {{RangeBuilder<T>}}
|
||||
*/
|
||||
const rangeBuilder = (start, end) => ({{
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, end, 'csv')); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} start
|
||||
* @returns {{FromBuilder<T>}}
|
||||
*/
|
||||
const fromBuilder = (start) => ({{
|
||||
take(/** @type {{number}} */ n) {{ return rangeBuilder(start, start + n); }},
|
||||
to(/** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(start, undefined), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(start, undefined, 'csv')); }},
|
||||
}});
|
||||
|
||||
/**
|
||||
* @param {{number}} end
|
||||
* @returns {{ToBuilder<T>}}
|
||||
*/
|
||||
const toBuilder = (end) => ({{
|
||||
takeLast(/** @type {{number}} */ n) {{ return rangeBuilder(end - n, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return rangeBuilder(start, end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(undefined, end), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, end, 'csv')); }},
|
||||
}});
|
||||
|
||||
return {{
|
||||
first(/** @type {{number}} */ n) {{ return rangeBuilder(undefined, n); }},
|
||||
last(/** @type {{number}} */ n) {{ return rangeBuilder(-n, undefined); }},
|
||||
range(/** @type {{number}} */ start, /** @type {{number}} */ end) {{ return rangeBuilder(start, end); }},
|
||||
from(/** @type {{number}} */ start) {{ return fromBuilder(start); }},
|
||||
to(/** @type {{number}} */ end) {{ return toBuilder(end); }},
|
||||
json(/** @type {{((value: MetricData<T>) => void) | undefined}} */ onUpdate) {{
|
||||
return client.getJson(buildPath(), onUpdate);
|
||||
}},
|
||||
csv() {{ return client.getText(buildPath(undefined, undefined, 'csv')); }},
|
||||
get path() {{ return p; }},
|
||||
}};
|
||||
}}
|
||||
@@ -272,7 +357,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
let by_fields: Vec<String> = pattern
|
||||
.indexes
|
||||
.iter()
|
||||
.map(|idx| format!("{}: MetricEndpoint<T>", idx.serialize_long()))
|
||||
.map(|idx| format!("{}: MetricEndpointBuilder<T>", idx.serialize_long()))
|
||||
.collect();
|
||||
let by_type = format!("{{ {} }}", by_fields.join(", "));
|
||||
|
||||
@@ -280,7 +365,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output, " * @template T").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" * @typedef {{{{ name: string, by: {}, indexes: () => Index[], get: (index: Index) => MetricEndpoint<T>|undefined }}}} {}",
|
||||
" * @typedef {{{{ name: string, by: {}, indexes: () => Index[], get: (index: Index) => MetricEndpointBuilder<T>|undefined }}}} {}",
|
||||
by_type, pattern.name
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -127,6 +127,20 @@ pub fn generate_main_client(
|
||||
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, " *").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();
|
||||
writeln!(output, " * @returns {{MetricEndpointBuilder<unknown>}}").unwrap();
|
||||
writeln!(output, " */").unwrap();
|
||||
writeln!(output, " metric(metric, index) {{").unwrap();
|
||||
writeln!(output, " return _endpoint(this, metric, index);").unwrap();
|
||||
writeln!(output, " }}\n").unwrap();
|
||||
|
||||
generate_api_methods(output, endpoints);
|
||||
|
||||
writeln!(output, "}}\n").unwrap();
|
||||
|
||||
@@ -29,6 +29,16 @@ pub fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
||||
writeln!(output, " self.metrics = MetricsTree(self)").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate metric() method for dynamic metric access
|
||||
writeln!(output, " def metric(self, metric: str, index: Index) -> MetricEndpointBuilder[Any]:").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, " \"\"\"").unwrap();
|
||||
writeln!(output, " return MetricEndpointBuilder(self, metric, index)").unwrap();
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate API methods
|
||||
generate_api_methods(output, endpoints);
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ def _m(acc: str, s: str) -> str:
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Generate the MetricData and MetricEndpoint classes
|
||||
/// Generate the MetricData and MetricEndpointBuilder classes
|
||||
pub fn generate_endpoint_class(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
@@ -156,36 +156,191 @@ pub fn generate_endpoint_class(output: &mut String) {
|
||||
AnyMetricData = MetricData[Any]
|
||||
|
||||
|
||||
class MetricEndpoint(Generic[T]):
|
||||
"""An endpoint for a specific metric + index combination."""
|
||||
class _EndpointConfig:
|
||||
"""Shared endpoint configuration."""
|
||||
client: BrkClientBase
|
||||
name: str
|
||||
index: Index
|
||||
start: Optional[int]
|
||||
end: Optional[int]
|
||||
|
||||
def __init__(self, client: BrkClientBase, name: str, index: str):
|
||||
self._client = client
|
||||
self._name = name
|
||||
self._index = index
|
||||
|
||||
def get(self) -> MetricData[T]:
|
||||
"""Fetch all data points for this metric/index."""
|
||||
return self._client.get_json(self.path())
|
||||
|
||||
def range(self, start: Optional[int] = None, end: Optional[int] = None) -> MetricData[T]:
|
||||
"""Fetch data points within a range."""
|
||||
params = []
|
||||
if start is not None:
|
||||
params.append(f"start={{start}}")
|
||||
if end is not None:
|
||||
params.append(f"end={{end}}")
|
||||
query = "&".join(params)
|
||||
p = self.path()
|
||||
return self._client.get_json(f"{{p}}?{{query}}" if query else p)
|
||||
def __init__(self, client: BrkClientBase, name: str, index: Index,
|
||||
start: Optional[int] = None, end: Optional[int] = None):
|
||||
self.client = client
|
||||
self.name = name
|
||||
self.index = index
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def path(self) -> str:
|
||||
"""Get the endpoint path."""
|
||||
return f"/api/metric/{{self._name}}/{{self._index}}"
|
||||
return f"/api/metric/{{self.name}}/{{self.index}}"
|
||||
|
||||
def _build_path(self, format: Optional[str] = None) -> str:
|
||||
params = []
|
||||
if self.start is not None:
|
||||
params.append(f"start={{self.start}}")
|
||||
if self.end is not None:
|
||||
params.append(f"end={{self.end}}")
|
||||
if format is not None:
|
||||
params.append(f"format={{format}}")
|
||||
query = "&".join(params)
|
||||
p = self.path()
|
||||
return f"{{p}}?{{query}}" if query else p
|
||||
|
||||
def get_json(self) -> Any:
|
||||
return self.client.get_json(self._build_path())
|
||||
|
||||
def get_csv(self) -> str:
|
||||
return self.client.get_text(self._build_path(format='csv'))
|
||||
|
||||
|
||||
class RangeBuilder(Generic[T]):
|
||||
"""Final builder with range fully specified. Can only call json() or csv()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class FromBuilder(Generic[T]):
|
||||
"""Builder after calling from(start). Can chain with take() or to()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take n items from the start position."""
|
||||
start = self._config.start or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, start + n
|
||||
))
|
||||
|
||||
def to(self, end: int) -> RangeBuilder[T]:
|
||||
"""Set the end position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
self._config.start, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start to end of data)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class ToBuilder(Generic[T]):
|
||||
"""Builder after calling to(end). Can chain with take_last() or from()."""
|
||||
|
||||
def __init__(self, config: _EndpointConfig):
|
||||
self._config = config
|
||||
|
||||
def take_last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Take last n items before the end position."""
|
||||
end = self._config.end or 0
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
end - n, end
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> RangeBuilder[T]:
|
||||
"""Set the start position."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, self._config.end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (from start of data to end)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string."""
|
||||
return self._config.get_csv()
|
||||
|
||||
|
||||
class MetricEndpointBuilder(Generic[T]):
|
||||
"""Initial builder for metric endpoint queries.
|
||||
|
||||
Use method chaining to specify the data range, then call json() or csv() to execute.
|
||||
|
||||
Examples:
|
||||
# Get all data
|
||||
endpoint.json()
|
||||
|
||||
# Get last 10 points
|
||||
endpoint.last(10).json()
|
||||
|
||||
# Get range [100, 200)
|
||||
endpoint.range(100, 200).json()
|
||||
|
||||
# Get 10 points starting from position 100
|
||||
endpoint.from_(100).take(10).json()
|
||||
"""
|
||||
|
||||
def __init__(self, client: BrkClientBase, name: str, index: Index):
|
||||
self._config = _EndpointConfig(client, name, index)
|
||||
|
||||
def first(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the first n data points."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, n
|
||||
))
|
||||
|
||||
def last(self, n: int) -> RangeBuilder[T]:
|
||||
"""Fetch the last n data points."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
-n, None
|
||||
))
|
||||
|
||||
def range(self, start: int, end: int) -> RangeBuilder[T]:
|
||||
"""Set an explicit range [start, end)."""
|
||||
return RangeBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, end
|
||||
))
|
||||
|
||||
def from_(self, start: int) -> FromBuilder[T]:
|
||||
"""Set the start position. Chain with take() or to()."""
|
||||
return FromBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
start, None
|
||||
))
|
||||
|
||||
def to(self, end: int) -> ToBuilder[T]:
|
||||
"""Set the end position. Chain with take_last() or from_()."""
|
||||
return ToBuilder(_EndpointConfig(
|
||||
self._config.client, self._config.name, self._config.index,
|
||||
None, end
|
||||
))
|
||||
|
||||
def json(self) -> MetricData[T]:
|
||||
"""Execute the query and return parsed JSON data (all data)."""
|
||||
return self._config.get_json()
|
||||
|
||||
def csv(self) -> str:
|
||||
"""Execute the query and return CSV data as a string (all data)."""
|
||||
return self._config.get_csv()
|
||||
|
||||
def path(self) -> str:
|
||||
"""Get the base endpoint path."""
|
||||
return self._config.path()
|
||||
|
||||
|
||||
# Type alias for non-generic usage
|
||||
AnyMetricEndpoint = MetricEndpoint[Any]
|
||||
AnyMetricEndpointBuilder = MetricEndpointBuilder[Any]
|
||||
|
||||
|
||||
class MetricPattern(Protocol[T]):
|
||||
@@ -200,8 +355,8 @@ class MetricPattern(Protocol[T]):
|
||||
"""Get the list of available indexes for this metric."""
|
||||
...
|
||||
|
||||
def get(self, index: str) -> Optional[MetricEndpoint[T]]:
|
||||
"""Get an endpoint for a specific index, if supported."""
|
||||
def get(self, index: Index) -> Optional[MetricEndpointBuilder[T]]:
|
||||
"""Get an endpoint builder for a specific index, if supported."""
|
||||
...
|
||||
|
||||
"#
|
||||
@@ -237,10 +392,10 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
for index in &pattern.indexes {
|
||||
let method_name = index_to_field_name(index);
|
||||
let index_name = index.serialize_long();
|
||||
writeln!(output, " def {}(self) -> MetricEndpoint[T]:", method_name).unwrap();
|
||||
writeln!(output, " def {}(self) -> MetricEndpointBuilder[T]:", method_name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" return MetricEndpoint(self._client, self._name, '{}')",
|
||||
" return MetricEndpointBuilder(self._client, self._name, '{}')",
|
||||
index_name
|
||||
)
|
||||
.unwrap();
|
||||
@@ -288,8 +443,8 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
writeln!(output).unwrap();
|
||||
|
||||
// Generate get(index) method
|
||||
writeln!(output, " def get(self, index: str) -> Optional[MetricEndpoint[T]]:").unwrap();
|
||||
writeln!(output, " \"\"\"Get an endpoint for a specific index, if supported.\"\"\"").unwrap();
|
||||
writeln!(output, " def get(self, index: Index) -> Optional[MetricEndpointBuilder[T]]:").unwrap();
|
||||
writeln!(output, " \"\"\"Get an endpoint builder for a specific index, if supported.\"\"\"").unwrap();
|
||||
for (i, index) in pattern.indexes.iter().enumerate() {
|
||||
let method_name = index_to_field_name(index);
|
||||
let index_name = index.serialize_long();
|
||||
|
||||
@@ -38,6 +38,25 @@ impl BrkClient {{
|
||||
pub fn metrics(&self) -> &MetricsTree {{
|
||||
&self.metrics
|
||||
}}
|
||||
|
||||
/// Create a dynamic metric endpoint builder for any metric/index combination.
|
||||
///
|
||||
/// Use this for programmatic access when the metric name is determined at runtime.
|
||||
/// For type-safe access, use the `metrics()` tree instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let data = client.metric("realized_price", Index::Height)
|
||||
/// .last(10)
|
||||
/// .json::<f64>()?;
|
||||
/// ```
|
||||
pub fn metric(&self, metric: impl Into<Metric>, index: Index) -> MetricEndpointBuilder<serde_json::Value> {{
|
||||
MetricEndpointBuilder::new(
|
||||
self.base.clone(),
|
||||
Arc::from(metric.into().as_str()),
|
||||
index,
|
||||
)
|
||||
}}
|
||||
"#,
|
||||
VERSION = VERSION
|
||||
)
|
||||
|
||||
@@ -141,8 +141,8 @@ pub trait AnyMetricPattern {{
|
||||
|
||||
/// Generic trait for metric patterns with endpoint access.
|
||||
pub trait MetricPattern<T>: AnyMetricPattern {{
|
||||
/// Get an endpoint for a specific index, if supported.
|
||||
fn get(&self, index: Index) -> Option<Endpoint<T>>;
|
||||
/// Get an endpoint builder for a specific index, if supported.
|
||||
fn get(&self, index: Index) -> Option<MetricEndpointBuilder<T>>;
|
||||
}}
|
||||
|
||||
"#
|
||||
@@ -150,50 +150,199 @@ pub trait MetricPattern<T>: AnyMetricPattern {{
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Generate the Endpoint struct.
|
||||
/// Generate the MetricEndpointBuilder structs with typestate pattern.
|
||||
pub fn generate_endpoint(output: &mut String) {
|
||||
writeln!(
|
||||
output,
|
||||
r#"/// An endpoint for a specific metric + index combination.
|
||||
pub struct Endpoint<T> {{
|
||||
r#"/// Shared endpoint configuration.
|
||||
#[derive(Clone)]
|
||||
struct EndpointConfig {{
|
||||
client: Arc<BrkClientBase>,
|
||||
name: Arc<str>,
|
||||
index: Index,
|
||||
start: Option<i64>,
|
||||
end: Option<i64>,
|
||||
}}
|
||||
|
||||
impl EndpointConfig {{
|
||||
fn new(client: Arc<BrkClientBase>, name: Arc<str>, index: Index) -> Self {{
|
||||
Self {{ client, name, index, start: None, end: None }}
|
||||
}}
|
||||
|
||||
fn path(&self) -> String {{
|
||||
format!("/api/metric/{{}}/{{}}", self.name, self.index.serialize_long())
|
||||
}}
|
||||
|
||||
fn build_path(&self, format: Option<&str>) -> String {{
|
||||
let mut params = Vec::new();
|
||||
if let Some(s) = self.start {{ params.push(format!("start={{}}", s)); }}
|
||||
if let Some(e) = self.end {{ params.push(format!("end={{}}", e)); }}
|
||||
if let Some(fmt) = format {{ params.push(format!("format={{}}", fmt)); }}
|
||||
let p = self.path();
|
||||
if params.is_empty() {{ p }} else {{ format!("{{}}?{{}}", p, params.join("&")) }}
|
||||
}}
|
||||
|
||||
fn get_json<T: DeserializeOwned>(&self, format: Option<&str>) -> Result<T> {{
|
||||
self.client.get_json(&self.build_path(format))
|
||||
}}
|
||||
|
||||
fn get_text(&self, format: Option<&str>) -> Result<String> {{
|
||||
self.client.get_text(&self.build_path(format))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Initial builder for metric endpoint queries.
|
||||
///
|
||||
/// Use method chaining to specify the data range, then call `json()` or `csv()` to execute.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```ignore
|
||||
/// // Get all data
|
||||
/// endpoint.json()?;
|
||||
///
|
||||
/// // Get last 10 points
|
||||
/// endpoint.last(10).json()?;
|
||||
///
|
||||
/// // Get range [100, 200)
|
||||
/// endpoint.range(100, 200).json()?;
|
||||
///
|
||||
/// // Get 10 points starting from position 100
|
||||
/// endpoint.from(100).take(10).json()?;
|
||||
/// ```
|
||||
pub struct MetricEndpointBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> Endpoint<T> {{
|
||||
impl<T: DeserializeOwned> MetricEndpointBuilder<T> {{
|
||||
pub fn new(client: Arc<BrkClientBase>, name: Arc<str>, index: Index) -> Self {{
|
||||
Self {{
|
||||
client,
|
||||
name,
|
||||
index,
|
||||
_marker: std::marker::PhantomData,
|
||||
}}
|
||||
Self {{ config: EndpointConfig::new(client, name, index), _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch all data points for this metric/index.
|
||||
pub fn get(&self) -> Result<MetricData<T>> {{
|
||||
self.client.get_json(&self.path())
|
||||
/// Fetch the first n data points.
|
||||
pub fn first(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Fetch data points within a range.
|
||||
pub fn range(&self, start: Option<i64>, end: Option<i64>) -> Result<MetricData<T>> {{
|
||||
let mut params = Vec::new();
|
||||
if let Some(s) = start {{ params.push(format!("start={{}}", s)); }}
|
||||
if let Some(e) = end {{ params.push(format!("end={{}}", e)); }}
|
||||
let p = self.path();
|
||||
let path = if params.is_empty() {{
|
||||
p
|
||||
}} else {{
|
||||
format!("{{}}?{{}}", p, params.join("&"))
|
||||
}};
|
||||
self.client.get_json(&path)
|
||||
/// Fetch the last n data points.
|
||||
pub fn last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(-(n as i64));
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Get the endpoint path.
|
||||
/// Set an explicit range [start, end).
|
||||
pub fn range(mut self, start: i64, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position. Chain with `take(n)` or `to(end)`.
|
||||
pub fn from(mut self, start: i64) -> FromBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
FromBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position. Chain with `takeLast(n)` or `from(start)`.
|
||||
pub fn to(mut self, end: i64) -> ToBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
ToBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (all data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string (all data).
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
|
||||
/// Get the base endpoint path.
|
||||
pub fn path(&self) -> String {{
|
||||
format!("/api/metric/{{}}/{{}}", self.name, self.index.serialize_long())
|
||||
self.config.path()
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `from(start)`. Can chain with `take(n)` or `to(end)`.
|
||||
pub struct FromBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> FromBuilder<T> {{
|
||||
/// Take n items from the start position.
|
||||
pub fn take(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
let start = self.config.start.unwrap_or(0);
|
||||
self.config.end = Some(start + n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the end position.
|
||||
pub fn to(mut self, end: i64) -> RangeBuilder<T> {{
|
||||
self.config.end = Some(end);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start to end of data).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Builder after calling `to(end)`. Can chain with `takeLast(n)` or `from(start)`.
|
||||
pub struct ToBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> ToBuilder<T> {{
|
||||
/// Take last n items before the end position.
|
||||
pub fn take_last(mut self, n: u64) -> RangeBuilder<T> {{
|
||||
let end = self.config.end.unwrap_or(0);
|
||||
self.config.start = Some(end - n as i64);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Set the start position.
|
||||
pub fn from(mut self, start: i64) -> RangeBuilder<T> {{
|
||||
self.config.start = Some(start);
|
||||
RangeBuilder {{ config: self.config, _marker: std::marker::PhantomData }}
|
||||
}}
|
||||
|
||||
/// Execute the query and return parsed JSON data (from start of data to end).
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
/// Final builder with range fully specified. Can only call `json()` or `csv()`.
|
||||
pub struct RangeBuilder<T> {{
|
||||
config: EndpointConfig,
|
||||
_marker: std::marker::PhantomData<T>,
|
||||
}}
|
||||
|
||||
impl<T: DeserializeOwned> RangeBuilder<T> {{
|
||||
/// Execute the query and return parsed JSON data.
|
||||
pub fn json(self) -> Result<MetricData<T>> {{
|
||||
self.config.get_json(None)
|
||||
}}
|
||||
|
||||
/// Execute the query and return CSV data as a string.
|
||||
pub fn csv(self) -> Result<String> {{
|
||||
self.config.get_text(Some("csv"))
|
||||
}}
|
||||
}}
|
||||
|
||||
@@ -225,10 +374,10 @@ 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) -> Endpoint<T> {{", method_name).unwrap();
|
||||
writeln!(output, " pub fn {}(&self) -> MetricEndpointBuilder<T> {{", method_name).unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
" Endpoint::new(self.client.clone(), self.name.clone(), Index::{})",
|
||||
" MetricEndpointBuilder::new(self.client.clone(), self.name.clone(), Index::{})",
|
||||
index
|
||||
)
|
||||
.unwrap();
|
||||
@@ -291,7 +440,7 @@ pub fn generate_index_accessors(output: &mut String, patterns: &[IndexSetPattern
|
||||
|
||||
// Implement MetricPattern<T> trait
|
||||
writeln!(output, "impl<T: DeserializeOwned> MetricPattern<T> for {}<T> {{", pattern.name).unwrap();
|
||||
writeln!(output, " fn get(&self, index: Index) -> Option<Endpoint<T>> {{").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);
|
||||
|
||||
Reference in New Issue
Block a user