Skip to content

Commit

Permalink
feat(core,graph,graphql,store,server,node): GraphQL API Versioning
Browse files Browse the repository at this point in the history
  • Loading branch information
kamilkisiela authored and dotansimha committed May 3, 2022
1 parent c5bd0cf commit 7caf53c
Show file tree
Hide file tree
Showing 28 changed files with 391 additions and 111 deletions.
2 changes: 1 addition & 1 deletion core/tests/interfaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ async fn insert_and_query(
insert_entities(&deployment, entities).await?;

let document = graphql_parser::parse_query(query).unwrap().into_static();
let target = QueryTarget::Deployment(subgraph_id);
let target = QueryTarget::Deployment(subgraph_id, Default::default());
let query = Query::new(document, None);
Ok(execute_subgraph_query(query, target)
.await
Expand Down
3 changes: 3 additions & 0 deletions graph/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub mod link_resolver;
/// Components dealing with collecting metrics
pub mod metrics;

/// Components dealing with versioning
pub mod versions;

/// A component that receives events of type `T`.
pub trait EventConsumer<E> {
/// Get the event sink.
Expand Down
9 changes: 7 additions & 2 deletions graph/src/components/store/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use web3::types::{Address, H256};
use super::*;
use crate::components::server::index_node::VersionInfo;
use crate::components::transaction_receipt;
use crate::components::versions::Version;
use crate::data::subgraph::status;
use crate::data::value::Word;
use crate::data::{query::QueryTarget, subgraph::schema::*};
Expand Down Expand Up @@ -110,7 +111,11 @@ pub trait SubgraphStore: Send + Sync + 'static {

/// Return the GraphQL schema that was derived from the user's schema by
/// adding a root query type etc. to it
fn api_schema(&self, subgraph_id: &DeploymentHash) -> Result<Arc<ApiSchema>, StoreError>;
fn api_schema(
&self,
subgraph_id: &DeploymentHash,
version: &Version,
) -> Result<Arc<ApiSchema>, StoreError>;

/// Return a `SubgraphFork`, derived from the user's `debug-fork` deployment argument,
/// that is used for debugging purposes only.
Expand Down Expand Up @@ -408,7 +413,7 @@ pub trait QueryStore: Send + Sync {
/// return details about it needed for executing queries
async fn deployment_state(&self) -> Result<DeploymentState, QueryExecutionError>;

fn api_schema(&self) -> Result<Arc<ApiSchema>, QueryExecutionError>;
fn api_schema(&self, version: &Version) -> Result<Arc<ApiSchema>, QueryExecutionError>;

fn network_name(&self) -> &str;

Expand Down
5 changes: 5 additions & 0 deletions graph/src/components/versions/features.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#[derive(Clone, PartialEq)]
pub enum FeatureFlag {
// A description of the feature. Give it a little context.
BasicOrdering,
}
5 changes: 5 additions & 0 deletions graph/src/components/versions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod features;
mod registry;

pub use features::FeatureFlag;
pub use registry::{Version, VersionNumber, VERSIONS};
112 changes: 112 additions & 0 deletions graph/src/components/versions/registry.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
use crate::prelude::FeatureFlag;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;

lazy_static! {
pub static ref VERSIONS: HashMap<VersionNumber, Vec<FeatureFlag>> = {
let supported_versions: Vec<(&str, Vec<FeatureFlag>)> = vec![
// baseline version
("1.0.0", vec![]),
// Versions with feature flags
("1.1.0", vec![FeatureFlag::BasicOrdering])
];

let mut map = HashMap::new();

for (version, flags) in supported_versions {
map.insert(VersionNumber::from(version), flags);
}

map
};

static ref LATEST_VERSION: String = {
let keys: Vec<VersionNumber> = VERSIONS.clone().into_keys().collect();

let last_version = keys.last().unwrap();

last_version.0.clone()
};
}

#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
pub struct VersionNumber(String);

impl Default for VersionNumber {
fn default() -> Self {
Self(LATEST_VERSION.to_string())
}
}

impl fmt::Display for VersionNumber {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(&self.0)?;
Ok(())
}
}

impl VersionNumber {
pub fn validate(version: String) -> Result<(), String> {
let chunks: Vec<&str> = version.split(".").collect();

if chunks.len() != 3 {
return Err(format!("Invalid version number: {}", version));
}

let major = chunks[0].parse::<u32>();
let minor = chunks[1].parse::<u32>();
let patch = chunks[2].parse::<u32>();

if major.is_err() || minor.is_err() || patch.is_err() {
return Err(format!("Invalid version number: {}", version));
}

if !VERSIONS.contains_key(&VersionNumber::from(version)) {
return Err("No versions found".to_string());
}

Ok(())
}
}

#[derive(Clone)]
pub struct Version {
pub version: VersionNumber,
features: Vec<FeatureFlag>,
}

impl From<String> for VersionNumber {
fn from(version: String) -> Self {
Self(version)
}
}

impl From<&str> for VersionNumber {
fn from(version: &str) -> Self {
Self(version.to_string())
}
}

impl Version {
pub fn new(current_version: VersionNumber) -> Self {
Self {
version: current_version.clone(),
features: VERSIONS
.get(&current_version)
.expect(format!("Version {:?} is not supported", current_version).as_str()) // At this point, we know that the version exists (thanks to VersionNumber::validate)
.to_vec(),
}
}

pub fn supports(&self, feature: FeatureFlag) -> bool {
self.features.contains(&feature)
}
}

impl Default for Version {
fn default() -> Self {
Self::new(VersionNumber::default())
}
}
21 changes: 11 additions & 10 deletions graph/src/data/query/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::sync::Arc;

use crate::{
data::graphql::shape_hash::shape_hash,
prelude::{q, r, DeploymentHash, SubgraphName, ENV_VARS},
prelude::{q, r, DeploymentHash, SubgraphName, Version, VersionNumber, ENV_VARS},
};

fn deserialize_number<'de, D>(deserializer: D) -> Result<q::Number, D::Error>
Expand Down Expand Up @@ -112,19 +112,20 @@ impl serde::ser::Serialize for QueryVariables {

#[derive(Clone, Debug)]
pub enum QueryTarget {
Name(SubgraphName),
Deployment(DeploymentHash),
Name(SubgraphName, VersionNumber),
Deployment(DeploymentHash, VersionNumber),
}

impl From<DeploymentHash> for QueryTarget {
fn from(id: DeploymentHash) -> Self {
Self::Deployment(id)
impl QueryTarget {
pub fn get_version_number(&self) -> VersionNumber {
match self {
Self::Name(_, version) => version.clone(),
Self::Deployment(_, version) => version.clone(),
}
}
}

impl From<SubgraphName> for QueryTarget {
fn from(name: SubgraphName) -> Self {
QueryTarget::Name(name)
pub fn get_version(&self) -> Version {
Version::new(self.get_version_number())
}
}

Expand Down
1 change: 1 addition & 0 deletions graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub mod prelude {
SubgraphAssignmentProvider, SubgraphInstanceManager, SubgraphRegistrar,
SubgraphVersionSwitchingMode,
};
pub use crate::components::versions::{FeatureFlag, Version, VersionNumber};
pub use crate::components::{transaction_receipt, EventConsumer, EventProducer};
pub use crate::env::ENV_VARS;

Expand Down
5 changes: 4 additions & 1 deletion graphql/examples/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ pub fn main() {
parse_schema(&schema).map(|v| v.into_static()),
"Failed to parse schema",
);
let schema = ensure(api_schema(&schema), "Failed to convert to API schema");
let schema = ensure(
api_schema(&schema, &Default::default()),
"Failed to convert to API schema",
);

println!("{}", schema);
}
9 changes: 5 additions & 4 deletions graphql/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,11 @@ where
// while the query is running. `self.store` can not be used after this
// point, and everything needs to go through the `store` we are
// setting up here
let store = self.store.query_store(target, false).await?;

let store = self.store.query_store(target.clone(), false).await?;
let state = store.deployment_state().await?;
let network = Some(store.network_name().to_string());
let schema = store.api_schema()?;
let schema = store.api_schema(&target.get_version())?;

// Test only, see c435c25decbc4ad7bbbadf8e0ced0ff2
#[cfg(debug_assertions)]
Expand Down Expand Up @@ -269,8 +270,8 @@ where
subscription: Subscription,
target: QueryTarget,
) -> Result<SubscriptionResult, SubscriptionError> {
let store = self.store.query_store(target, true).await?;
let schema = store.api_schema()?;
let store = self.store.query_store(target.clone(), true).await?;
let schema = store.api_schema(&target.get_version())?;
let network = store.network_name().to_string();

let query = crate::execution::Query::new(
Expand Down
Loading

0 comments on commit 7caf53c

Please sign in to comment.