use nautilus_schema::{ast::*, parser::Parser, Lexer, Result};
fn tokenize(source: &str) -> Result<Vec<nautilus_schema::Token>> {
let mut lexer = Lexer::new(source);
let mut tokens = Vec::new();
loop {
let token = lexer.next_token()?;
if matches!(token.kind, nautilus_schema::TokenKind::Eof) {
tokens.push(token);
break;
}
tokens.push(token);
}
Ok(tokens)
}
fn parse(source: &str) -> Result<Schema> {
let tokens = tokenize(source)?;
Parser::new(&tokens, source).parse_schema()
}
#[test]
fn test_parse_multiple_models() {
let source = r#"
model User {
id Int @id
}
model Post {
id Int @id
}
model Comment {
id Int @id
}
"#;
let schema = parse(source).unwrap();
assert_eq!(schema.models().count(), 3);
}
#[test]
fn test_parse_composite_primary_key() {
let source = r#"
model UserRole {
userId Int
roleId Int
@@id([userId, roleId])
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
assert!(model.has_composite_key());
match &model.attributes[0] {
ModelAttribute::Id(fields) => {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].value, "userId");
assert_eq!(fields[1].value, "roleId");
}
_ => panic!("Expected @@id attribute"),
}
}
#[test]
fn test_parse_composite_unique() {
let source = r#"
model User {
email String
username String
@@unique([email, username])
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
match &model.attributes[0] {
ModelAttribute::Unique(fields) => {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].value, "email");
assert_eq!(fields[1].value, "username");
}
_ => panic!("Expected @@unique attribute"),
}
}
#[test]
fn test_parse_index() {
let source = r#"
model Post {
title String
createdAt DateTime
@@index([createdAt, title])
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
match &model.attributes[0] {
ModelAttribute::Index { fields, .. } => {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].value, "createdAt");
assert_eq!(fields[1].value, "title");
}
_ => panic!("Expected @@index attribute"),
}
}
#[test]
fn test_parse_all_scalar_types() {
let source = r#"
model AllTypes {
str String
bool Boolean
int Int
bigInt BigInt
float Float
decimal Decimal(10, 2)
dateTime DateTime
bytes Bytes
json Json
uuid Uuid
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
assert_eq!(model.fields.len(), 10);
assert!(matches!(model.fields[0].field_type, FieldType::String));
assert!(matches!(model.fields[1].field_type, FieldType::Boolean));
assert!(matches!(model.fields[2].field_type, FieldType::Int));
assert!(matches!(model.fields[3].field_type, FieldType::BigInt));
assert!(matches!(model.fields[4].field_type, FieldType::Float));
assert!(matches!(
model.fields[5].field_type,
FieldType::Decimal {
precision: 10,
scale: 2
}
));
assert!(matches!(model.fields[6].field_type, FieldType::DateTime));
assert!(matches!(model.fields[7].field_type, FieldType::Bytes));
assert!(matches!(model.fields[8].field_type, FieldType::Json));
assert!(matches!(model.fields[9].field_type, FieldType::Uuid));
}
#[test]
fn test_parse_default_expressions() {
let source = r#"
model User {
id Int @id @default(autoincrement())
uuid Uuid @default(uuid())
createdAt DateTime @default(now())
role String @default("USER")
count Int @default(0)
active Boolean @default(true)
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
for field in &model.fields {
let default_attr = field
.attributes
.iter()
.find(|a| matches!(a, FieldAttribute::Default(..)));
assert!(
default_attr.is_some(),
"Field {} missing @default",
field.name.value
);
}
}
#[test]
fn test_parse_optional_and_array_fields() {
let source = r#"
model User {
optional String?
array String[]
required String
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
assert!(model.fields[0].is_optional());
assert!(!model.fields[0].is_array());
assert!(!model.fields[1].is_optional());
assert!(model.fields[1].is_array());
assert!(!model.fields[2].is_optional());
assert!(!model.fields[2].is_array());
}
#[test]
fn test_parse_relation_with_all_options() {
let source = r#"
model Post {
authorId Int
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Restrict)
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
let author_field = model.find_field("author").unwrap();
match &author_field.attributes[0] {
FieldAttribute::Relation {
fields,
references,
on_delete,
on_update,
..
} => {
assert_eq!(fields.as_ref().unwrap().len(), 1);
assert_eq!(fields.as_ref().unwrap()[0].value, "authorId");
assert_eq!(references.as_ref().unwrap().len(), 1);
assert_eq!(references.as_ref().unwrap()[0].value, "id");
assert_eq!(*on_delete, Some(ReferentialAction::Cascade));
assert_eq!(*on_update, Some(ReferentialAction::Restrict));
}
_ => panic!("Expected relation attribute"),
}
}
#[test]
fn test_parse_relation_with_name() {
let source = r#"
model Post {
authorId Int
reviewerId Int
author User @relation(name: "AuthoredPosts", fields: [authorId], references: [id])
reviewer User @relation(name: "ReviewedPosts", fields: [reviewerId], references: [id])
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
let author_field = model.find_field("author").unwrap();
match &author_field.attributes[0] {
FieldAttribute::Relation {
name,
fields,
references,
..
} => {
assert_eq!(name.as_ref().unwrap(), "AuthoredPosts");
assert_eq!(fields.as_ref().unwrap()[0].value, "authorId");
assert_eq!(references.as_ref().unwrap()[0].value, "id");
}
_ => panic!("Expected relation attribute"),
}
let reviewer_field = model.find_field("reviewer").unwrap();
match &reviewer_field.attributes[0] {
FieldAttribute::Relation {
name,
fields,
references,
..
} => {
assert_eq!(name.as_ref().unwrap(), "ReviewedPosts");
assert_eq!(fields.as_ref().unwrap()[0].value, "reviewerId");
assert_eq!(references.as_ref().unwrap()[0].value, "id");
}
_ => panic!("Expected relation attribute"),
}
}
#[test]
fn test_model_helper_methods() {
let source = r#"
model User {
id Int @id @map("user_id")
email String @unique
posts Post[]
@@map("users")
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
assert_eq!(model.table_name(), "users");
assert!(model.find_field("id").is_some());
assert!(model.find_field("nonexistent").is_none());
let relation_fields: Vec<_> = model.relation_fields().collect();
assert_eq!(relation_fields.len(), 1);
assert_eq!(relation_fields[0].name.value, "posts");
assert!(!model.has_composite_key());
}
#[test]
fn test_field_helper_methods() {
let source = r#"
model User {
id Int @id @map("user_id")
email String @unique
optional String?
array String[]
}
"#;
let schema = parse(source).unwrap();
let model = schema.models().next().unwrap();
let id_field = model.find_field("id").unwrap();
assert_eq!(id_field.column_name(), "user_id");
assert!(id_field.find_attribute("id").is_some());
assert!(id_field.find_attribute("nonexistent").is_none());
let optional_field = model.find_field("optional").unwrap();
assert!(optional_field.is_optional());
assert!(!optional_field.is_array());
let array_field = model.find_field("array").unwrap();
assert!(!array_field.is_optional());
assert!(array_field.is_array());
}
#[test]
fn test_error_recovery_continues_parsing() {
let source = r#"
model Bad {
id Int @id
// Missing closing brace - this would cause an error
model Good {
id Int @id
}
"#;
let schema = parse(source).unwrap();
assert!(schema.models().any(|m| m.name.value == "Good"));
}
#[test]
fn test_schema_with_comments() {
let source = r#"
// This is a datasource
datasource db {
provider = "postgresql"
}
/* Multi-line
comment */
model User {
id Int @id // inline comment
}
"#;
let schema = parse(source).unwrap();
assert_eq!(schema.declarations.len(), 2);
}