Introduce Spanned type (#1147)

<!-- The PR description should answer 2 important questions: -->

### What

This PR adds the `Spanned` type: an OpenDD wrapper that can be placed
inside the Metadata. It's basically a pair of the value and the path to
the value in the original metadata. This allows us to do things like
source maps.

<!-- What is this PR trying to accomplish (and why, if it's not
obvious)? -->

<!-- Consider: do we need to add a changelog entry? -->

<!-- Does this PR introduce new validation that might break old builds?
-->

<!-- Consider: do we need to put new checks behind a flag? -->

### How

It's almost exactly what @danieljharvey proposed originally.

<!-- How is it trying to accomplish it (what are the implementation
steps)? -->

---------

Co-authored-by: Daniel Harvey <danieljamesharvey@gmail.com>
V3_GIT_ORIGIN_REV_ID: 4f037686b6981fffc4b0a8ac8f95c2f9c623af67
This commit is contained in:
Tom Harding 2024-09-24 16:38:43 +02:00 committed by hasura-bot
parent 0e4a3afbca
commit 4630ade31e
5 changed files with 99 additions and 10 deletions

View File

@ -407,7 +407,7 @@ fn make_order_by_expression(
) -> Result<Qualified<OrderByExpressionIdentifier>, ModelsError> {
let identifier = Qualified::new(
subgraph.clone(),
OrderByExpressionIdentifier::FromModel(model_v1.name.clone()),
OrderByExpressionIdentifier::FromModel(model_v1.name.value.clone()),
);
let ordered_type = Qualified::new(subgraph.clone(), model_v1.object_type.clone());
let open_dds_orderable_fields = model_v1

View File

@ -2657,12 +2657,11 @@
],
"properties": {
"name": {
"$id": "https://hasura.io/jsonschemas/metadata/ModelName",
"title": "ModelName",
"description": "The name of the data model.",
"allOf": [
{
"$ref": "#/definitions/ModelName"
}
]
"type": "string",
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$"
},
"objectType": {
"description": "The type of the objects of which this model is a collection.",

View File

@ -19,6 +19,7 @@ pub mod plugins;
pub mod query;
pub mod relationships;
pub mod session_variables;
pub mod spanned;
pub mod test_utils;
pub mod traits;
pub mod types;

View File

@ -8,6 +8,7 @@ use crate::{
data_connector::{CollectionName, DataConnectorName},
identifier::Identifier,
order_by_expression::OrderByExpressionName,
spanned::Spanned,
str_newtype,
traits::{OpenDd, OpenDdDeserializeError},
types::{CustomTypeName, Deprecated, FieldName, GraphQlFieldName, GraphQlTypeName},
@ -100,8 +101,8 @@ impl Model {
pub fn name(&self) -> &ModelName {
match self {
Model::V1(v1) => &v1.name,
Model::V2(v2) => &v2.name,
Model::V1(v1) => &v1.name.value,
Model::V2(v2) => &v2.name.value,
}
}
@ -169,7 +170,7 @@ impl Model {
/// A data model is a collection of objects of a particular type. Models can support one or more CRUD operations.
pub struct ModelV1 {
/// The name of the data model.
pub name: ModelName,
pub name: Spanned<ModelName>,
/// The type of the objects of which this model is a collection.
pub object_type: CustomTypeName,
/// Whether this model should be used as the global ID source for all objects of its type.
@ -201,7 +202,7 @@ pub struct ModelV1 {
/// ModelV2 implements the changes described in rfcs/open-dd-expression-type-changes.md.
pub struct ModelV2 {
/// The name of the data model.
pub name: ModelName,
pub name: Spanned<ModelName>,
/// The type of the objects of which this model is a collection.
pub object_type: CustomTypeName,
/// Whether this model should be used as the global ID source for all objects of its type.

View File

@ -0,0 +1,88 @@
use crate::traits::{OpenDd, OpenDdDeserializeError};
use core::ops::Deref;
use serde::{Serialize, Serializer};
/// Wrapper that combines an item with its parsed JSONPath
/// for use in error reporting
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Spanned<T> {
pub path: jsonpath::JSONPath,
pub value: T,
}
impl<T> Deref for Spanned<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T: Serialize> Serialize for Spanned<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.value.serialize(serializer)
}
}
impl<T: OpenDd> OpenDd for Spanned<T> {
fn deserialize(
json: serde_json::Value,
path: jsonpath::JSONPath,
) -> Result<Self, OpenDdDeserializeError> {
Ok(Spanned {
value: T::deserialize(json, path.clone())?,
path,
})
}
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
T::json_schema(gen)
}
fn _schema_name() -> String {
"Spanned".to_string()
}
fn _schema_is_referenceable() -> bool {
false
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
#[test]
fn test_spanned_path() {
let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("examples/reference.json");
let metadata =
open_dds::Metadata::from_json_str(&std::fs::read_to_string(path).unwrap()).unwrap();
// Very deliberate tree-walking in the absence of optics.
let open_dds::Metadata::Versioned(versioned) = metadata else {
todo!("Test not implemented for unversioned metadata")
};
let open_dds::MetadataWithVersion::V2(metadata_v2) = versioned else {
todo!("Test not implemented for non-V2 metadata")
};
for (subgraph_index, subgraph) in metadata_v2.subgraphs.iter().enumerate() {
for (object_index, object) in subgraph.objects.iter().enumerate() {
if let open_dds::OpenDdSubgraphObject::Model(model) = object {
let path = match model {
open_dds::models::Model::V1(model) => model.name.path.to_string(),
open_dds::models::Model::V2(model) => model.name.path.to_string(),
};
let expected = format!(
"$.subgraphs[{subgraph_index}].objects[{object_index}].definition.name"
);
assert_eq!(path, expected);
}
}
}
}
}