Skip to content

Commit

Permalink
feat(psl-core): implement parse_configuration_multi_file to correct…
Browse files Browse the repository at this point in the history
… `prisma/prisma`'s `getConfig` implementation (#4825)

* feat(psl-core): implement "parse_configuration_multi_file" to correct TypeScript's "getConfig" function

* chore: fix clippy
  • Loading branch information
jkomyno committed May 7, 2024
1 parent 133a47f commit d880d75
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 43 deletions.
19 changes: 8 additions & 11 deletions prisma-fmt/src/get_config.rs
@@ -1,4 +1,4 @@
use psl::{Diagnostics, ValidatedSchema};
use psl::{parser_database::Files, Diagnostics};
use serde::Deserialize;
use serde_json::json;
use std::collections::HashMap;
Expand Down Expand Up @@ -43,26 +43,23 @@ pub(crate) fn get_config(params: &str) -> Result<String, String> {
}

fn get_config_impl(params: GetConfigParams) -> Result<serde_json::Value, GetConfigError> {
let mut schema = psl::validate_multi_file(params.prisma_schema.into());
if schema.diagnostics.has_errors() {
return Err(create_get_config_error(&schema, &schema.diagnostics));
}
let (files, mut config) =
psl::parse_configuration_multi_file(params.prisma_schema.into()).map_err(create_get_config_error)?;

if !params.ignore_env_var_errors {
let overrides: Vec<(_, _)> = params.datasource_overrides.into_iter().collect();
schema
.configuration
config
.resolve_datasource_urls_prisma_fmt(&overrides, |key| params.env.get(key).map(String::from))
.map_err(|diagnostics| create_get_config_error(&schema, &diagnostics))?;
.map_err(|diagnostics| create_get_config_error((files, diagnostics)))?;
}

Ok(psl::get_config(&schema.configuration))
Ok(psl::get_config(&config))
}

fn create_get_config_error(schema: &ValidatedSchema, diagnostics: &Diagnostics) -> GetConfigError {
fn create_get_config_error((files, diagnostics): (Files, Diagnostics)) -> GetConfigError {
use std::fmt::Write as _;

let mut rendered_diagnostics = schema.render_diagnostics(diagnostics);
let mut rendered_diagnostics = files.render_diagnostics(&diagnostics);
write!(
rendered_diagnostics,
"\nValidation Error Count: {}",
Expand Down
38 changes: 35 additions & 3 deletions psl/parser-database/src/files.rs
@@ -1,27 +1,59 @@
use crate::FileId;
use diagnostics::Diagnostics;
use schema_ast::ast;
use std::ops::Index;

/// The content is a list of (file path, file source text, file AST).
///
/// The file path can be anything, the PSL implementation will only use it to display the file name
/// in errors. For example, files can come from nested directories.
pub(crate) struct Files(pub(super) Vec<(String, schema_ast::SourceFile, ast::SchemaAst)>);
pub struct Files(pub Vec<(String, schema_ast::SourceFile, ast::SchemaAst)>);

impl Files {
pub(crate) fn iter(&self) -> impl Iterator<Item = (FileId, &String, &schema_ast::SourceFile, &ast::SchemaAst)> {
/// Create a new Files instance from multiple files.
pub fn new(files: Vec<(String, schema_ast::SourceFile)>, diagnostics: &mut Diagnostics) -> Self {
let asts = files
.into_iter()
.enumerate()
.map(|(file_idx, (path, source))| {
let id = FileId(file_idx as u32);
let ast = schema_ast::parse_schema(source.as_str(), diagnostics, id);
(path, source, ast)
})
.collect();
Self(asts)
}

/// Iterate all parsed files.
#[allow(clippy::should_implement_trait)]
pub fn iter(&self) -> impl Iterator<Item = (FileId, &String, &schema_ast::SourceFile, &ast::SchemaAst)> {
self.0
.iter()
.enumerate()
.map(|(idx, (path, contents, ast))| (FileId(idx as u32), path, contents, ast))
}

pub(crate) fn into_iter(self) -> impl Iterator<Item = (FileId, String, schema_ast::SourceFile, ast::SchemaAst)> {
/// Iterate all parsed files, consuming the parser database.
#[allow(clippy::should_implement_trait)]
pub fn into_iter(self) -> impl Iterator<Item = (FileId, String, schema_ast::SourceFile, ast::SchemaAst)> {
self.0
.into_iter()
.enumerate()
.map(|(idx, (path, contents, ast))| (FileId(idx as u32), path, contents, ast))
}

/// Render the given diagnostics (warnings + errors) into a String.
/// This method is multi-file aware.
pub fn render_diagnostics(&self, diagnostics: &Diagnostics) -> String {
let mut out = Vec::new();

for error in diagnostics.errors() {
let (file_name, source, _) = &self[error.span().file_id];
error.pretty_print(&mut out, file_name, source.as_str()).unwrap();
}

String::from_utf8(out).unwrap()
}
}

impl Index<crate::FileId> for Files {
Expand Down
25 changes: 11 additions & 14 deletions psl/parser-database/src/lib.rs
Expand Up @@ -38,21 +38,21 @@ mod names;
mod relations;
mod types;

use self::{context::Context, interner::StringId, relations::Relations, types::Types};
pub use coerce_expression::{coerce, coerce_array, coerce_opt};
pub use diagnostics::FileId;
use diagnostics::{DatamodelError, Diagnostics};
pub use files::Files;
pub use ids::*;
pub use names::is_reserved_type_name;
use names::Names;
pub use relations::{ManyToManyRelationId, ReferentialAction, RelationId};
pub use schema_ast::{ast, SourceFile};
pub use types::{
IndexAlgorithm, IndexFieldPath, IndexType, OperatorClass, RelationFieldId, ScalarFieldId, ScalarFieldType,
ScalarType, SortOrder,
};

use self::{context::Context, files::Files, interner::StringId, relations::Relations, types::Types};
use diagnostics::{DatamodelError, Diagnostics};
use names::Names;

/// ParserDatabase is a container for a Schema AST, together with information
/// gathered during schema validation. Each validation step enriches the
/// database with information that can be used to work with the schema, without
Expand Down Expand Up @@ -88,16 +88,7 @@ impl ParserDatabase {

/// See the docs on [ParserDatabase](/struct.ParserDatabase.html).
pub fn new(schemas: Vec<(String, schema_ast::SourceFile)>, diagnostics: &mut Diagnostics) -> Self {
let asts = schemas
.into_iter()
.enumerate()
.map(|(file_idx, (path, source))| {
let id = FileId(file_idx as u32);
let ast = schema_ast::parse_schema(source.as_str(), diagnostics, id);
(path, source, ast)
})
.collect();
let asts = Files(asts);
let asts = Files::new(schemas, diagnostics);

let mut interner = Default::default();
let mut names = Default::default();
Expand Down Expand Up @@ -161,6 +152,12 @@ impl ParserDatabase {
}
}

/// Render the given diagnostics (warnings + errors) into a String.
/// This method is multi-file aware.
pub fn render_diagnostics(&self, diagnostics: &Diagnostics) -> String {
self.asts.render_diagnostics(diagnostics)
}

/// The parsed AST. This methods asserts that there is a single prisma schema file. As
/// multi-file schemas are implemented, calls to this methods should be replaced with
/// `ParserDatabase::ast()` and `ParserDatabase::iter_asts()`.
Expand Down
6 changes: 6 additions & 0 deletions psl/psl-core/src/configuration/configuration_struct.rs
Expand Up @@ -14,6 +14,12 @@ pub struct Configuration {
}

impl Configuration {
pub fn extend(&mut self, configuration: Configuration) {
self.generators.extend(configuration.generators);
self.datasources.extend(configuration.datasources);
self.warnings.extend(configuration.warnings);
}

pub fn validate_that_one_datasource_is_provided(&self) -> Result<(), Diagnostics> {
if self.datasources.is_empty() {
Err(DatamodelError::new_validation_error(
Expand Down
35 changes: 22 additions & 13 deletions psl/psl-core/src/lib.rs
Expand Up @@ -29,7 +29,7 @@ pub use set_config_dir::set_config_dir;

use self::validate::{datasource_loader, generator_loader};
use diagnostics::Diagnostics;
use parser_database::{ast, ParserDatabase, SourceFile};
use parser_database::{ast, Files, ParserDatabase, SourceFile};

/// The collection of all available connectors.
pub type ConnectorRegistry<'a> = &'a [&'static dyn datamodel_connector::Connector];
Expand All @@ -54,18 +54,7 @@ impl ValidatedSchema {
}

pub fn render_own_diagnostics(&self) -> String {
self.render_diagnostics(&self.diagnostics)
}

pub fn render_diagnostics(&self, diagnostics: &Diagnostics) -> String {
let mut out = Vec::new();

for error in diagnostics.errors() {
let (file_name, source, _) = &self.db[error.span().file_id];
error.pretty_print(&mut out, file_name, source.as_str()).unwrap();
}

String::from_utf8(out).unwrap()
self.db.render_diagnostics(&self.diagnostics)
}
}

Expand Down Expand Up @@ -149,6 +138,26 @@ pub fn parse_configuration(
diagnostics.to_result().map(|_| out)
}

pub fn parse_configuration_multi_file(
files: Vec<(String, SourceFile)>,
connectors: ConnectorRegistry<'_>,
) -> Result<(Files, Configuration), (Files, diagnostics::Diagnostics)> {
let mut diagnostics = Diagnostics::default();
let mut configuration = Configuration::default();

let asts = Files::new(files, &mut diagnostics);

for (_, _, _, ast) in asts.iter() {
let out = validate_configuration(ast, &mut diagnostics, connectors);
configuration.extend(out);
}

match diagnostics.to_result() {
Ok(_) => Ok((asts, configuration)),
Err(err) => Err((asts, err)),
}
}

fn validate_configuration(
schema_ast: &ast::SchemaAst,
diagnostics: &mut Diagnostics,
Expand Down
9 changes: 9 additions & 0 deletions psl/psl/src/lib.rs
Expand Up @@ -2,6 +2,7 @@
#![deny(rust_2018_idioms, unsafe_code, missing_docs)]

pub use psl_core::builtin_connectors;
use psl_core::parser_database::Files;
pub use psl_core::{
builtin_connectors::{can_have_capability, can_support_relation_load_strategy, has_capability},
datamodel_connector,
Expand Down Expand Up @@ -40,6 +41,14 @@ pub fn parse_configuration(schema: &str) -> Result<Configuration, Diagnostics> {
psl_core::parse_configuration(schema, builtin_connectors::BUILTIN_CONNECTORS)
}

/// Parses and validates Prisma schemas, but skip analyzing everything except datasource and generator
/// blocks.
pub fn parse_configuration_multi_file(
files: Vec<(String, SourceFile)>,
) -> Result<(Files, Configuration), (Files, Diagnostics)> {
psl_core::parse_configuration_multi_file(files, builtin_connectors::BUILTIN_CONNECTORS)
}

/// Parse and analyze a Prisma schema.
pub fn parse_schema(file: impl Into<SourceFile>) -> Result<ValidatedSchema, String> {
let mut schema = validate(file.into());
Expand Down
2 changes: 0 additions & 2 deletions psl/schema-ast/src/ast/find_at_position.rs
Expand Up @@ -354,9 +354,7 @@ impl<'ast> SourcePosition<'ast> {
pub enum PropertyPosition<'ast> {
/// prop
Property,
///
Value(&'ast str),
///
FunctionValue(&'ast str),
}

Expand Down

0 comments on commit d880d75

Please sign in to comment.