1#![allow(clippy::type_complexity)]
2
3use std::{collections::btree_map::Entry, fs::create_dir_all, io, path::PathBuf};
4
5use brk_query::Vecs;
6
7#[derive(Debug, Clone, Default)]
20pub struct ClientOutputPaths {
21 pub rust: Option<PathBuf>,
23 pub javascript: Option<PathBuf>,
25 pub python: Option<PathBuf>,
27}
28
29impl ClientOutputPaths {
30 pub fn new() -> Self {
31 Self::default()
32 }
33
34 pub fn rust(mut self, path: impl Into<PathBuf>) -> Self {
35 self.rust = Some(path.into());
36 self
37 }
38
39 pub fn javascript(mut self, path: impl Into<PathBuf>) -> Self {
40 self.javascript = Some(path.into());
41 self
42 }
43
44 pub fn python(mut self, path: impl Into<PathBuf>) -> Self {
45 self.python = Some(path.into());
46 self
47 }
48}
49
50mod analysis;
51mod backends;
52mod generate;
53mod generators;
54mod openapi;
55mod syntax;
56mod types;
57
58pub use analysis::*;
59pub use backends::*;
60pub use generate::*;
61pub use generators::*;
62pub use openapi::*;
63pub use syntax::*;
64pub use types::*;
65
66pub const VERSION: &str = env!("CARGO_PKG_VERSION");
67
68pub fn generate_clients(
83 vecs: &Vecs,
84 openapi_json: &str,
85 output_paths: &ClientOutputPaths,
86) -> io::Result<()> {
87 let metadata = ClientMetadata::from_vecs(vecs);
88
89 let spec = parse_openapi_json(openapi_json)?;
91 let endpoints = extract_endpoints(&spec);
92 let mut schemas = extract_schemas(openapi_json);
93
94 collect_leaf_type_schemas(&metadata.catalog, &mut schemas);
96
97 let schema_values: Vec<_> = schemas.values().cloned().collect();
100 for schema in &schema_values {
101 collect_schema_definitions(schema, &mut schemas);
102 }
103
104 if let Some(rust_path) = &output_paths.rust {
106 if let Some(parent) = rust_path.parent() {
107 create_dir_all(parent)?;
108 }
109 generate_rust_client(&metadata, &endpoints, rust_path)?;
110 }
111
112 if let Some(js_path) = &output_paths.javascript {
114 if let Some(parent) = js_path.parent() {
115 create_dir_all(parent)?;
116 }
117 generate_javascript_client(&metadata, &endpoints, &schemas, js_path)?;
118 }
119
120 if let Some(python_path) = &output_paths.python {
122 if let Some(parent) = python_path.parent() {
123 create_dir_all(parent)?;
124 }
125 generate_python_client(&metadata, &endpoints, &schemas, python_path)?;
126 }
127
128 Ok(())
129}
130
131use brk_types::TreeNode;
132use serde_json::Value;
133
134fn collect_leaf_type_schemas(node: &TreeNode, schemas: &mut TypeSchemas) {
138 match node {
139 TreeNode::Leaf(leaf) => {
140 collect_schema_definitions(&leaf.schema, schemas);
143
144 let type_name = extract_inner_type(leaf.kind());
146
147 if let Entry::Vacant(e) = schemas.entry(type_name) {
148 let schema = unwrap_allof(&leaf.schema);
150
151 let has_type = schema.get("type").is_some();
157 let has_properties = schema.get("properties").is_some();
158 let has_enum = schema.get("enum").is_some() || schema.get("oneOf").is_some();
159 let is_ref = schema.get("$ref").is_some();
160
161 if has_type || has_properties || has_enum || is_ref {
162 e.insert(schema.clone());
163 }
164 }
165 }
166 TreeNode::Branch(children) => {
167 for child in children.values() {
168 collect_leaf_type_schemas(child, schemas);
169 }
170 }
171 }
172}
173
174fn collect_schema_definitions(schema: &Value, schemas: &mut TypeSchemas) {
177 for key in ["definitions", "$defs"] {
179 if let Some(defs) = schema.get(key).and_then(|d| d.as_object()) {
180 for (name, def_schema) in defs {
181 if !schemas.contains_key(name) {
182 schemas.insert(name.clone(), def_schema.clone());
183 }
184 }
185 }
186 }
187}