From 5e3519aad4549aa07fb4ace58b86f13ac536369b Mon Sep 17 00:00:00 2001 From: nym21 Date: Sun, 21 Dec 2025 00:33:56 +0100 Subject: [PATCH] binder: snapshot --- crates/brk_binder/src/javascript.rs | 15 ++++++++++++- crates/brk_binder/src/lib.rs | 35 ++++++++++++++++++++--------- crates/brk_binder/src/openapi.rs | 11 +++++++++ crates/brk_binder/src/python.rs | 35 +++++++++++++++++++++++++---- crates/brk_binder/src/rust.rs | 2 +- crates/brk_binder/src/types.rs | 12 +++++++++- 6 files changed, 92 insertions(+), 18 deletions(-) diff --git a/crates/brk_binder/src/javascript.rs b/crates/brk_binder/src/javascript.rs index b534f6e38..d39091869 100644 --- a/crates/brk_binder/src/javascript.rs +++ b/crates/brk_binder/src/javascript.rs @@ -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 = 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; } diff --git a/crates/brk_binder/src/lib.rs b/crates/brk_binder/src/lib.rs index bbcbd9e8a..2b8cf3d90 100644 --- a/crates/brk_binder/src/lib.rs +++ b/crates/brk_binder/src/lib.rs @@ -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") - 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` 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() { diff --git a/crates/brk_binder/src/openapi.rs b/crates/brk_binder/src/openapi.rs index fa05b1c16..07fb67dca 100644 --- a/crates/brk_binder/src/openapi.rs +++ b/crates/brk_binder/src/openapi.rs @@ -27,6 +27,16 @@ pub struct Endpoint { pub query_params: Vec, /// Response type (simplified) pub response_type: Option, + /// 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 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 { // 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 = 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; } diff --git a/crates/brk_binder/src/rust.rs b/crates/brk_binder/src/rust.rs index d5188e393..fca881f1a 100644 --- a/crates/brk_binder/src/rust.rs +++ b/crates/brk_binder/src/rust.rs @@ -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; } diff --git a/crates/brk_binder/src/types.rs b/crates/brk_binder/src/types.rs index 6b5438d6c..6c97bd2d8 100644 --- a/crates/brk_binder/src/types.rs +++ b/crates/brk_binder/src/types.rs @@ -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`. /// Also handles malformed types like `Dollars>` (from vecdb's short_type_name which /// extracts "Dollars>" from "Close" using rsplit("::")). @@ -706,7 +713,7 @@ fn generate_pattern_name(field_name: &str, name_counts: &mut HashMap