coreason-meta-engineering 0.1.0

Rust port of the CoReason Agentic Forge & AST Manipulation Layer
Documentation
// Copyright (c) 2026 CoReason, Inc.
// All rights reserved.
// SPDX-License-Identifier: LicenseRef-Prosperity-3.0

use regex::Regex;
use serde_json::Value;

pub fn to_kebab_case(name: &str) -> String {
    let r1 = Regex::new(r"(.)([A-Z][a-z]+)").unwrap();
    let r2 = Regex::new(r"([a-z0-9])([A-Z])").unwrap();
    
    let s1 = r1.replace_all(name, "${1}-${2}");
    let s2 = r2.replace_all(&s1, "${1}-${2}");
    
    s2.replace('_', "-").to_lowercase().trim_matches('-').to_string()
}

fn resolve_ts_type(prop_schema: &Value) -> String {
    match prop_schema.get("type").and_then(|v| v.as_str()) {
        Some("string") => "string".to_string(),
        Some("integer") | Some("number") => "number".to_string(),
        Some("boolean") => "boolean".to_string(),
        Some("array") => {
            let items = prop_schema.get("items").unwrap_or(&Value::Null);
            let item_type = resolve_ts_type(items);
            format!("{}[]", item_type)
        }
        Some("object") => "Record<string, any>".to_string(),
        _ => "any".to_string(),
    }
}

fn resolve_wit_type(prop_schema: &Value) -> String {
    match prop_schema.get("type").and_then(|v| v.as_str()) {
        Some("string") => "string".to_string(),
        Some("integer") => "s32".to_string(),
        Some("number") => "f64".to_string(),
        Some("boolean") => "bool".to_string(),
        Some("array") => {
            let items = prop_schema.get("items").unwrap_or(&Value::Null);
            let item_type = resolve_wit_type(items);
            format!("list<{}>", item_type)
        }
        Some("object") => "string".to_string(), // WIT does not support dynamic map; serialize as JSON string
        _ => "string".to_string(),
    }
}

pub fn generate_typescript_interface(name: &str, schema: &Value) -> Result<String, String> {
    let mut lines = Vec::new();
    lines.push(format!("export interface {} {{", name));
    
    let properties = match schema.get("properties").and_then(|v| v.as_object()) {
        Some(p) => p,
        None => return Ok(format!("export interface {} {{\n}}\n", name)),
    };
    
    let required_set: std::collections::HashSet<&str> = schema
        .get("required")
        .and_then(|v| v.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|val| val.as_str())
                .collect()
        })
        .unwrap_or_default();
        
    for (prop_name, prop_schema) in properties {
        let is_required = required_set.contains(prop_name.as_str());
        let optional_marker = if is_required { "" } else { "?" };
        let ts_type = resolve_ts_type(prop_schema);
        
        if let Some(desc) = prop_schema.get("description").and_then(|v| v.as_str()) {
            if !desc.is_empty() {
                lines.push(format!("  /** {} */", desc));
            }
        }
        lines.push(format!("  {}{}: {};", prop_name, optional_marker, ts_type));
    }
    
    lines.push("}".to_string());
    Ok(lines.join("\n") + "\n")
}

pub fn generate_wit_record(name: &str, schema: &Value) -> Result<String, String> {
    let kebab_name = to_kebab_case(name);
    let mut lines = Vec::new();
    lines.push(format!("record {} {{", kebab_name));
    
    let properties = match schema.get("properties").and_then(|v| v.as_object()) {
        Some(p) => p,
        None => return Ok(format!("record {} {{\n}}\n", kebab_name)),
    };
    
    let required_set: std::collections::HashSet<&str> = schema
        .get("required")
        .and_then(|v| v.as_array())
        .map(|arr| {
            arr.iter()
                .filter_map(|val| val.as_str())
                .collect()
        })
        .unwrap_or_default();
        
    for (prop_name, prop_schema) in properties {
        let is_required = required_set.contains(prop_name.as_str());
        let mut wit_type = resolve_wit_type(prop_schema);
        if !is_required {
            wit_type = format!("option<{}>", wit_type);
        }
        let kebab_prop_name = to_kebab_case(prop_name);
        lines.push(format!("    {}: {},", kebab_prop_name, wit_type));
    }
    
    lines.push("}".to_string());
    Ok(lines.join("\n") + "\n")
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_to_kebab_case() {
        assert_eq!(to_kebab_case("PascalCase"), "pascal-case");
        assert_eq!(to_kebab_case("camelCase"), "camel-case");
        assert_eq!(to_kebab_case("snake_case"), "snake-case");
        assert_eq!(to_kebab_case("kebab-case"), "kebab-case");
        assert_eq!(to_kebab_case("actionspace:node:test_agent"), "actionspace:node:test-agent");
    }

    #[test]
    fn test_generate_typescript_interface() {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "User name"
                },
                "age": {
                    "type": "integer"
                }
            },
            "required": ["name"]
        });
        let ts = generate_typescript_interface("User", &schema).unwrap();
        assert!(ts.contains("export interface User"));
        assert!(ts.contains("name: string;"));
        assert!(ts.contains("age?: number;"));
        assert!(ts.contains("User name"));
    }

    #[test]
    fn test_generate_wit_record() {
        let schema = json!({
            "type": "object",
            "properties": {
                "firstName": {
                    "type": "string"
                },
                "isActive": {
                    "type": "boolean"
                }
            },
            "required": ["firstName"]
        });
        let wit = generate_wit_record("UserRecord", &schema).unwrap();
        assert!(wit.contains("record user-record"));
        assert!(wit.contains("first-name: string,"));
        assert!(wit.contains("is-active: option<bool>,"));
    }
}