#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../readme.md")]
use crate::config::Configuration;
use crate::convex::codegen::run_codegen;
use crate::convex::parser::{parse_function_ast, parse_schema_ast};
use crate::convex::{create_function_asts, create_schema_ast};
use crate::error::ConvexTypeGeneratorError;
use crate::fs::rcfp;
pub mod config;
mod convex;
mod error;
mod fs;
pub mod prelude;
pub use serde;
pub use serde_json;
#[cfg(feature = "client")]
#[cfg_attr(docsrs, doc(cfg(feature = "client")))]
pub use crate::convex::ConvexClientExt;
pub fn generate(config: Configuration) -> Result<(), ConvexTypeGeneratorError>
{
if !config.schema_path.exists() {
return Err(ConvexTypeGeneratorError::MissingSchemaFile);
}
let schema_path = config
.schema_path
.canonicalize()
.map_err(|e| ConvexTypeGeneratorError::IOError {
file: config.schema_path.to_string_lossy().to_string(),
error: e,
})?;
let schema_ast = create_schema_ast(schema_path)?;
let function_paths = rcfp(&config)?;
let function_asts = create_function_asts(function_paths)?;
let parsed_schema = parse_schema_ast(schema_ast)?;
let parsed_functions = parse_function_ast(function_asts)?;
run_codegen(&config.out_file, (parsed_schema, parsed_functions))?;
Ok(())
}
#[cfg(test)]
mod generate_tests
{
use std::fs;
use tempfile::tempdir;
use super::generate;
use crate::config::Configuration;
#[test]
fn generate_errors_when_schema_path_missing()
{
let tmp = tempdir().unwrap();
let cfg = Configuration {
schema_path: tmp.path().join("nope/schema.ts"),
out_file: tmp.path().join("out.rs"),
..Default::default()
};
let err = generate(cfg).unwrap_err();
assert!(matches!(err, crate::error::ConvexTypeGeneratorError::MissingSchemaFile));
}
#[test]
fn generate_accepts_define_table_with_chained_index()
{
const SCHEMA: &str = r#"
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
users: defineTable({ email: v.string() }).index("by_email", ["email"]),
});
"#;
let tmp = tempdir().unwrap();
let convex = tmp.path().join("convex");
fs::create_dir_all(&convex).unwrap();
let schema = convex.join("schema.ts");
fs::write(&schema, SCHEMA).unwrap();
let out = tmp.path().join("out.rs");
let cfg = Configuration {
schema_path: schema.clone(),
out_file: out.clone(),
convex_dir: convex,
function_paths: Vec::new(),
};
generate(cfg).unwrap();
let body = fs::read_to_string(&out).unwrap();
assert!(body.contains("UsersTable"), "expected table struct: {body}");
assert!(body.contains("pub email: String,"), "expected email column: {body}");
}
#[test]
fn generate_duplicate_export_names_produce_unique_args_structs()
{
const SCHEMA: &str = r#"
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
games: defineTable({ n: v.number() }),
});
"#;
const MOD_A: &str = r#"
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { a: v.number() },
handler: async (_ctx, _args) => null,
});
"#;
const MOD_B: &str = r#"
import { query } from "./_generated/server";
import { v } from "convex/values";
export const list = query({
args: { b: v.string() },
handler: async (_ctx, _args) => null,
});
"#;
let tmp = tempdir().unwrap();
let convex = tmp.path().join("convex");
fs::create_dir_all(&convex).unwrap();
let schema = convex.join("schema.ts");
fs::write(&schema, SCHEMA).unwrap();
fs::write(convex.join("mod_a.ts"), MOD_A).unwrap();
fs::write(convex.join("mod_b.ts"), MOD_B).unwrap();
let out = tmp.path().join("out.rs");
fs::create_dir_all(out.parent().unwrap()).unwrap();
let cfg = Configuration {
schema_path: schema,
out_file: out.clone(),
convex_dir: convex,
function_paths: Vec::new(),
};
generate(cfg).unwrap();
let body = fs::read_to_string(&out).unwrap();
assert!(body.contains("pub struct ModAListArgs"), "expected mod_a:list args struct");
assert!(body.contains("pub struct ModBListArgs"), "expected mod_b:list args struct");
assert!(body.contains("\"mod_a:list\""), "expected first module path in emitted Rust");
assert!(body.contains("\"mod_b:list\""), "expected second module path in emitted Rust");
}
}