saola-psl 0.1.0

Prisma Schema Language parser and AST for Saola ORM
Documentation
use crate::common::*;
use saola_psl::parser_database::ScalarType;

#[test]
fn parse_basic_model() {
    let dml = indoc! {r#"
        model User {
          id Int @id
          firstName String
          lastName String
        }
    "#};

    let schema = saola_psl::parse_schema_without_extensions(dml).unwrap();
    let user_model = schema.assert_has_model("User");

    user_model
        .assert_has_scalar_field("firstName")
        .assert_scalar_type(ScalarType::String);

    user_model
        .assert_has_scalar_field("lastName")
        .assert_scalar_type(ScalarType::String);
}

#[test]
fn parse_basic_enum() {
    let dml = indoc! {r#"
        enum Roles {
          Admin
          User
          USER
          ADMIN
          ADMIN_USER
          Admin_User
          HHorse99
        }
    "#};

    let schema = saola_psl::parse_schema_without_extensions(dml).unwrap();
    let role_enum = schema.db.find_enum("Roles").unwrap();
    let values: Vec<_> = role_enum.values().map(|v| v.name()).collect();

    assert_eq!(
        values,
        &["Admin", "User", "USER", "ADMIN", "ADMIN_USER", "Admin_User", "HHorse99"]
    );
}

#[test]
fn parse_comments() {
    let dml = indoc! {r#"
        /// The user model.
        model User {
          id Int @id
          /// The first name.
          /// Can be multi-line.
          firstName String
          lastName String
        }
    "#};

    let schema = saola_psl::parse_schema_without_extensions(dml).unwrap();

    let user_model = schema.assert_has_model("User");
    user_model.assert_with_documentation("The user model.");

    user_model
        .assert_has_scalar_field("firstName")
        .assert_with_documentation("The first name.\nCan be multi-line.");
}

#[test]
fn must_error_for_invalid_model_names() {
    let dml = indoc! {r#"
        model PrismaClient {
          id Int @id
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating model "PrismaClient": The model name `PrismaClient` is invalid. It is a reserved name. Please change it. Read more at https://pris.ly/d/naming-models
          -->  schema.prisma:1
           | 
           | 
         1 | model PrismaClient {
         2 |   id Int @id
         3 | }
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn must_error_for_invalid_enum_names() {
    let dml = indoc! {r#"
        enum PrismaClient {
          one
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r##"
        error: Error validating enum `PrismaClient`: The enum name `PrismaClient` is invalid. It is a reserved name. Please change it. Read more at https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-schema/data-model#naming-enums
          -->  schema.prisma:1
           | 
           | 
         1 | enum PrismaClient {
         2 |   one
         3 | }
           | 
    "##]];

    expectation.assert_eq(&error);
}

#[test]
fn must_return_good_error_messages_for_numbers_in_enums() {
    let dml = indoc! {r#"
        enum MyEnum {
          1
          TWO
          THREE
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating: The name of a Enum Value must not start with a number.
          -->  schema.prisma:2
           | 
         1 | enum MyEnum {
         2 |   1
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn must_return_good_error_message_for_empty_enum() {
    let dml = indoc! {r#"
        enum MyEnum {

        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating: An enum must have at least one value.
          -->  schema.prisma:1
           | 
           | 
         1 | enum MyEnum {
         2 | 
         3 | }
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn must_return_good_error_message_for_enum_with_all_variants_commented_out() {
    let dml = indoc! {r#"
        enum MyEnum {
          // 1
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating: An enum must have at least one value.
          -->  schema.prisma:1
           | 
           | 
         1 | enum MyEnum {
         2 |   // 1
         3 | }
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn invalid_line_must_not_break() {
    let dml = indoc! {r#"
        $ /a/b/c:.

        model Blog {
          id Int @id
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating: This line is invalid. It does not start with any known Prisma schema keyword.
          -->  schema.prisma:1
           | 
           | 
         1 | $ /a/b/c:.
         2 | 
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn type_aliases_must_error() {
    let dml = indoc! {r#"
      type MyString = String @default("B")

      model A {
        id  Int      @id
        val MyString
      }
    "#};

    let error = parse_unwrap_err(dml);

    let expectation = expect![[r#"
        error: Error validating: Invalid type definition. Please check the documentation in https://pris.ly/d/composite-types
          -->  schema.prisma:1
           | 
           | 
         1 | type MyString = String @default("B")
           | 
        error: Type "MyString" is neither a built-in type, nor refers to another model, composite type, or enum.
          -->  schema.prisma:5
           | 
         4 |   id  Int      @id
         5 |   val MyString
           | 
    "#]];

    expectation.assert_eq(&error);
}

#[test]
fn must_return_good_error_message_for_type_match() {
    let dml = indoc! {r#"
        model User {
          firstName String
        }
        model B {
          a  datetime
          b  footime
          c user
          d DB
          e JS
        }

        datasource db {
          provider   = "postgresql"
          url        = "dummy-url"
          extensions = [citext, pg_trgm]
        }

        generator js {
          provider        = "prisma-client"
          previewFeatures = ["postgresqlExtensions"]
        }
    "#};

    let error = parse_unwrap_err(dml);

    let expected = expect![[r#"
        error: Type "datetime" is neither a built-in type, nor refers to another model, composite type, or enum. Did you mean "DateTime"?
          -->  schema.prisma:5
           | 
         4 | model B {
         5 |   a  datetime
           | 
        error: Type "footime" is neither a built-in type, nor refers to another model, composite type, or enum.
          -->  schema.prisma:6
           | 
         5 |   a  datetime
         6 |   b  footime
           | 
        error: Type "user" is neither a built-in type, nor refers to another model, composite type, or enum. Did you mean "User"?
          -->  schema.prisma:7
           | 
         6 |   b  footime
         7 |   c user
           | 
        error: Type "DB" is neither a built-in type, nor refers to another model, composite type, or enum.
          -->  schema.prisma:8
           | 
         7 |   c user
         8 |   d DB
           | 
        error: Type "JS" is neither a built-in type, nor refers to another model, composite type, or enum.
          -->  schema.prisma:9
           | 
         8 |   d DB
         9 |   e JS
           | 
    "#]];

    expected.assert_eq(&error);
}