mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 01:12:56 +03:00
constraint allowed open dd identifier names (#356)
V3_GIT_ORIGIN_REV_ID: 66512df837ccd1b72cd39fd35979bdc8ce39de55
This commit is contained in:
parent
47f4d2ac76
commit
7c7e50505f
@ -158,219 +158,243 @@ pub fn count_command(command: Qualified<CommandName>, all_usage_counts: &mut Usa
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_usage_count() {
|
||||
let model_count1 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
count: 1,
|
||||
};
|
||||
let model_count2 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model2".to_string())),
|
||||
count: 5,
|
||||
};
|
||||
let model_count3 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model3".to_string())),
|
||||
count: 2,
|
||||
};
|
||||
let command_count1 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName("command1".to_string())),
|
||||
count: 2,
|
||||
};
|
||||
let command_count2 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName("command2".to_string())),
|
||||
count: 1,
|
||||
};
|
||||
let command_count3 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName("command3".to_string())),
|
||||
count: 3,
|
||||
};
|
||||
let usage_counts = UsagesCounts {
|
||||
models_used: vec![model_count1, model_count2.clone()],
|
||||
commands_used: vec![command_count1, command_count2.clone()],
|
||||
};
|
||||
let mut aggregator = UsagesCounts {
|
||||
models_used: vec![model_count2, model_count3],
|
||||
commands_used: vec![command_count2, command_count3],
|
||||
};
|
||||
extend_usage_count(usage_counts, &mut aggregator);
|
||||
let expected = UsagesCounts {
|
||||
models_used: vec![
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model2".to_string())),
|
||||
count: 10,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model3".to_string())),
|
||||
count: 2,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
count: 1,
|
||||
},
|
||||
],
|
||||
commands_used: vec![
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command2".to_string()),
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command3".to_string()),
|
||||
),
|
||||
count: 3,
|
||||
},
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string()),
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(aggregator, expected);
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use open_dds::{commands::CommandName, identifier, models::ModelName};
|
||||
|
||||
#[test]
|
||||
fn test_counter_functions() {
|
||||
let mut aggregator = UsagesCounts::new();
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName("command1".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: Vec::new(),
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
),
|
||||
count: 1,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName("command1".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: Vec::new(),
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
count: 1,
|
||||
}],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
count: 2,
|
||||
}],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName("model2".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
use crate::{
|
||||
execute::model_tracking::{
|
||||
count_command, count_model, extend_usage_count, CommandCount, ModelCount, UsagesCounts,
|
||||
},
|
||||
metadata::resolved::subgraph::Qualified,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_extend_usage_count() {
|
||||
let model_count1 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
count: 1,
|
||||
};
|
||||
let model_count2 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model2"))),
|
||||
count: 5,
|
||||
};
|
||||
let model_count3 = ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model3"))),
|
||||
count: 2,
|
||||
};
|
||||
let command_count1 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName(identifier!("command1"))),
|
||||
count: 2,
|
||||
};
|
||||
let command_count2 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName(identifier!("command2"))),
|
||||
count: 1,
|
||||
};
|
||||
let command_count3 = CommandCount {
|
||||
command: Qualified::new("subgraph".to_string(), CommandName(identifier!("command3"))),
|
||||
count: 3,
|
||||
};
|
||||
let usage_counts = UsagesCounts {
|
||||
models_used: vec![model_count1, model_count2.clone()],
|
||||
commands_used: vec![command_count1, command_count2.clone()],
|
||||
};
|
||||
let mut aggregator = UsagesCounts {
|
||||
models_used: vec![model_count2, model_count3],
|
||||
commands_used: vec![command_count2, command_count3],
|
||||
};
|
||||
extend_usage_count(usage_counts, &mut aggregator);
|
||||
let expected = UsagesCounts {
|
||||
models_used: vec![
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model2"))),
|
||||
count: 10,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model3"))),
|
||||
count: 2,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model2".to_string())),
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
count: 1,
|
||||
}
|
||||
],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName("command2".to_string())),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model1".to_string())),
|
||||
count: 2,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName("model2".to_string())),
|
||||
count: 1,
|
||||
}
|
||||
],
|
||||
commands_used: vec![
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command1".to_string())
|
||||
CommandName(identifier!("command2")),
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName("command2".to_string())
|
||||
CommandName(identifier!("command3")),
|
||||
),
|
||||
count: 3,
|
||||
},
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1")),
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
],
|
||||
};
|
||||
assert_eq!(aggregator, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_counter_functions() {
|
||||
let mut aggregator = UsagesCounts::new();
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName(identifier!("command1"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: Vec::new(),
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 1,
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName(identifier!("command1"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: Vec::new(),
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
count: 1,
|
||||
}],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![ModelCount {
|
||||
model: Qualified::new("subgraph".to_string(), ModelName(identifier!("model1"))),
|
||||
count: 2,
|
||||
}],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_model(
|
||||
Qualified::new("subgraph".to_string(), ModelName(identifier!("model2"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![
|
||||
ModelCount {
|
||||
model: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
ModelName(identifier!("model1"))
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
ModelName(identifier!("model2"))
|
||||
),
|
||||
count: 1,
|
||||
}
|
||||
],
|
||||
commands_used: vec![CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 2,
|
||||
}]
|
||||
}
|
||||
);
|
||||
count_command(
|
||||
Qualified::new("subgraph".to_string(), CommandName(identifier!("command2"))),
|
||||
&mut aggregator,
|
||||
);
|
||||
assert_eq!(
|
||||
aggregator,
|
||||
UsagesCounts {
|
||||
models_used: vec![
|
||||
ModelCount {
|
||||
model: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
ModelName(identifier!("model1"))
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
ModelCount {
|
||||
model: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
ModelName(identifier!("model2"))
|
||||
),
|
||||
count: 1,
|
||||
}
|
||||
],
|
||||
commands_used: vec![
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command1"))
|
||||
),
|
||||
count: 2,
|
||||
},
|
||||
CommandCount {
|
||||
command: Qualified::new(
|
||||
"subgraph".to_string(),
|
||||
CommandName(identifier!("command2"))
|
||||
),
|
||||
count: 1,
|
||||
}
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ use indexmap::IndexMap;
|
||||
use lang_graphql::ast::common as ast;
|
||||
use ndc_client as ndc;
|
||||
use open_dds::data_connector::DataConnectorName;
|
||||
use open_dds::identifier;
|
||||
use open_dds::models::EnableAllOrSpecific;
|
||||
use open_dds::permissions::{Role, TypeOutputPermission, TypePermissionsV1};
|
||||
use open_dds::types::{
|
||||
@ -184,7 +185,7 @@ pub fn resolve_object_type(
|
||||
if !global_id_fields.is_empty() {
|
||||
// Throw error if the object type has a field called id" and has global fields configured.
|
||||
// Because, when the global id fields are configured, the `id` field will be auto-generated.
|
||||
if resolved_fields.contains_key(&FieldName("id".into())) {
|
||||
if resolved_fields.contains_key(&FieldName(identifier!("id"))) {
|
||||
return Err(Error::IdFieldConflictingGlobalId {
|
||||
type_name: qualified_type_name.clone(),
|
||||
});
|
||||
@ -297,7 +298,7 @@ pub fn resolve_data_connector_type_mapping(
|
||||
} else {
|
||||
// If no mapping is defined for a field, implicitly create a mapping
|
||||
// with the same column name as the field.
|
||||
field_name.0.to_owned()
|
||||
field_name.to_string()
|
||||
};
|
||||
let source_column =
|
||||
get_column(ndc_object_type, field_name, &resolved_field_mapping_column)?;
|
||||
|
@ -367,7 +367,7 @@
|
||||
},
|
||||
"target": {
|
||||
"argument": {
|
||||
"argumentName": "dummy-argument"
|
||||
"argumentName": "dummy_argument"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -463,7 +463,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/DataConnectorName",
|
||||
"title": "DataConnectorName",
|
||||
"description": "The name of a data connector.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"DataConnectorUrlV1": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/DataConnectorUrlV1",
|
||||
@ -684,7 +685,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/CustomTypeName",
|
||||
"title": "CustomTypeName",
|
||||
"description": "The name of a user-defined type.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"FieldDefinition": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/FieldDefinition",
|
||||
@ -726,7 +728,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/FieldName",
|
||||
"title": "FieldName",
|
||||
"description": "The name of a field in a user-defined object type.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"TypeReference": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/TypeReference",
|
||||
@ -1307,7 +1310,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/ModelName",
|
||||
"title": "ModelName",
|
||||
"description": "The name of data model.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"ArgumentDefinition": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/ArgumentDefinition",
|
||||
@ -1337,7 +1341,8 @@
|
||||
"ArgumentName": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/ArgumentName",
|
||||
"title": "ArgumentName",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"ModelSource": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/ModelSource",
|
||||
@ -1664,7 +1669,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/CommandName",
|
||||
"title": "CommandName",
|
||||
"description": "The name of a command.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"CommandSource": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/CommandSource",
|
||||
@ -1886,7 +1892,8 @@
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/RelationshipName",
|
||||
"title": "RelationshipName",
|
||||
"description": "The name of the GraphQL relationship field.",
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
|
||||
},
|
||||
"RelationshipTarget": {
|
||||
"$id": "https://hasura.io/jsonschemas/metadata/RelationshipTarget",
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{impl_JsonSchema_with_OpenDd_for, types::TypeReference};
|
||||
use crate::{identifier::Identifier, impl_JsonSchema_with_OpenDd_for, types::TypeReference};
|
||||
|
||||
#[derive(
|
||||
Serialize,
|
||||
@ -15,7 +15,7 @@ use crate::{impl_JsonSchema_with_OpenDd_for, types::TypeReference};
|
||||
derive_more::Display,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct ArgumentName(pub String);
|
||||
pub struct ArgumentName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(ArgumentName);
|
||||
|
||||
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
arguments::{ArgumentDefinition, ArgumentName},
|
||||
data_connector::DataConnectorName,
|
||||
identifier::Identifier,
|
||||
impl_JsonSchema_with_OpenDd_for,
|
||||
types::{GraphQlFieldName, TypeReference},
|
||||
};
|
||||
@ -22,7 +23,7 @@ use crate::{
|
||||
derive_more::Display,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct CommandName(pub String);
|
||||
pub struct CommandName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(CommandName);
|
||||
|
||||
|
@ -6,7 +6,7 @@ mod v1;
|
||||
|
||||
pub use v1::{DataConnectorLinkV1, DataConnectorUrlV1 as DataConnectorUrl, ReadWriteUrls};
|
||||
|
||||
use crate::impl_OpenDd_default_for;
|
||||
use crate::{identifier::Identifier, impl_OpenDd_default_for};
|
||||
|
||||
/// The name of a data connector.
|
||||
#[derive(
|
||||
@ -21,7 +21,7 @@ use crate::impl_OpenDd_default_for;
|
||||
derive_more::Display,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct DataConnectorName(pub String);
|
||||
pub struct DataConnectorName(pub Identifier);
|
||||
|
||||
#[derive(Serialize, Clone, Debug, PartialEq, opendds_derive::OpenDd)]
|
||||
#[serde(tag = "version", content = "definition")]
|
||||
|
103
v3/open-dds/src/identifier.rs
Normal file
103
v3/open-dds/src/identifier.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use schemars::schema::{Schema::Object as SchemaObjectVariant, SchemaObject, StringValidation};
|
||||
use serde::{de::Error, Deserialize, Serialize};
|
||||
use std::ops::Deref;
|
||||
|
||||
use crate::{
|
||||
impl_JsonSchema_with_OpenDd_for,
|
||||
traits::{OpenDd, OpenDdDeserializeError},
|
||||
};
|
||||
|
||||
// Macro to produce a validated identifier using a string literal that crashes
|
||||
// if the literal is invalid. Does not work for non-literal strings to avoid
|
||||
// use on user supplied input.
|
||||
#[macro_export]
|
||||
macro_rules! identifier {
|
||||
($name:literal) => {
|
||||
open_dds::identifier::Identifier::new($name.to_string()).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
/// Type capturing an identifier used within the metadata. The wrapped String
|
||||
/// is guaranteed to be a valid identifier, i.e.
|
||||
/// - starts with an alphabet or underscore
|
||||
/// - all characters are either alphanumeric or underscore
|
||||
#[derive(Serialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, derive_more::Display)]
|
||||
pub struct Identifier(pub String);
|
||||
|
||||
impl Identifier {
|
||||
pub fn new(string: String) -> Result<Identifier, &'static str> {
|
||||
if let Some(c) = string.chars().next() {
|
||||
if !c.is_ascii_alphabetic() && c != '_' {
|
||||
return Err("must start with an alphabet or underscore");
|
||||
}
|
||||
} else {
|
||||
return Err("cannot be an empty string");
|
||||
}
|
||||
if !string
|
||||
.chars()
|
||||
.all(|c| c.is_ascii_alphanumeric() || c == '_')
|
||||
{
|
||||
return Err("must contain only alphanumeric characters or underscore");
|
||||
}
|
||||
Ok(Identifier(string))
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Identifier {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl OpenDd for Identifier {
|
||||
fn deserialize(json: serde_json::Value) -> Result<Self, OpenDdDeserializeError> {
|
||||
let string: String =
|
||||
serde_json::from_value(json).map_err(|error| OpenDdDeserializeError {
|
||||
error,
|
||||
path: Default::default(),
|
||||
})?;
|
||||
Identifier::new(string).map_err(|e| OpenDdDeserializeError {
|
||||
error: serde_json::Error::custom(e),
|
||||
path: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
SchemaObjectVariant(SchemaObject {
|
||||
instance_type: Some(schemars::schema::SingleOrVec::Single(Box::new(
|
||||
schemars::schema::InstanceType::String,
|
||||
))),
|
||||
string: Some(Box::new(StringValidation {
|
||||
pattern: Some("^[_a-zA-Z][_a-zA-Z0-9]*$".to_string()),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn _schema_name() -> String {
|
||||
"Identifier".to_string()
|
||||
}
|
||||
|
||||
fn _schema_is_referenceable() -> bool {
|
||||
// This is a tiny leaf schema so just make it non-referenceable to avoid a layer of
|
||||
// indirection in the overall JSONSchema.
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(Identifier);
|
||||
|
||||
impl<'de> Deserialize<'de> for Identifier {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
OpenDd::deserialize(serde_json::Value::deserialize(deserializer)?).map_err(D::Error::custom)
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ pub mod commands;
|
||||
pub mod data_connector;
|
||||
pub mod flags;
|
||||
pub mod graphql_config;
|
||||
pub mod identifier;
|
||||
pub mod models;
|
||||
pub mod permissions;
|
||||
pub mod relationships;
|
||||
|
@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
arguments::{ArgumentDefinition, ArgumentName},
|
||||
data_connector::DataConnectorName,
|
||||
identifier::Identifier,
|
||||
impl_JsonSchema_with_OpenDd_for,
|
||||
traits::{OpenDd, OpenDdDeserializeError},
|
||||
types::{CustomTypeName, FieldName, GraphQlFieldName, GraphQlTypeName},
|
||||
@ -23,7 +24,7 @@ use crate::{
|
||||
derive_more::Display,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct ModelName(pub String);
|
||||
pub struct ModelName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(ModelName);
|
||||
|
||||
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
arguments::ArgumentName,
|
||||
commands::CommandName,
|
||||
identifier::Identifier,
|
||||
impl_JsonSchema_with_OpenDd_for,
|
||||
models::ModelName,
|
||||
permissions::ValueExpression,
|
||||
@ -22,7 +23,7 @@ use crate::{
|
||||
Hash,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct RelationshipName(pub String);
|
||||
pub struct RelationshipName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(RelationshipName);
|
||||
|
||||
|
@ -9,8 +9,8 @@ use serde::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
data_connector::DataConnectorName, impl_JsonSchema_with_OpenDd_for, impl_OpenDd_default_for,
|
||||
models::EnableAllOrSpecific,
|
||||
data_connector::DataConnectorName, identifier::Identifier, impl_JsonSchema_with_OpenDd_for,
|
||||
impl_OpenDd_default_for, models::EnableAllOrSpecific,
|
||||
};
|
||||
|
||||
#[derive(
|
||||
@ -46,25 +46,25 @@ pub enum TypeName {
|
||||
Ord,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct CustomTypeName(pub String);
|
||||
pub struct CustomTypeName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(CustomTypeName);
|
||||
|
||||
impl CustomTypeName {
|
||||
fn new(s: String) -> Result<CustomTypeName, String> {
|
||||
// First character should be alphabetic or underscore
|
||||
let first_char_valid =
|
||||
matches!(s.chars().next(), Some(c) if c.is_ascii_alphabetic() || c == '_');
|
||||
// All characters should be alphanumeric or underscore
|
||||
let all_chars_valid = s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_');
|
||||
let identifier = Identifier::new(s)?;
|
||||
// Should not be an inbuilt type
|
||||
let not_an_inbuilt_type =
|
||||
InbuiltType::deserialize(StrDeserializer::<serde::de::value::Error>::new(s.as_str()))
|
||||
.is_err();
|
||||
if first_char_valid && all_chars_valid && not_an_inbuilt_type {
|
||||
Ok(CustomTypeName(s))
|
||||
if InbuiltType::deserialize(StrDeserializer::<serde::de::value::Error>::new(
|
||||
identifier.0.as_str(),
|
||||
))
|
||||
.is_ok()
|
||||
{
|
||||
Err(format!(
|
||||
"custom types cannot have the same name as an inbuilt type: {}",
|
||||
identifier.0
|
||||
))
|
||||
} else {
|
||||
Err(format!("invalid custom type name: {s}"))
|
||||
Ok(CustomTypeName(identifier))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -392,7 +392,7 @@ pub struct ColumnFieldMapping {
|
||||
Ord,
|
||||
opendds_derive::OpenDd,
|
||||
)]
|
||||
pub struct FieldName(pub String);
|
||||
pub struct FieldName(pub Identifier);
|
||||
|
||||
impl_JsonSchema_with_OpenDd_for!(FieldName);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user