Add the SubgraphIdentifier type (#613)

## Description

Parallel to #611, in service of removing supergraph config, we'd like to
map all current supergraph config into a generated subgraph. To ensure
that we don't end up with an unfortunate naming collision, I've decided
to refine the definition of a subgraph identifier to exclude any string
starting with `__internal`, which we can then have as our "internal
namespace". This change introduces the new type, and updates the
"unknown_namespace" subgraph to come under this umbrella.

This change should be a no-op to any user who hasn't used a subgraph
name prefixed with `__internal`.

<!--
  Questions to consider answering:
  1. What user-facing changes are being made?
2. What are issues related to this PR? (Consider adding `(close
#<issue-no>)` to the PR title)
  3. What is the conceptual design behind this PR?
  4. How can this PR be tested/verified?
  5. Does the PR have limitations?
  6. Does the PR introduce breaking changes?
-->

## Changelog

User-defined subgraphs may no longer be given names that start with
`__internal`, as this will be reserved for internally generated
subgraphs.

- Add a changelog entry (in the "Changelog entry" section below) if the
changes
  in this PR have any user-facing impact. See
[changelog
guide](https://github.com/hasura/graphql-engine-mono/wiki/Changelog-Guide).
- If no changelog is required ignore/remove this section and add a
  `no-changelog-required` label to the PR.

### Product

_(Select all products this will be available in)_

- [x] community-edition
- [x] cloud
<!-- product : end : DO NOT REMOVE -->

### Type

<!-- See changelog structure:
https://github.com/hasura/graphql-engine-mono/wiki/Changelog-Guide#structure-of-our-changelog
-->

_(Select only one. In case of multiple, choose the most appropriate)_

- [ ] highlight
- [ ] enhancement
- [ ] bugfix
- [x] behaviour-change
- [ ] performance-enhancement
- [ ] security-fix
<!-- type : end : DO NOT REMOVE -->

### Changelog entry

<!--
  - Add a user understandable changelog entry
- Include all details needed to understand the change. Try including
links to docs or issues if relevant
  - For Highlights start with a H4 heading (#### <entry title>)
  - Get the changelog entry reviewed by your team
-->

User-defined subgraphs may no longer have names that start with
`__internal`.

<!-- changelog-entry : end : DO NOT REMOVE -->

<!-- changelog : end : DO NOT REMOVE -->

V3_GIT_ORIGIN_REV_ID: a6df1bba7b1f2cda4593654c15d168f25b7134b2
This commit is contained in:
Tom Harding 2024-05-27 12:05:50 +02:00 committed by hasura-bot
parent 0b16216bad
commit e4c5d7a752
9 changed files with 99 additions and 3 deletions

View File

@ -0,0 +1,9 @@
{
"version": "v2",
"subgraphs": [
{
"name": "__default",
"objects": []
}
]
}

View File

@ -0,0 +1 @@
__ is a reserved prefix for subgraph names at path $.subgraphs[0].name

View File

@ -0,0 +1,9 @@
{
"version": "v2",
"subgraphs": [
{
"name": "default",
"objects": []
}
]
}

View File

@ -3645,7 +3645,7 @@
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"pattern": "^[_a-zA-Z][_a-zA-Z0-9]*$" "pattern": "^(?!__)[_a-zA-Z][_a-zA-Z0-9]*$"
}, },
"objects": { "objects": {
"type": "array", "type": "array",

View File

@ -140,7 +140,7 @@ impl MetadataAccessor {
match metadata { match metadata {
Metadata::WithoutNamespaces(metadata) => { Metadata::WithoutNamespaces(metadata) => {
let mut accessor: MetadataAccessor = MetadataAccessor::new_empty(None); let mut accessor: MetadataAccessor = MetadataAccessor::new_empty(None);
load_metadata_objects(metadata, "unknown_namespace", &mut accessor); load_metadata_objects(metadata, "__unknown_namespace", &mut accessor);
accessor accessor
} }
Metadata::Versioned(MetadataWithVersion::V1(metadata)) => { Metadata::Versioned(MetadataWithVersion::V1(metadata)) => {

View File

@ -101,3 +101,80 @@ impl<'de> Deserialize<'de> for Identifier {
OpenDd::deserialize(serde_json::Value::deserialize(deserializer)?).map_err(D::Error::custom) OpenDd::deserialize(serde_json::Value::deserialize(deserializer)?).map_err(D::Error::custom)
} }
} }
// Macro to produce a validated subgraph 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! subgraph_identifier {
($name:literal) => {
open_dds::identifier::SubgraphIdentifier::new($name.to_string()).unwrap()
};
}
/// Type capturing a subgraph identifier used within the metadata. The wrapped String
/// is guaranteed to be a valid identifier, i.e.
/// - does not start with __
/// - 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 SubgraphIdentifier(pub String);
impl SubgraphIdentifier {
pub fn new(string: String) -> Result<SubgraphIdentifier, &'static str> {
let Identifier(string) = Identifier::new(string)?;
if string.starts_with("__") {
return Err("__ is a reserved prefix for subgraph names");
}
Ok(SubgraphIdentifier(string))
}
pub fn as_str(&self) -> &str {
self.0.as_str()
}
}
impl Deref for SubgraphIdentifier {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl OpenDd for SubgraphIdentifier {
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(),
})?;
SubgraphIdentifier::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 {
"SubgraphIdentifier".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
}
}

View File

@ -258,7 +258,7 @@ impl Supergraph {
#[derive(Serialize, Clone, Debug, PartialEq, opendds_derive::OpenDd)] #[derive(Serialize, Clone, Debug, PartialEq, opendds_derive::OpenDd)]
#[opendd(json_schema(rename = "OpenDdSubgraph"))] #[opendd(json_schema(rename = "OpenDdSubgraph"))]
pub struct Subgraph { pub struct Subgraph {
pub name: identifier::Identifier, pub name: identifier::SubgraphIdentifier,
pub objects: Vec<OpenDdSubgraphObject>, pub objects: Vec<OpenDdSubgraphObject>,
} }