Add source files: main, lib, cli, error, convert, highlight, validate, typescript
This commit is contained in:
191
src/typescript.rs
Normal file
191
src/typescript.rs
Normal file
@@ -0,0 +1,191 @@
|
||||
use serde_json::Value;
|
||||
use crate::error::{ConfigForgeError, Result};
|
||||
|
||||
pub fn generate_ts_interface(config: &Value, name: &str) -> Result<String> {
|
||||
let mut output = String::new();
|
||||
output.push_str(&format!("export interface {} {{\n", name.pascal_case()));
|
||||
let interface_body = value_to_typescript(config, 1);
|
||||
output.push_str(&interface_body);
|
||||
output.push_str("}\n");
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn value_to_typescript(value: &Value, indent: usize) -> String {
|
||||
let indent_str = " ".repeat(indent);
|
||||
match value {
|
||||
Value::Null => "null".to_string(),
|
||||
Value::Bool(_) => "boolean".to_string(),
|
||||
Value::Number(_) => "number".to_string(),
|
||||
Value::String(_) => "string".to_string(),
|
||||
Value::Array(arr) => {
|
||||
if arr.is_empty() {
|
||||
"any[]".to_string()
|
||||
} else if arr.len() == 1 {
|
||||
format!("({})", value_to_typescript(&arr[0], indent))
|
||||
} else {
|
||||
let types: std::collections::HashSet<String> = arr.iter()
|
||||
.map(|v| value_to_typescript(v, indent))
|
||||
.collect();
|
||||
if types.len() == 1 {
|
||||
format!("({})", types.iter().next().unwrap())
|
||||
} else {
|
||||
format!("({})", types.iter().collect::<Vec<_>>().join(" | "))
|
||||
}
|
||||
}
|
||||
}
|
||||
Value::Object(map) => {
|
||||
if map.is_empty() {
|
||||
"Record<string, unknown>".to_string()
|
||||
} else {
|
||||
let mut result = String::new();
|
||||
result.push_str("{\n");
|
||||
for (key, value) in map {
|
||||
let ts_type = value_to_typescript(value, indent + 1);
|
||||
let optional = key.starts_with('_') || key.ends_with('?');
|
||||
let key_name = if key.ends_with('?') {
|
||||
&key[..key.len() - 1]
|
||||
} else {
|
||||
key
|
||||
};
|
||||
if optional {
|
||||
result.push_str(&format!("{} {}?: {};\n", indent_str, key_name, ts_type));
|
||||
} else {
|
||||
result.push_str(&format!("{} {}: {};\n", indent_str, key_name, ts_type));
|
||||
}
|
||||
}
|
||||
result.push_str(&format!("{}}}", indent_str));
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schema_to_typescript(schema: &Value, name: &str) -> Result<String> {
|
||||
let mut output = String::new();
|
||||
output.push_str(&format!("export interface {} {{\n", name.pascal_case()));
|
||||
if let Some(properties) = schema.get("properties") {
|
||||
let body = schema_properties_to_typescript(properties, 1);
|
||||
output.push_str(&body);
|
||||
}
|
||||
output.push_str("}\n");
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
fn schema_properties_to_typescript(value: &Value, indent: usize) -> String {
|
||||
let indent_str = " ".repeat(indent);
|
||||
match value {
|
||||
Value::Object(map) => {
|
||||
let mut result = String::new();
|
||||
if let Some(props) = map.get("properties") {
|
||||
if let Some(props_obj) = props.as_object() {
|
||||
for (key, prop_schema) in props_obj {
|
||||
let ts_type = schema_type_to_typescript(prop_schema, indent);
|
||||
let required = if let Some(required_arr) = map.get("required") {
|
||||
if let Some(required_vec) = required_arr.as_array() {
|
||||
required_vec.iter().any(|r| r == key)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !required {
|
||||
result.push_str(&format!("{} {}?: {};\n", indent_str, key, ts_type));
|
||||
} else {
|
||||
result.push_str(&format!("{} {}: {};\n", indent_str, key, ts_type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn schema_type_to_typescript(schema: &Value, indent: usize) -> String {
|
||||
if let Some(type_str) = schema.get("type") {
|
||||
match type_str.as_str() {
|
||||
Some("string") => "string".to_string(),
|
||||
Some("number") => "number".to_string(),
|
||||
Some("integer") => "number".to_string(),
|
||||
Some("boolean") => "boolean".to_string(),
|
||||
Some("null") => "null".to_string(),
|
||||
Some("array") => {
|
||||
if let Some(items) = schema.get("items") {
|
||||
format!("{}", schema_type_to_typescript(items, indent))
|
||||
} else {
|
||||
"any[]".to_string()
|
||||
}
|
||||
}
|
||||
Some("object") => {
|
||||
if let Some(props) = schema.get("properties") {
|
||||
let mut result = String::new();
|
||||
result.push_str("{\n");
|
||||
result.push_str(&schema_properties_to_typescript(props, indent + 1));
|
||||
result.push_str(&format!("{}}}\n", " ".repeat(indent)));
|
||||
result
|
||||
} else {
|
||||
"Record<string, unknown>".to_string()
|
||||
}
|
||||
}
|
||||
_ => "unknown".to_string(),
|
||||
}
|
||||
} else if let Some(any_of) = schema.get("anyOf") {
|
||||
if let Some(arr) = any_of.as_array() {
|
||||
let types: Vec<String> = arr.iter()
|
||||
.map(|s| schema_type_to_typescript(s, indent))
|
||||
.collect();
|
||||
if types.is_empty() {
|
||||
"unknown".to_string()
|
||||
} else {
|
||||
types.join(" | ")
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
} else if let Some(one_of) = schema.get("oneOf") {
|
||||
if let Some(arr) = one_of.as_array() {
|
||||
let types: Vec<String> = arr.iter()
|
||||
.map(|s| schema_type_to_typescript(s, indent))
|
||||
.collect();
|
||||
if types.is_empty() {
|
||||
"unknown".to_string()
|
||||
} else {
|
||||
types.join(" | ")
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
trait PascalCase {
|
||||
fn pascal_case(&self) -> String;
|
||||
}
|
||||
|
||||
impl PascalCase for str {
|
||||
fn pascal_case(&self) -> String {
|
||||
let mut result = String::new();
|
||||
let mut capitalize = true;
|
||||
for c in self.chars() {
|
||||
if c == '_' || c == '-' || c == ' ' {
|
||||
capitalize = true;
|
||||
} else if capitalize {
|
||||
result.push(c.to_ascii_uppercase());
|
||||
capitalize = false;
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl PascalCase for String {
|
||||
fn pascal_case(&self) -> String {
|
||||
self.as_str().pascal_case()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user