Skip to content

Commit

Permalink
check for duplicate names when parsing configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
hallettj committed Mar 18, 2024
1 parent e6b32b1 commit 0d098d3
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 32 deletions.
2 changes: 1 addition & 1 deletion crates/cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub async fn run(command: Command, context: &Context) -> anyhow::Result<()> {
/// Update the configuration in the current directory by introspecting the database.
async fn update(context: &Context) -> anyhow::Result<()> {
let schema = introspection::get_metadata_from_validation_schema(&context.mongo_config).await?;
let configuration = Configuration::from_schema(schema);
let configuration = Configuration::from_schema(schema)?;

configuration::write_directory(&context.path, &configuration).await?;

Expand Down
92 changes: 88 additions & 4 deletions crates/configuration/src/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::path::Path;

use anyhow::ensure;
use itertools::Itertools;
use schemars::JsonSchema;
use serde::Deserialize;

use crate::{native_queries::NativeQuery, read_directory, Schema};
use crate::{native_queries::NativeQuery, read_directory, schema::ObjectType, Schema};

#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
Expand All @@ -18,16 +20,98 @@ pub struct Configuration {
}

impl Configuration {
pub fn from_schema(schema: Schema) -> Self {
Self {
pub fn validate(schema: Schema, native_queries: Vec<NativeQuery>) -> anyhow::Result<Self> {
let config = Configuration {
schema,
..Default::default()
native_queries,
};

{
let duplicate_type_names: Vec<&str> = config
.object_types()
.map(|t| t.name.as_ref())
.duplicates()
.collect();
ensure!(
duplicate_type_names.is_empty(),
"configuration contains multiple definitions for these object type names: {}",
duplicate_type_names.join(", ")
);
}

{
let duplicate_collection_names: Vec<&str> = config
.schema
.collections
.iter()
.map(|c| c.name.as_ref())
.duplicates()
.collect();
ensure!(
duplicate_collection_names.is_empty(),
"configuration contains multiple definitions for these collection names: {}",
duplicate_collection_names.join(", ")
);
}

Ok(config)
}

pub fn from_schema(schema: Schema) -> anyhow::Result<Self> {
Self::validate(schema, Default::default())
}

pub async fn parse_configuration(
configuration_dir: impl AsRef<Path> + Send,
) -> anyhow::Result<Self> {
read_directory(configuration_dir).await
}

/// Returns object types collected from schema and native queries
pub fn object_types(&self) -> impl Iterator<Item = &ObjectType> {
let object_types_from_schema = self.schema.object_types.iter();
let object_types_from_native_queries = self
.native_queries
.iter()
.flat_map(|native_query| &native_query.object_types);
object_types_from_schema.chain(object_types_from_native_queries)
}
}

#[cfg(test)]
mod tests {
use mongodb::bson::doc;

use super::*;
use crate::{schema::Type, Schema};

#[test]
fn fails_with_duplicate_object_types() {
let schema = Schema {
collections: Default::default(),
object_types: vec![ObjectType {
name: "Album".to_owned(),
fields: Default::default(),
description: Default::default(),
}],
};
let native_queries = vec![NativeQuery {
name: "hello".to_owned(),
object_types: vec![ObjectType {
name: "Album".to_owned(),
fields: Default::default(),
description: Default::default(),
}],
result_type: Type::Object("Album".to_owned()),
command: doc! { "command": 1 },
arguments: Default::default(),
selection_criteria: Default::default(),
description: Default::default(),
mode: Default::default(),
}];
let result = Configuration::validate(schema, native_queries);
let error_msg = result.unwrap_err().to_string();
assert!(error_msg.contains("multiple definitions"));
assert!(error_msg.contains("Album"));
}
}
5 changes: 1 addition & 4 deletions crates/configuration/src/directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,7 @@ pub async fn read_directory(
.await?
.unwrap_or_default();

Ok(Configuration {
schema,
native_queries,
})
Configuration::validate(schema, native_queries)
}

/// Parse all files in a directory with one of the allowed configuration extensions according to
Expand Down
32 changes: 9 additions & 23 deletions crates/mongodb-connector/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,7 @@ pub async fn get_schema(
) -> Result<models::SchemaResponse, connector::SchemaError> {
let schema = &config.schema;
let collections = schema.collections.iter().map(map_collection).collect();

let object_types_from_schema = map_object_types(&schema.object_types);
let object_types_from_native_queries = map_object_types(
config
.native_queries
.iter()
.flat_map(|native_query| &native_query.object_types),
);
let object_types = object_types_from_schema
.chain(object_types_from_native_queries)
.collect();
let object_types = config.object_types().map(map_object_type).collect();

let functions = config
.native_queries
Expand All @@ -48,18 +38,14 @@ pub async fn get_schema(
})
}

fn map_object_types<'a>(
object_types: impl IntoIterator<Item = &'a schema::ObjectType> + 'a,
) -> impl Iterator<Item = (String, models::ObjectType)> + 'a {
object_types.into_iter().map(|t| {
(
t.name.clone(),
models::ObjectType {
fields: map_field_infos(&t.fields),
description: t.description.clone(),
},
)
})
fn map_object_type(object_type: &schema::ObjectType) -> (String, models::ObjectType) {
(
object_type.name.clone(),
models::ObjectType {
fields: map_field_infos(&object_type.fields),
description: object_type.description.clone(),
},
)
}

fn map_field_infos(fields: &[schema::ObjectField]) -> BTreeMap<String, models::ObjectField> {
Expand Down

0 comments on commit 0d098d3

Please sign in to comment.