Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental: add graphql schema parser #146

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ benchmarks/results

# Remove doc build folders
.cache/
.cargo_check
build/
rust-coverage/
target/
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ hf-hub = "=0.3.2"
tokenizers = { version = "=0.20.3", features = ["http"] }
rustc-hash = "2.1.0"
regex-automata = "0.4.9"
apollo-compiler = "1.25.0"

[features]
python-bindings = ["pyo3"]
Expand Down
15 changes: 15 additions & 0 deletions src/graphql/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
use apollo_compiler::validation::DiagnosticList;
use apollo_compiler::Name;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum GraphQLParserError {
#[error("GraphQL apollo compiler error: {0}")]
ApolloCompiler(String),
#[error("Can't find any Query type in your schema, it must exists and is the entrypoint")]
UnknownQuery,
#[error("Can't find any type {0} in your schema")]
UnknownType(Name),
#[error("Input value definition is not supported")]
InputValueDefinitionNotSupported,
}
154 changes: 154 additions & 0 deletions src/graphql/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
mod error;
mod parsing;
mod types;

use error::GraphQLParserError;
pub use types::*;

type Result<T> = std::result::Result<T, GraphQLParserError>;

pub fn build_regex_from_schema(
graphql_schema: &str,
whitespace_pattern: Option<&str>,
) -> Result<String> {
let mut parser = parsing::Parser::new(graphql_schema)?;
if let Some(pattern) = whitespace_pattern {
parser = parser.with_whitespace_pattern(pattern)
}
parser.to_regex()
}

#[cfg(test)]
mod tests {
use regex::Regex;

use super::*;

fn should_match(re: &Regex, value: &str) {
// Asserts that value is fully matched.
match re.find(value) {
Some(matched) => {
assert_eq!(
matched.as_str(),
value,
"Value should match, but does not for: {value}, re:\n{re}"
);
assert_eq!(matched.range(), 0..value.len());
}
None => unreachable!(
"Value should match, but does not, in unreachable for: {value}, re:\n{re}"
),
}
}

fn should_not_match(re: &Regex, value: &str) {
// Asserts that regex does not find a match or not a full match.
if let Some(matched) = re.find(value) {
assert_ne!(
matched.as_str(),
value,
"Value should NOT match, but does for: {value}, re:\n{re}"
);
assert_ne!(matched.range(), 0..value.len());
}
}

#[test]
fn test_schema_matches_regex() {
for (schema, regex, a_match, not_a_match) in [
// ==========================================================
// Integer Type
// ==========================================================
// Required integer property
(
r#"type Query {
count: Int!
}"#,
r#"\{[ ]?"count"[ ]?:[ ]?(-)?(0|[1-9][0-9]*)[ ]?\}"#,
vec![r#"{ "count": 100 }"#],
vec![r#"{ "count": "a" }"#, ""],
),
(
r#"type Query {
count: Int
}"#,
r#"\{([ ]?"count"[ ]?:[ ]?((-)?(0|[1-9][0-9]*))?)?[ ]?\}"#,
vec![r#"{ "count": 100 }"#],
vec![r#"{ "count": "a" }"#, ""],
),
// ==========================================================
// Number Type
// ==========================================================
// Required number property
(
r#"type Query {
count: Float!
}"#,
r#"\{[ ]?"count"[ ]?:[ ]?((-)?(0|[1-9][0-9]*))(\.[0-9]+)?([eE][+-][0-9]+)?[ ]?\}"#,
vec![r#"{ "count": 100 }"#, r#"{ "count": 100.5 }"#],
vec![""],
),
(
r#"type Query {
count: Float
}"#,
r#"\{([ ]?"count"[ ]?:[ ]?(((-)?(0|[1-9][0-9]*))(\.[0-9]+)?([eE][+-][0-9]+)?)?)?[ ]?\}"#,
vec![r#"{ "count": 100 }"#, r#"{ "count": 100.5 }"#],
vec![""],
),
// ==========================================================
// Enum
// ==========================================================
// Required number property
(
r#"
enum Episode {
NEWHOPE
EMPIRE
JEDI
}
type Query {
episode: Episode!
}"#,
r#"\{[ ]?"episode"[ ]?:[ ]?("NEWHOPE"|"EMPIRE"|"JEDI")[ ]?\}"#,
vec![r#"{ "episode": "NEWHOPE" }"#, r#"{ "episode": "JEDI" }"#],
vec![""],
),
(
r#"type Query {
count: Float
}"#,
r#"\{([ ]?"count"[ ]?:[ ]?(((-)?(0|[1-9][0-9]*))(\.[0-9]+)?([eE][+-][0-9]+)?)?)?[ ]?\}"#,
vec![r#"{ "count": 100 }"#, r#"{ "count": 100.5 }"#],
vec![""],
),
// ==========================================================
// Array Type
// ==========================================================
// Required number property
(
r#"type Query {
count: [Float]!
}"#,
r#"\{[ ]?"count"[ ]?:[ ]?\[[ ]?(((((-)?(0|[1-9][0-9]*))(\.[0-9]+)?([eE][+-][0-9]+)?)?)(,[ ]?((((-)?(0|[1-9][0-9]*))(\.[0-9]+)?([eE][+-][0-9]+)?)?)){0,})[ ]?\][ ]?\}"#,
vec![
r#"{ "count": [100.5] }"#,
r#"{ "count": [100] }"#,
r#"{ "count": [100, 101] }"#,
],
vec![""],
),
] {
let result = build_regex_from_schema(schema, None).expect("To regex failed");
assert_eq!(result, regex, "JSON Schema {} didn't match", schema);

let re = Regex::new(&result).expect("Regex failed");
for m in a_match {
should_match(&re, m);
}
for not_m in not_a_match {
should_not_match(&re, not_m);
}
}
}
}
Loading