mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-06-11 15:33:33 -07:00
binder: snapshot
This commit is contained in:
@@ -97,6 +97,7 @@ fn is_primitive_alias(schema: &Value) -> bool {
|
||||
&& schema.get("items").is_none()
|
||||
&& schema.get("anyOf").is_none()
|
||||
&& schema.get("oneOf").is_none()
|
||||
&& schema.get("enum").is_none()
|
||||
}
|
||||
|
||||
/// Convert JSON Schema to JavaScript/JSDoc type
|
||||
@@ -109,6 +110,18 @@ fn schema_to_js_type(schema: &Value) -> String {
|
||||
return ref_path.rsplit('/').next().unwrap_or("*").to_string();
|
||||
}
|
||||
|
||||
// Handle enum (array of string values)
|
||||
if let Some(enum_values) = schema.get("enum").and_then(|e| e.as_array()) {
|
||||
let literals: Vec<String> = enum_values
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str())
|
||||
.map(|s| format!("\"{}\"", s))
|
||||
.collect();
|
||||
if !literals.is_empty() {
|
||||
return format!("({})", literals.join("|"));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle type field
|
||||
if let Some(ty) = schema.get("type").and_then(|t| t.as_str()) {
|
||||
return match ty {
|
||||
@@ -844,7 +857,7 @@ fn infer_child_accumulated_name(node: &TreeNode, parent_acc: &str, field_name: &
|
||||
/// Generate API methods
|
||||
fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
for endpoint in endpoints {
|
||||
if endpoint.method != "GET" {
|
||||
if !endpoint.should_generate() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,21 +60,34 @@ use serde_json::Value;
|
||||
|
||||
/// Recursively collect leaf type schemas from the tree and add to schemas map.
|
||||
/// Only adds schemas that aren't already present (OpenAPI schemas take precedence).
|
||||
/// Also collects definitions from schemars-generated schemas (for referenced types).
|
||||
/// Collects definitions from schemars-generated schemas (for referenced types).
|
||||
fn collect_leaf_type_schemas(node: &TreeNode, schemas: &mut TypeSchemas) {
|
||||
match node {
|
||||
TreeNode::Leaf(leaf) => {
|
||||
// Extract the inner type name (e.g., "Dollars" from "Close<Dollars>")
|
||||
let type_name = extract_inner_type(leaf.value_type());
|
||||
|
||||
// Only add if not already present (OpenAPI schemas take precedence)
|
||||
if !schemas.contains_key(&type_name) {
|
||||
// The leaf schema is the schemars-generated JSON schema
|
||||
schemas.insert(type_name, leaf.schema.clone());
|
||||
}
|
||||
|
||||
// Also collect any definitions from the schema (schemars puts referenced types here)
|
||||
// Collect definitions from the schema (schemars puts type schemas here)
|
||||
// This includes the inner types like `Bitcoin` from `Close<Bitcoin>`
|
||||
collect_schema_definitions(&leaf.schema, schemas);
|
||||
|
||||
// Get the type name for this leaf
|
||||
let type_name = extract_inner_type(leaf.value_type());
|
||||
if !schemas.contains_key(&type_name) {
|
||||
// Unwrap single-element allOf
|
||||
let schema = unwrap_allof(&leaf.schema);
|
||||
|
||||
// Add the schema if it's usable:
|
||||
// - Simple type (has "type")
|
||||
// - Object type with properties (complex types like OHLCCents, EmptyAddressData)
|
||||
// - Enum type (has "enum" or "oneOf")
|
||||
// - Or a $ref to another type
|
||||
let has_type = schema.get("type").is_some();
|
||||
let has_properties = schema.get("properties").is_some();
|
||||
let has_enum = schema.get("enum").is_some() || schema.get("oneOf").is_some();
|
||||
let is_ref = schema.get("$ref").is_some();
|
||||
|
||||
if has_type || has_properties || has_enum || is_ref {
|
||||
schemas.insert(type_name, schema.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
TreeNode::Branch(children) => {
|
||||
for child in children.values() {
|
||||
|
||||
@@ -27,6 +27,16 @@ pub struct Endpoint {
|
||||
pub query_params: Vec<Parameter>,
|
||||
/// Response type (simplified)
|
||||
pub response_type: Option<String>,
|
||||
/// Whether this endpoint is deprecated
|
||||
pub deprecated: bool,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
/// Returns true if this endpoint should be included in client generation.
|
||||
/// Only non-deprecated GET endpoints are included.
|
||||
pub fn should_generate(&self) -> bool {
|
||||
self.method == "GET" && !self.deprecated
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameter information
|
||||
@@ -161,6 +171,7 @@ fn extract_endpoint(path: &str, method: &str, operation: &Operation) -> Option<E
|
||||
path_params,
|
||||
query_params,
|
||||
response_type,
|
||||
deprecated: operation.deprecated.unwrap_or(false),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ use serde_json::Value;
|
||||
|
||||
use crate::{
|
||||
ClientMetadata, Endpoint, FieldNamePosition, IndexSetPattern, PatternField, StructuralPattern,
|
||||
TypeSchemas, extract_inner_type, get_node_fields, get_pattern_instance_base, to_pascal_case,
|
||||
to_snake_case, unwrap_allof,
|
||||
TypeSchemas, extract_inner_type, get_node_fields, get_pattern_instance_base, is_enum_schema,
|
||||
to_pascal_case, to_snake_case, unwrap_allof,
|
||||
};
|
||||
|
||||
/// Generate Python client from metadata and OpenAPI endpoints
|
||||
@@ -28,7 +28,7 @@ pub fn generate_python_client(
|
||||
writeln!(output, "from __future__ import annotations").unwrap();
|
||||
writeln!(
|
||||
output,
|
||||
"from typing import TypeVar, Generic, Any, Optional, List, TypedDict"
|
||||
"from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict"
|
||||
)
|
||||
.unwrap();
|
||||
writeln!(output, "import httpx\n").unwrap();
|
||||
@@ -86,6 +86,10 @@ fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
|
||||
writeln!(output, " {}: {}", safe_name, prop_type).unwrap();
|
||||
}
|
||||
writeln!(output).unwrap();
|
||||
} else if is_enum_schema(schema) {
|
||||
// Enum type -> Literal union
|
||||
let py_type = schema_to_python_type(schema);
|
||||
writeln!(output, "{} = {}", name, py_type).unwrap();
|
||||
} else {
|
||||
// Primitive type alias
|
||||
let py_type = schema_to_python_type(schema);
|
||||
@@ -147,6 +151,17 @@ fn topological_sort_schemas(schemas: &TypeSchemas) -> Vec<String> {
|
||||
|
||||
// Reverse so dependencies come first
|
||||
result.reverse();
|
||||
|
||||
// Add any types that weren't processed (e.g., due to circular refs or other edge cases)
|
||||
let result_set: HashSet<_> = result.iter().cloned().collect();
|
||||
let mut missing: Vec<_> = schemas
|
||||
.keys()
|
||||
.filter(|k| !result_set.contains(*k))
|
||||
.cloned()
|
||||
.collect();
|
||||
missing.sort();
|
||||
result.extend(missing);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
@@ -182,6 +197,18 @@ fn schema_to_python_type(schema: &Value) -> String {
|
||||
return ref_path.rsplit('/').next().unwrap_or("Any").to_string();
|
||||
}
|
||||
|
||||
// Handle enum (array of string values)
|
||||
if let Some(enum_values) = schema.get("enum").and_then(|e| e.as_array()) {
|
||||
let literals: Vec<String> = enum_values
|
||||
.iter()
|
||||
.filter_map(|v| v.as_str())
|
||||
.map(|s| format!("\"{}\"", s))
|
||||
.collect();
|
||||
if !literals.is_empty() {
|
||||
return format!("Literal[{}]", literals.join(", "));
|
||||
}
|
||||
}
|
||||
|
||||
// Handle type field
|
||||
if let Some(ty) = schema.get("type").and_then(|t| t.as_str()) {
|
||||
return match ty {
|
||||
@@ -786,7 +813,7 @@ fn generate_main_client(output: &mut String, endpoints: &[Endpoint]) {
|
||||
/// Generate API methods from OpenAPI endpoints
|
||||
fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
for endpoint in endpoints {
|
||||
if endpoint.method != "GET" {
|
||||
if !endpoint.should_generate() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -715,7 +715,7 @@ impl BrkClient {{
|
||||
/// Generate API methods from OpenAPI endpoints
|
||||
fn generate_api_methods(output: &mut String, endpoints: &[Endpoint]) {
|
||||
for endpoint in endpoints {
|
||||
if endpoint.method != "GET" {
|
||||
if !endpoint.should_generate() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -196,6 +196,13 @@ pub fn unwrap_allof(schema: &Value) -> &Value {
|
||||
schema
|
||||
}
|
||||
|
||||
/// Check if a schema represents an enum type.
|
||||
/// Enums have either an "enum" array or "oneOf" without properties.
|
||||
pub fn is_enum_schema(schema: &Value) -> bool {
|
||||
schema.get("enum").is_some()
|
||||
|| (schema.get("oneOf").is_some() && schema.get("properties").is_none())
|
||||
}
|
||||
|
||||
/// Extract inner type from a wrapper generic like `Close<Dollars>` -> `Dollars`.
|
||||
/// Also handles malformed types like `Dollars>` (from vecdb's short_type_name which
|
||||
/// extracts "Dollars>" from "Close<brk_types::Dollars>" using rsplit("::")).
|
||||
@@ -706,7 +713,7 @@ fn generate_pattern_name(field_name: &str, name_counts: &mut HashMap<String, usi
|
||||
let pascal = to_pascal_case(field_name);
|
||||
|
||||
// Sanitize: ensure it starts with a letter (prepend "_" if starts with digit)
|
||||
let base_name = if pascal
|
||||
let sanitized = if pascal
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.is_ascii_digit())
|
||||
@@ -717,6 +724,9 @@ fn generate_pattern_name(field_name: &str, name_counts: &mut HashMap<String, usi
|
||||
pascal
|
||||
};
|
||||
|
||||
// Add "Pattern" suffix to avoid conflicts with type aliases (e.g., Sats = int vs class Sats)
|
||||
let base_name = format!("{}Pattern", sanitized);
|
||||
|
||||
// Track usage count and append index if needed
|
||||
let count = name_counts.entry(base_name.clone()).or_insert(0);
|
||||
*count += 1;
|
||||
|
||||
Reference in New Issue
Block a user