graphql-parser-0.4.1/.cargo_vcs_info.json0000644000000001360000000000100137660ustar { "git": { "sha1": "fbdfb6cf637e4417b00a7720ceeaa1d38e07597d" }, "path_in_vcs": "" }graphql-parser-0.4.1/.editorconfig000064400000000000000000000001151046102023000152300ustar 00000000000000[*.rs] end_of_line = lf charset = utf-8 indent_style = space indent_size = 4 graphql-parser-0.4.1/.github/workflows/ci.yml000064400000000000000000000005171046102023000172740ustar 00000000000000name: ci on: push: branches: - master pull_request: branches: - master jobs: check: runs-on: ubuntu-latest steps: - name: Check out repository code uses: actions/checkout@v2 - name: Cache Rust uses: Swatinem/rust-cache@v1 - name: Test Rust run: cargo test graphql-parser-0.4.1/.gitignore000064400000000000000000000000341046102023000145430ustar 00000000000000/Cargo.lock /.vagga /target graphql-parser-0.4.1/.travis.yml000064400000000000000000000022231046102023000146660ustar 00000000000000sudo: false dist: trusty language: rust cache: - cargo before_cache: - rm -r $TRAVIS_BUILD_DIR/target/debug jobs: include: - os: linux rust: stable - os: linux rust: beta - os: linux rust: nightly # deploy - stage: publish os: linux rust: stable env: # CARGO_TOKEN - secure: "qHOydmz3Un/32JFlWEGYc4VzqKNBrOODOrVQxKO6u6NqTDo+4vGK7xLtct5rx58vF77BvSdfiR6RRcqIuYlWKNA/WbPCTolmy2cAkSztzjhpvmyF18LoM32gdaaRJ5+6Zay64/vG5LtE5QhIhFzmbJ6opBVv9PaQR1ptI/0Ss+gyvI7jw5WZAoUV4YD5ODTacdCAcoVohHHQcSxpMMVWQp7hFf8ZWwVVYQOZF44AZ3N9BHRc9MKr9XQ70E+TvHJjGRdwJIX6mNpWlI43JwkN2DeVA6cA/eWh91zLWkmkzNuYW63He02ueS9iTXvkoZnQysSMgB7TNNkP4TtlY2udvL7tO2UXt+6Wbh1kMESrtsvUSK9363Q/lrVSUKV3DZUFQC8CMh3Enn+W/2hyE/YDghcip+P7FHbrTf4TuWUzSWXA20xBMn/kkTWzX4gchTcrRKqW7RZHNZpOOplGvnW5JfGnRKC9CDx+kT+IGrKvnVsnRNqGI8bAm5SKgu6Nqr9TLxIQ6IKv/FAuQSjKh7l6STwNVPv5q3bji8zmEeSonKhSCbtSfHRepnY16mirM3Fw5f1beoDZR0qwsaIHnJz0RS0Mw7iuB0mt0bnpqgrl/agLmrjPrRQtPo01eqnli8ZU5cSpAND7B+ZWfqRFGdekyXsKJNAWycqAjOjg0qCjJEA=" install: true script: true deploy: - provider: script script: 'cargo publish --verbose --token=$CARGO_TOKEN' on: tags: true graphql-parser-0.4.1/Cargo.lock0000644000000070040000000000100117420ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ "winapi", ] [[package]] name = "bytes" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "combine" version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "graphql-parser" version = "0.4.1" dependencies = [ "combine", "pretty_assertions", "thiserror", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "pretty_assertions" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a029430f0d744bc3d15dd474d591bed2402b645d024583082b9f63bb936dac6" dependencies = [ "ansi_term", "difference", ] [[package]] name = "proc-macro2" version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "syn" version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" graphql-parser-0.4.1/Cargo.toml0000644000000030410000000000100117620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "graphql-parser" version = "0.4.1" authors = ["Paul Colomiets "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = """ A parser, AST and serializer for graphql query language and scheme definition language (sometimes called IDL). """ homepage = "https://github.com/graphql-rust/graphql-parser" documentation = "https://docs.rs/graphql-parser" readme = "README.md" keywords = [ "graphql", "parser", ] categories = [ "parser-implementations", "command-line-interface", ] license = "MIT/Apache-2.0" [lib] name = "graphql_parser" path = "src/lib.rs" [[test]] name = "query_errors" path = "tests/query_errors.rs" [[test]] name = "query_roundtrips" path = "tests/query_roundtrips.rs" [[test]] name = "schema_roundtrips" path = "tests/schema_roundtrips.rs" [[bench]] name = "graphql" path = "benches/graphql.rs" [dependencies.combine] version = "4.6.6" [dependencies.thiserror] version = "1.0.11" [dev-dependencies.pretty_assertions] version = "0.5.0" graphql-parser-0.4.1/Cargo.toml.orig000064400000000000000000000011541046102023000154460ustar 00000000000000[package] name = "graphql-parser" description = """ A parser, AST and serializer for graphql query language and scheme definition language (sometimes called IDL). """ license = "MIT/Apache-2.0" readme = "README.md" keywords = ["graphql", "parser"] categories = ["parser-implementations", "command-line-interface"] homepage = "https://github.com/graphql-rust/graphql-parser" documentation = "https://docs.rs/graphql-parser" version = "0.4.1" authors = ["Paul Colomiets "] edition = "2018" [dependencies] combine = "4.6.6" thiserror = "1.0.11" [dev-dependencies] pretty_assertions = "0.5.0" graphql-parser-0.4.1/LICENSE-APACHE000064400000000000000000000261361046102023000145120ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. graphql-parser-0.4.1/LICENSE-MIT000064400000000000000000000020611046102023000142110ustar 00000000000000Copyright (c) 2017 The graphql-parser Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. graphql-parser-0.4.1/README.md000064400000000000000000000015021046102023000140330ustar 00000000000000GraphQL Parser ============== [Documentation](https://docs.rs/graphql-parser) | [Github](https://github.com/tailhook/graphql-parser) | [Crate](https://crates.io/crates/graphql-parser) A parser, formatter and AST for graphql query and schema definition language for rust. Supported extensions: 1. Subscriptions 2. Block (triple quoted) strings License ======= Licensed under either of * Apache License, Version 2.0, (./LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0) * MIT license (./LICENSE-MIT or http://opensource.org/licenses/MIT) at your option. Contribution ------------ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. graphql-parser-0.4.1/benches/graphql.rs000064400000000000000000000021361046102023000161730ustar 00000000000000#![feature(test)] extern crate test; extern crate graphql_parser; use std::fs::File; use std::io::Read; use graphql_parser::parse_query; fn load_file(name: &str) -> String { let mut buf = String::with_capacity(1024); let path = format!("tests/queries/{}.graphql", name); let mut f = File::open(path).unwrap(); f.read_to_string(&mut buf).unwrap(); buf } #[bench] fn bench_minimal(b: &mut test::Bencher) { let f = load_file("minimal"); b.iter(|| parse_query::(&f).unwrap()); } #[bench] fn bench_inline_fragment(b: &mut test::Bencher) { let f = load_file("inline_fragment"); b.iter(|| parse_query::(&f).unwrap()); } #[bench] fn bench_directive_args(b: &mut test::Bencher) { let f = load_file("directive_args"); b.iter(|| parse_query::(&f).unwrap()); } #[bench] fn bench_query_vars(b: &mut test::Bencher) { let f = load_file("query_vars"); b.iter(|| parse_query::(&f).unwrap()); } #[bench] fn bench_kitchen_sink(b: &mut test::Bencher) { let f = load_file("kitchen-sink"); b.iter(|| parse_query::(&f).unwrap()); } graphql-parser-0.4.1/bulk.yaml000064400000000000000000000002051046102023000143740ustar 00000000000000minimum-bulk: v0.4.5 versions: - file: Cargo.toml block-start: ^\[package\] block-end: ^\[.*\] regex: ^version\s*=\s*"(\S+)" graphql-parser-0.4.1/src/common.rs000064400000000000000000000337451046102023000152170ustar 00000000000000use std::{collections::BTreeMap, fmt}; use combine::easy::{Error, Info}; use combine::{choice, many, many1, optional, position, StdParseResult}; use combine::{parser, Parser}; use crate::helpers::{ident, kind, name, punct}; use crate::position::Pos; use crate::tokenizer::{Kind as T, Token, TokenStream}; /// Text abstracts over types that hold a string value. /// It is used to make the AST generic over the string type. pub trait Text<'a>: 'a { type Value: 'a + From<&'a str> + AsRef + std::borrow::Borrow + PartialEq + Eq + PartialOrd + Ord + fmt::Debug + Clone; } impl<'a> Text<'a> for &'a str { type Value = Self; } impl<'a> Text<'a> for String { type Value = String; } impl<'a> Text<'a> for std::borrow::Cow<'a, str> { type Value = Self; } #[derive(Debug, Clone, PartialEq)] pub struct Directive<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub arguments: Vec<(T::Value, Value<'a, T>)>, } /// This represents integer number /// /// But since there is no definition on limit of number in spec /// (only in implemetation), we do a trick similar to the one /// in `serde_json`: encapsulate value in new-type, allowing type /// to be extended later. #[derive(Debug, Clone, PartialEq)] // we use i64 as a reference implementation: graphql-js thinks even 32bit // integers is enough. We might consider lift this limit later though pub struct Number(pub(crate) i64); #[derive(Debug, Clone, PartialEq)] pub enum Value<'a, T: Text<'a>> { Variable(T::Value), Int(Number), Float(f64), String(String), Boolean(bool), Null, Enum(T::Value), List(Vec>), Object(BTreeMap>), } impl<'a, T: Text<'a>> Value<'a, T> { pub fn into_static(&self) -> Value<'static, String> { match self { Self::Variable(v) => Value::Variable(v.as_ref().into()), Self::Int(i) => Value::Int(i.clone()), Self::Float(f) => Value::Float(*f), Self::String(s) => Value::String(s.clone()), Self::Boolean(b) => Value::Boolean(*b), Self::Null => Value::Null, Self::Enum(v) => Value::Enum(v.as_ref().into()), Self::List(l) => Value::List(l.iter().map(|e| e.into_static()).collect()), Self::Object(o) => Value::Object( o.iter() .map(|(k, v)| (k.as_ref().into(), v.into_static())) .collect(), ), } } } #[derive(Debug, Clone, PartialEq)] pub enum Type<'a, T: Text<'a>> { NamedType(T::Value), ListType(Box>), NonNullType(Box>), } impl Number { /// Returns a number as i64 if it fits the type pub fn as_i64(&self) -> Option { Some(self.0) } } impl From for Number { fn from(i: i32) -> Self { Number(i as i64) } } pub fn directives<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult>, TokenStream<'a>> where T: Text<'a>, { many( position() .skip(punct("@")) .and(name::<'a, T>()) .and(parser(arguments)) .map(|((position, name), arguments)| Directive { position, name, arguments, }), ) .parse_stream(input) .into_result() } #[allow(clippy::type_complexity)] pub fn arguments<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult)>, TokenStream<'a>> where T: Text<'a>, { optional( punct("(") .with(many1(name::<'a, T>().skip(punct(":")).and(parser(value)))) .skip(punct(")")), ) .map(|opt| opt.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn int_value<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { kind(T::IntValue) .and_then(|tok| tok.value.parse()) .map(Number) .map(Value::Int) .parse_stream(input) .into_result() } pub fn float_value<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { kind(T::FloatValue) .and_then(|tok| tok.value.parse()) .map(Value::Float) .parse_stream(input) .into_result() } fn unquote_block_string(src: &str) -> Result, Token<'_>>> { debug_assert!(src.starts_with("\"\"\"") && src.ends_with("\"\"\"")); let lines = src[3..src.len() - 3].lines(); let mut common_indent = usize::MAX; let mut first_non_empty_line: Option = None; let mut last_non_empty_line = 0; for (idx, line) in lines.clone().enumerate() { let indent = line.len() - line.trim_start().len(); if indent == line.len() { continue; } first_non_empty_line.get_or_insert(idx); last_non_empty_line = idx; if idx != 0 { common_indent = std::cmp::min(common_indent, indent); } } if first_non_empty_line.is_none() { // The block string contains only whitespace. return Ok("".to_string()); } let first_non_empty_line = first_non_empty_line.unwrap(); let mut result = String::with_capacity(src.len() - 6); let mut lines = lines .enumerate() // Skip leading and trailing empty lines. .skip(first_non_empty_line) .take(last_non_empty_line - first_non_empty_line + 1) // Remove indent, except the first line. .map(|(idx, line)| { if idx != 0 && line.len() >= common_indent { &line[common_indent..] } else { line } }) // Handle escaped triple-quote (\"""). .map(|x| x.replace(r#"\""""#, r#"""""#)); if let Some(line) = lines.next() { result.push_str(&line); for line in lines { result.push_str("\n"); result.push_str(&line); } } return Ok(result); } fn unquote_string(s: &str) -> Result> { let mut res = String::with_capacity(s.len()); debug_assert!(s.starts_with('"') && s.ends_with('"')); let mut chars = s[1..s.len() - 1].chars(); let mut temp_code_point = String::with_capacity(4); while let Some(c) = chars.next() { match c { '\\' => { match chars.next().expect("slash cant be at the end") { c @ '"' | c @ '\\' | c @ '/' => res.push(c), 'b' => res.push('\u{0010}'), 'f' => res.push('\u{000C}'), 'n' => res.push('\n'), 'r' => res.push('\r'), 't' => res.push('\t'), 'u' => { temp_code_point.clear(); for _ in 0..4 { match chars.next() { Some(inner_c) => temp_code_point.push(inner_c), None => { return Err(Error::Unexpected(Info::Owned( format_args!( "\\u must have 4 characters after it, only found '{}'", temp_code_point ) .to_string(), ))) } } } // convert our hex string into a u32, then convert that into a char match u32::from_str_radix(&temp_code_point, 16).map(std::char::from_u32) { Ok(Some(unicode_char)) => res.push(unicode_char), _ => { return Err(Error::Unexpected(Info::Owned( format_args!( "{} is not a valid unicode code point", temp_code_point ) .to_string(), ))) } } } c => { return Err(Error::Unexpected(Info::Owned( format_args!("bad escaped char {:?}", c).to_string(), ))); } } } c => res.push(c), } } Ok(res) } pub fn string<'a>(input: &mut TokenStream<'a>) -> StdParseResult> { choice(( kind(T::StringValue).and_then(|tok| unquote_string(tok.value)), kind(T::BlockString).and_then(|tok| unquote_block_string(tok.value)), )) .parse_stream(input) .into_result() } pub fn string_value<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { kind(T::StringValue) .and_then(|tok| unquote_string(tok.value)) .map(Value::String) .parse_stream(input) .into_result() } pub fn block_string_value<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { kind(T::BlockString) .and_then(|tok| unquote_block_string(tok.value)) .map(Value::String) .parse_stream(input) .into_result() } pub fn plain_value<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ident("true") .map(|_| Value::Boolean(true)) .or(ident("false").map(|_| Value::Boolean(false))) .or(ident("null").map(|_| Value::Null)) .or(name::<'a, T>().map(Value::Enum)) .or(parser(int_value)) .or(parser(float_value)) .or(parser(string_value)) .or(parser(block_string_value)) .parse_stream(input) .into_result() } pub fn value<'a, T>(input: &mut TokenStream<'a>) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { parser(plain_value) .or(punct("$").with(name::<'a, T>()).map(Value::Variable)) .or(punct("[") .with(many(parser(value))) .skip(punct("]")) .map(Value::List)) .or(punct("{") .with(many(name::<'a, T>().skip(punct(":")).and(parser(value)))) .skip(punct("}")) .map(Value::Object)) .parse_stream(input) .into_result() } pub fn default_value<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { parser(plain_value) .or(punct("[") .with(many(parser(default_value))) .skip(punct("]")) .map(Value::List)) .or(punct("{") .with(many( name::<'a, T>().skip(punct(":")).and(parser(default_value)), )) .skip(punct("}")) .map(Value::Object)) .parse_stream(input) .into_result() } pub fn parse_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { name::<'a, T>() .map(Type::NamedType) .or(punct("[") .with(parser(parse_type)) .skip(punct("]")) .map(Box::new) .map(Type::ListType)) .and(optional(punct("!")).map(|v| v.is_some())) .map(|(typ, strict)| { if strict { Type::NonNullType(Box::new(typ)) } else { typ } }) .parse_stream(input) .into_result() } #[cfg(test)] mod tests { use super::unquote_block_string; use super::unquote_string; use super::Number; #[test] fn number_from_i32_and_to_i64_conversion() { assert_eq!(Number::from(1).as_i64(), Some(1)); assert_eq!(Number::from(584).as_i64(), Some(584)); assert_eq!( Number::from(i32::min_value()).as_i64(), Some(i32::min_value() as i64) ); assert_eq!( Number::from(i32::max_value()).as_i64(), Some(i32::max_value() as i64) ); } #[test] fn unquote_unicode_string() { // basic tests assert_eq!(unquote_string(r#""\u0009""#).expect(""), "\u{0009}"); assert_eq!(unquote_string(r#""\u000A""#).expect(""), "\u{000A}"); assert_eq!(unquote_string(r#""\u000D""#).expect(""), "\u{000D}"); assert_eq!(unquote_string(r#""\u0020""#).expect(""), "\u{0020}"); assert_eq!(unquote_string(r#""\uFFFF""#).expect(""), "\u{FFFF}"); // a more complex string assert_eq!( unquote_string(r#""\u0009 hello \u000A there""#).expect(""), "\u{0009} hello \u{000A} there" ); } #[test] fn block_string_leading_and_trailing_empty_lines() { let block = &triple_quote(" \n\n Hello,\n World!\n\n Yours,\n GraphQL.\n\n\n"); assert_eq!( unquote_block_string(&block), Result::Ok("Hello,\n World!\n\nYours,\n GraphQL.".to_string()) ); } #[test] fn block_string_indent() { let block = &triple_quote("Hello \n\n Hello,\n World!\n"); assert_eq!( unquote_block_string(&block), Result::Ok("Hello \n\nHello,\n World!".to_string()) ); } #[test] fn block_string_escaping() { let block = triple_quote(r#"\""""#); assert_eq!( unquote_block_string(&block), Result::Ok("\"\"\"".to_string()) ); } #[test] fn block_string_empty() { let block = triple_quote(""); assert_eq!(unquote_block_string(&block), Result::Ok("".to_string())); let block = triple_quote(" \n\t\n"); assert_eq!(unquote_block_string(&block), Result::Ok("".to_string())); } fn triple_quote(input: &str) -> String { return format!("\"\"\"{}\"\"\"", input); } } graphql-parser-0.4.1/src/format.rs000064400000000000000000000121671046102023000152120ustar 00000000000000//! Formatting graphql use std::default::Default; use crate::common::Directive; #[derive(Debug, PartialEq)] pub(crate) struct Formatter<'a> { buf: String, style: &'a Style, indent: u32, } /// A configuration of formatting style /// /// Currently we only have indentation configured, other things might be /// added later (such as minification). #[derive(Debug, PartialEq, Clone)] pub struct Style { indent: u32, multiline_arguments: bool, } impl Default for Style { fn default() -> Style { Style { indent: 2, multiline_arguments: false, } } } impl Style { /// Change the number of spaces used for indentation pub fn indent(&mut self, indent: u32) -> &mut Self { self.indent = indent; self } /// Set whether to add new lines between arguments pub fn multiline_arguments(&mut self, multiline_arguments: bool) -> &mut Self { self.multiline_arguments = multiline_arguments; self } } pub(crate) trait Displayable { fn display(&self, f: &mut Formatter); } impl<'a> Formatter<'a> { pub fn new(style: &Style) -> Formatter { Formatter { buf: String::with_capacity(1024), style, indent: 0, } } pub fn indent(&mut self) { for _ in 0..self.indent { self.buf.push(' '); } } pub fn endline(&mut self) { self.buf.push('\n'); } pub fn start_argument_block(&mut self, open_char: char) { self.buf.push(open_char); if self.style.multiline_arguments { self.inc_indent(); } } pub fn end_argument_block(&mut self, close_char: char) { if self.style.multiline_arguments { self.endline(); self.dec_indent(); self.indent(); } self.buf.push(close_char); } pub fn start_argument(&mut self) { if self.style.multiline_arguments { self.endline(); self.indent(); } } pub fn deliniate_argument(&mut self) { self.buf.push(','); if !self.style.multiline_arguments { self.buf.push(' '); } } pub fn start_block(&mut self) { self.buf.push('{'); self.endline(); self.inc_indent(); } pub fn end_block(&mut self) { self.dec_indent(); self.indent(); self.buf.push('}'); self.endline(); } pub fn margin(&mut self) { if !self.buf.is_empty() { self.buf.push('\n'); } } pub fn write(&mut self, s: &str) { self.buf.push_str(s); } pub fn into_string(self) -> String { self.buf } pub fn write_quoted(&mut self, s: &str) { let mut has_newline = false; let mut has_nonprintable = false; for c in s.chars() { match c { '\n' => has_newline = true, '\r' | '\t' | '\u{0020}'..='\u{FFFF}' => {} _ => has_nonprintable = true, } } if !has_newline || has_nonprintable { use std::fmt::Write; self.buf.push('"'); for c in s.chars() { match c { '\r' => self.write(r"\r"), '\n' => self.write(r"\n"), '\t' => self.write(r"\t"), '"' => self.write("\\\""), '\\' => self.write(r"\\"), '\u{0020}'..='\u{FFFF}' => self.buf.push(c), _ => write!(&mut self.buf, "\\u{:04}", c as u32).unwrap(), } } self.buf.push('"'); } else { self.buf.push_str(r#"""""#); self.endline(); self.indent += self.style.indent; for line in s.lines() { if !line.trim().is_empty() { self.indent(); self.write(&line.replace(r#"""""#, r#"\""""#)); } self.endline(); } self.indent -= self.style.indent; self.indent(); self.buf.push_str(r#"""""#); } } fn inc_indent(&mut self) { self.indent += self.style.indent; } fn dec_indent(&mut self) { self.indent = self.indent.checked_sub(self.style.indent) .expect("negative indent"); } } pub(crate) fn format_directives<'a, T>(dirs: &[Directive<'a, T>], f: &mut Formatter) where T: crate::common::Text<'a>, { for dir in dirs { f.write(" "); dir.display(f); } } macro_rules! impl_display { ($( $typ: ident, )+) => { $( impl fmt::Display for $typ { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&to_string(self)) } } )+ }; ('a $($typ: ident, )+) => { $( impl<'a, T> fmt::Display for $typ<'a, T> where T: Text<'a>, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&to_string(self)) } } )+ }; } graphql-parser-0.4.1/src/helpers.rs000064400000000000000000000055521046102023000153640ustar 00000000000000use std::marker::PhantomData; use combine::error::Tracked; use combine::stream::easy::{Error, Errors, Info}; use combine::{satisfy, ParseResult, Parser, StreamOnce}; use crate::position::Pos; use crate::tokenizer::{Kind, Token, TokenStream}; use super::common::Text; #[derive(Debug, Clone)] pub struct TokenMatch<'a> { kind: Kind, phantom: PhantomData<&'a u8>, } #[derive(Debug, Clone)] pub struct NameMatch<'a, T> where T: Text<'a>, { phantom: PhantomData<&'a T>, } #[derive(Debug, Clone)] pub struct Value<'a> { kind: Kind, value: &'static str, phantom: PhantomData<&'a u8>, } pub fn kind<'x>(kind: Kind) -> TokenMatch<'x> { TokenMatch { kind, phantom: PhantomData, } } pub fn name<'a, T>() -> NameMatch<'a, T> where T: Text<'a>, { NameMatch { phantom: PhantomData, } } impl<'a> Parser> for TokenMatch<'a> { type Output = Token<'a>; type PartialState = (); #[inline] fn parse_lazy( &mut self, input: &mut TokenStream<'a>, ) -> ParseResult as StreamOnce>::Error> { satisfy(|c: Token<'a>| c.kind == self.kind).parse_lazy(input) } fn add_error(&mut self, error: &mut Tracked, Token<'a>, Pos>>) { error .error .add_error(Error::Expected(Info::Owned(format!("{:?}", self.kind)))); } } pub fn punct<'s>(value: &'static str) -> Value<'s> { Value { kind: Kind::Punctuator, value, phantom: PhantomData, } } pub fn ident<'s>(value: &'static str) -> Value<'s> { Value { kind: Kind::Name, value, phantom: PhantomData, } } impl<'a> Parser> for Value<'a> { type Output = Token<'a>; type PartialState = (); #[inline] fn parse_lazy( &mut self, input: &mut TokenStream<'a>, ) -> ParseResult as StreamOnce>::Error> { satisfy(|c: Token<'a>| c.kind == self.kind && c.value == self.value).parse_lazy(input) } fn add_error(&mut self, error: &mut Tracked< as StreamOnce>::Error>) { error .error .add_error(Error::Expected(Info::Static(self.value))); } } impl<'a, S> Parser> for NameMatch<'a, S> where S: Text<'a>, { type Output = S::Value; type PartialState = (); #[inline] fn parse_lazy( &mut self, input: &mut TokenStream<'a>, ) -> ParseResult as StreamOnce>::Error> { satisfy(|c: Token<'a>| c.kind == Kind::Name) .map(|t: Token<'a>| -> S::Value { S::Value::from(t.value) }) .parse_lazy(input) } fn add_error(&mut self, error: &mut Tracked, Token<'a>, Pos>>) { error.error.add_error(Error::Expected(Info::Static("Name"))); } } graphql-parser-0.4.1/src/lib.rs000064400000000000000000000045461046102023000144720ustar 00000000000000//! Graphql Parser //! ============== //! //! This library contains full parser and formatter of the graphql //! query language as well as AST types. //! //! [Docs](https://docs.rs/graphql-parser/) | //! [Github](https://github.com/graphql-rust/graphql-parser/) | //! [Crate](https://crates.io/crates/graphql-parser) //! //! Current this library supports full graphql syntax, and the following //! extensions: //! //! 1. Subscriptions //! 2. Block (triple quoted) strings //! 3. Schema definition language a/k/a IDL (which is still in RFC) //! //! //! Example: Parse and Format Query //! ------------------------------- //! //! ```rust //! # extern crate graphql_parser; //! use graphql_parser::query::{parse_query, ParseError}; //! //! # fn parse() -> Result<(), ParseError> { //! let ast = parse_query::<&str>("query MyQuery { field1, field2 }")?; //! // Format canonical representation //! assert_eq!(format!("{}", ast), "\ //! query MyQuery { //! field1 //! field2 //! } //! "); //! # Ok(()) //! # } //! # fn main() { //! # parse().unwrap() //! # } //! ``` //! //! Example: Parse and Format Schema //! -------------------------------- //! //! ```rust //! # extern crate graphql_parser; //! use graphql_parser::schema::{parse_schema, ParseError}; //! //! # fn parse() -> Result<(), ParseError> { //! let ast = parse_schema::(r#" //! schema { //! query: Query //! } //! type Query { //! users: [User!]!, //! } //! """ //! Example user object //! //! This is just a demo comment. //! """ //! type User { //! name: String!, //! } //! "#)?.to_owned(); //! // Format canonical representation //! assert_eq!(format!("{}", ast), "\ //! schema { //! query: Query //! } //! //! type Query { //! users: [User!]! //! } //! //! \"\"\" //! Example user object //! //! This is just a demo comment. //! \"\"\" //! type User { //! name: String! //! } //! "); //! # Ok(()) //! # } //! # fn main() { //! # parse().unwrap() //! # } //! ``` //! #![warn(missing_debug_implementations)] #[cfg(test)] #[macro_use] extern crate pretty_assertions; mod common; #[macro_use] mod format; mod position; mod tokenizer; mod helpers; pub mod query; pub mod schema; pub use crate::query::parse_query; pub use crate::schema::parse_schema; pub use crate::query::minify_query; pub use crate::position::Pos; pub use crate::format::Style; graphql-parser-0.4.1/src/position.rs000064400000000000000000000010531046102023000155560ustar 00000000000000use std::fmt; /// Original position of element in source code #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Copy, Default, Hash)] pub struct Pos { /// One-based line number pub line: usize, /// One-based column number pub column: usize, } impl fmt::Debug for Pos { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Pos({}:{})", self.line, self.column) } } impl fmt::Display for Pos { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}:{}", self.line, self.column) } } graphql-parser-0.4.1/src/query/ast.rs000064400000000000000000000103071046102023000156500ustar 00000000000000//! Query Language Abstract Syntax Tree (AST) //! //! The types and fields here resemble official [graphql grammar] whenever it //! makes sense for rust. //! //! [graphql grammar]: http://facebook.github.io/graphql/October2016/#sec-Appendix-Grammar-Summary //! use crate::position::Pos; pub use crate::common::{Directive, Number, Value, Text, Type}; /// Root of query data #[derive(Debug, Clone, PartialEq)] pub struct Document<'a, T: Text<'a>> { pub definitions: Vec>, } impl<'a> Document<'a, String> { pub fn into_static(self) -> Document<'static, String> { // To support both reference and owned values in the AST, // all string data is represented with the ::common::Str<'a, T: Text<'a>> // wrapper type. // This type must carry the lifetime of the query string, // and is stored in a PhantomData value on the Str type. // When using owned String types, the actual lifetime of // the Ast nodes is 'static, since no references are kept, // but the nodes will still carry the input lifetime. // To continue working with Document in a owned fasion // the lifetime needs to be transmuted to 'static. // // This is safe because no references are present. // Just the PhantomData lifetime reference is transmuted away. unsafe { std::mem::transmute::<_, Document<'static, String>>(self) } } } #[derive(Debug, Clone, PartialEq)] pub enum Definition<'a, T: Text<'a>> { Operation(OperationDefinition<'a, T>), Fragment(FragmentDefinition<'a, T>), } #[derive(Debug, Clone, PartialEq)] pub struct FragmentDefinition<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub type_condition: TypeCondition<'a, T>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } #[derive(Debug, Clone, PartialEq)] pub enum OperationDefinition<'a, T: Text<'a>> { SelectionSet(SelectionSet<'a, T>), Query(Query<'a, T>), Mutation(Mutation<'a, T>), Subscription(Subscription<'a, T>), } #[derive(Debug, Clone, PartialEq)] pub struct Query<'a, T: Text<'a>> { pub position: Pos, pub name: Option, pub variable_definitions: Vec>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } #[derive(Debug, Clone, PartialEq)] pub struct Mutation<'a, T: Text<'a>> { pub position: Pos, pub name: Option, pub variable_definitions: Vec>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } #[derive(Debug, Clone, PartialEq)] pub struct Subscription<'a, T: Text<'a>> { pub position: Pos, pub name: Option, pub variable_definitions: Vec>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } #[derive(Debug, Clone, PartialEq)] pub struct SelectionSet<'a, T: Text<'a>> { pub span: (Pos, Pos), pub items: Vec>, } #[derive(Debug, Clone, PartialEq)] pub struct VariableDefinition<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub var_type: Type<'a, T>, pub default_value: Option>, } #[derive(Debug, Clone, PartialEq)] pub enum Selection<'a, T: Text<'a>> { Field(Field<'a, T>), FragmentSpread(FragmentSpread<'a, T>), InlineFragment(InlineFragment<'a, T>), } #[derive(Debug, Clone, PartialEq)] pub struct Field<'a, T: Text<'a>> { pub position: Pos, pub alias: Option, pub name: T::Value, pub arguments: Vec<(T::Value, Value<'a, T>)>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } #[derive(Debug, Clone, PartialEq)] pub struct FragmentSpread<'a, T: Text<'a>> { pub position: Pos, pub fragment_name: T::Value, pub directives: Vec>, } #[derive(Debug, Clone, PartialEq)] pub enum TypeCondition<'a, T: Text<'a>> { On(T::Value), } #[derive(Debug, Clone, PartialEq)] pub struct InlineFragment<'a, T: Text<'a>> { pub position: Pos, pub type_condition: Option>, pub directives: Vec>, pub selection_set: SelectionSet<'a, T>, } graphql-parser-0.4.1/src/query/error.rs000064400000000000000000000010611046102023000162070ustar 00000000000000use combine::easy::Errors; use thiserror::Error; use crate::tokenizer::Token; use crate::position::Pos; pub type InternalError<'a> = Errors, Token<'a>, Pos>; /// Error parsing query /// /// This structure is opaque for forward compatibility. We are exploring a /// way to improve both error message and API. #[derive(Error, Debug)] #[error("query parse error: {}", _0)] pub struct ParseError(String); impl<'a> From> for ParseError { fn from(e: InternalError<'a>) -> ParseError { ParseError(format!("{}", e)) } } graphql-parser-0.4.1/src/query/format.rs000064400000000000000000000246051046102023000163570ustar 00000000000000use std::fmt; use crate::format::{format_directives, Displayable, Formatter, Style}; use crate::query::ast::*; impl<'a, T: Text<'a>> Document<'a, T> where T: Text<'a>, { /// Format a document according to style pub fn format(&self, style: &Style) -> String { let mut formatter = Formatter::new(style); self.display(&mut formatter); formatter.into_string() } } fn to_string(v: &T) -> String { let style = Style::default(); let mut formatter = Formatter::new(&style); v.display(&mut formatter); formatter.into_string() } impl<'a, T: Text<'a>> Displayable for Document<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { for item in &self.definitions { item.display(f); } } } impl<'a, T: Text<'a>> Displayable for Definition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { Definition::Operation(ref op) => op.display(f), Definition::Fragment(ref frag) => frag.display(f), } } } impl<'a, T: Text<'a>> Displayable for OperationDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { OperationDefinition::SelectionSet(ref set) => set.display(f), OperationDefinition::Query(ref q) => q.display(f), OperationDefinition::Mutation(ref m) => m.display(f), OperationDefinition::Subscription(ref s) => s.display(f), } } } impl<'a, T: Text<'a>> Displayable for FragmentDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); f.indent(); f.write("fragment "); f.write(self.name.as_ref()); f.write(" "); self.type_condition.display(f); format_directives(&self.directives, f); f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for SelectionSet<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); f.indent(); f.start_block(); for item in &self.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for Selection<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { Selection::Field(ref fld) => fld.display(f), Selection::InlineFragment(ref frag) => frag.display(f), Selection::FragmentSpread(ref frag) => frag.display(f), } } } fn format_arguments<'a, T: Text<'a>>(arguments: &[(T::Value, Value<'a, T>)], f: &mut Formatter) where T: Text<'a>, { if !arguments.is_empty() { f.start_argument_block('('); f.start_argument(); f.write(arguments[0].0.as_ref()); f.write(": "); arguments[0].1.display(f); for arg in &arguments[1..] { f.deliniate_argument(); f.start_argument(); f.write(arg.0.as_ref()); f.write(": "); arg.1.display(f); } f.end_argument_block(')'); } } impl<'a, T: Text<'a>> Displayable for Field<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); if let Some(ref alias) = self.alias { f.write(alias.as_ref()); f.write(": "); } f.write(self.name.as_ref()); format_arguments(&self.arguments, f); format_directives(&self.directives, f); if !self.selection_set.items.is_empty() { f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } else { f.endline(); } } } impl<'a, T: Text<'a>> Displayable for Query<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); f.indent(); f.write("query"); if let Some(ref name) = self.name { f.write(" "); f.write(name.as_ref()); } if !self.variable_definitions.is_empty() { f.write("("); self.variable_definitions[0].display(f); for var in &self.variable_definitions[1..] { f.write(", "); var.display(f); } f.write(")"); } format_directives(&self.directives, f); f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for Mutation<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); f.indent(); f.write("mutation"); if let Some(ref name) = self.name { f.write(" "); f.write(name.as_ref()); } if !self.variable_definitions.is_empty() { f.write("("); self.variable_definitions[0].display(f); for var in &self.variable_definitions[1..] { f.write(", "); var.display(f); } f.write(")"); } format_directives(&self.directives, f); f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for Subscription<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); f.indent(); f.write("subscription"); if let Some(ref name) = self.name { f.write(" "); f.write(name.as_ref()); if !self.variable_definitions.is_empty() { f.write("("); for var in &self.variable_definitions { var.display(f); } f.write(")"); } } format_directives(&self.directives, f); f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for VariableDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.write("$"); f.write(self.name.as_ref()); f.write(": "); self.var_type.display(f); if let Some(ref default) = self.default_value { f.write(" = "); default.display(f); } } } impl<'a, T: Text<'a>> Displayable for Type<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { Type::NamedType(ref name) => f.write(name.as_ref()), Type::ListType(ref typ) => { f.write("["); typ.display(f); f.write("]"); } Type::NonNullType(ref typ) => { typ.display(f); f.write("!"); } } } } impl<'a, T: Text<'a>> Displayable for Value<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { Value::Variable(ref name) => { f.write("$"); f.write(name.as_ref()); } Value::Int(ref num) => f.write(&format!("{}", num.0)), Value::Float(val) => f.write(&format!("{}", val)), Value::String(ref val) => f.write_quoted(val), Value::Boolean(true) => f.write("true"), Value::Boolean(false) => f.write("false"), Value::Null => f.write("null"), Value::Enum(ref name) => f.write(name.as_ref()), Value::List(ref items) => { f.start_argument_block('['); if !items.is_empty() { f.start_argument(); items[0].display(f); for item in &items[1..] { f.deliniate_argument(); f.start_argument(); item.display(f); } } f.end_argument_block(']'); } Value::Object(ref items) => { f.start_argument_block('{'); let mut first = true; for (name, value) in items.iter() { if first { first = false; } else { f.deliniate_argument(); } f.start_argument(); f.write(name.as_ref()); f.write(": "); value.display(f); } f.end_argument_block('}'); } } } } impl<'a, T: Text<'a>> Displayable for InlineFragment<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("..."); if let Some(ref cond) = self.type_condition { f.write(" "); cond.display(f); } format_directives(&self.directives, f); f.write(" "); f.start_block(); for item in &self.selection_set.items { item.display(f); } f.end_block(); } } impl<'a, T: Text<'a>> Displayable for TypeCondition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { TypeCondition::On(ref name) => { f.write("on "); f.write(name.as_ref()); } } } } impl<'a, T: Text<'a>> Displayable for FragmentSpread<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("..."); f.write(self.fragment_name.as_ref()); format_directives(&self.directives, f); f.endline(); } } impl<'a, T: Text<'a>> Displayable for Directive<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.write("@"); f.write(self.name.as_ref()); format_arguments(self.arguments.as_slice(), f); } } impl_display!( 'a Document, Definition, OperationDefinition, FragmentDefinition, SelectionSet, Field, Query, Mutation, Subscription, VariableDefinition, Type, Value, InlineFragment, TypeCondition, FragmentSpread, Directive, ); graphql-parser-0.4.1/src/query/grammar.rs000064400000000000000000000314521046102023000165130ustar 00000000000000use combine::{eof, many1, optional, position, StdParseResult}; use combine::{parser, Parser}; use crate::common::Directive; use crate::common::{arguments, default_value, directives, parse_type}; use crate::helpers::{ident, name, punct}; use crate::query::ast::*; use crate::query::error::ParseError; use crate::tokenizer::TokenStream; pub fn field<'a, S>(input: &mut TokenStream<'a>) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position(), name::<'a, S>(), optional(punct(":").with(name::<'a, S>())), parser(arguments), parser(directives), optional(parser(selection_set)), ) .map( |(position, name_or_alias, opt_name, arguments, directives, sel)| { let (name, alias) = match opt_name { Some(name) => (name, Some(name_or_alias)), None => (name_or_alias, None), }; Field { position, name, alias, arguments, directives, selection_set: sel.unwrap_or_else(|| SelectionSet { span: (position, position), items: Vec::new(), }), } }, ) .parse_stream(input) .into_result() } pub fn selection<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { parser(field) .map(Selection::Field) .or(punct("...").with( ( position(), optional(ident("on").with(name::<'a, S>()).map(TypeCondition::On)), parser(directives), parser(selection_set), ) .map( |(position, type_condition, directives, selection_set)| InlineFragment { position, type_condition, selection_set, directives, }, ) .map(Selection::InlineFragment) .or((position(), name::<'a, S>(), parser(directives)) .map(|(position, fragment_name, directives)| FragmentSpread { position, fragment_name, directives, }) .map(Selection::FragmentSpread)), )) .parse_stream(input) .into_result() } pub fn selection_set<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position().skip(punct("{")), many1(parser(selection)), position().skip(punct("}")), ) .map(|(start, items, end)| SelectionSet { span: (start, end), items, }) .parse_stream(input) .into_result() } pub fn query<'a, T: Text<'a>>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { position() .skip(ident("query")) .and(parser(operation_common)) .map( |(position, (name, variable_definitions, directives, selection_set))| Query { position, name, selection_set, variable_definitions, directives, }, ) .parse_stream(input) .into_result() } /// A set of attributes common to a Query and a Mutation #[allow(type_alias_bounds)] type OperationCommon<'a, T: Text<'a>> = ( Option, Vec>, Vec>, SelectionSet<'a, T>, ); pub fn operation_common<'a, T: Text<'a>>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { optional(name::<'a, T>()) .and( optional( punct("(") .with(many1( ( position(), punct("$").with(name::<'a, T>()).skip(punct(":")), parser(parse_type), optional(punct("=").with(parser(default_value))), ) .map( |(position, name, var_type, default_value)| VariableDefinition { position, name, var_type, default_value, }, ), )) .skip(punct(")")), ) .map(|vars| vars.unwrap_or_default()), ) .and(parser(directives)) .and(parser(selection_set)) .map(|(((a, b), c), d)| (a, b, c, d)) .parse_stream(input) .into_result() } pub fn mutation<'a, T: Text<'a>>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { position() .skip(ident("mutation")) .and(parser(operation_common)) .map( |(position, (name, variable_definitions, directives, selection_set))| Mutation { position, name, selection_set, variable_definitions, directives, }, ) .parse_stream(input) .into_result() } pub fn subscription<'a, T: Text<'a>>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { position() .skip(ident("subscription")) .and(parser(operation_common)) .map( |(position, (name, variable_definitions, directives, selection_set))| Subscription { position, name, selection_set, variable_definitions, directives, }, ) .parse_stream(input) .into_result() } pub fn operation_definition<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { parser(selection_set) .map(OperationDefinition::SelectionSet) .or(parser(query).map(OperationDefinition::Query)) .or(parser(mutation).map(OperationDefinition::Mutation)) .or(parser(subscription).map(OperationDefinition::Subscription)) .parse_stream(input) .into_result() } pub fn fragment_definition<'a, T: Text<'a>>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position().skip(ident("fragment")), name::<'a, T>(), ident("on").with(name::<'a, T>()).map(TypeCondition::On), parser(directives), parser(selection_set), ) .map( |(position, name, type_condition, directives, selection_set)| FragmentDefinition { position, name, type_condition, directives, selection_set, }, ) .parse_stream(input) .into_result() } pub fn definition<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { parser(operation_definition) .map(Definition::Operation) .or(parser(fragment_definition).map(Definition::Fragment)) .parse_stream(input) .into_result() } /// Parses a piece of query language and returns an AST pub fn parse_query<'a, S>(s: &'a str) -> Result, ParseError> where S: Text<'a>, { let mut tokens = TokenStream::new(s); let (doc, _) = many1(parser(definition)) .map(|d| Document { definitions: d }) .skip(eof()) .parse_stream(&mut tokens) .into_result() .map_err(|e| e.into_inner().error)?; Ok(doc) } /// Parses a single ExecutableDefinition and returns an AST as well as the /// remainder of the input which is unparsed pub fn consume_definition<'a, S>(s: &'a str) -> Result<(Definition<'a, S>, &'a str), ParseError> where S: Text<'a>, { let tokens = TokenStream::new(s); let (doc, tokens) = parser(definition).parse(tokens)?; Ok((doc, &s[tokens.offset()..])) } #[cfg(test)] mod test { use super::{consume_definition, parse_query}; use crate::position::Pos; use crate::query::grammar::*; fn ast(s: &str) -> Document { parse_query::(s).unwrap().to_owned() } #[test] fn one_field() { assert_eq!( ast("{ a }"), Document { definitions: vec![Definition::Operation(OperationDefinition::SelectionSet( SelectionSet { span: (Pos { line: 1, column: 1 }, Pos { line: 1, column: 5 }), items: vec![Selection::Field(Field { position: Pos { line: 1, column: 3 }, alias: None, name: "a".into(), arguments: Vec::new(), directives: Vec::new(), selection_set: SelectionSet { span: (Pos { line: 1, column: 3 }, Pos { line: 1, column: 3 }), items: Vec::new() }, }),], } ))], } ); } #[test] fn builtin_values() { assert_eq!( ast("{ a(t: true, f: false, n: null) }"), Document { definitions: vec![Definition::Operation(OperationDefinition::SelectionSet( SelectionSet { span: ( Pos { line: 1, column: 1 }, Pos { line: 1, column: 33 } ), items: vec![Selection::Field(Field { position: Pos { line: 1, column: 3 }, alias: None, name: "a".into(), arguments: vec![ ("t".into(), Value::Boolean(true)), ("f".into(), Value::Boolean(false)), ("n".into(), Value::Null), ], directives: Vec::new(), selection_set: SelectionSet { span: (Pos { line: 1, column: 3 }, Pos { line: 1, column: 3 }), items: Vec::new() }, }),], } ))], } ); } #[test] fn one_field_roundtrip() { assert_eq!(ast("{ a }").to_string(), "{\n a\n}\n"); } #[test] #[should_panic(expected = "number too large")] fn large_integer() { ast("{ a(x: 10000000000000000000000000000 }"); } #[test] fn consume_single_query() { let (query, remainder) = consume_definition::("query { a } query { b }").unwrap(); assert!(matches!(query, Definition::Operation(_))); assert_eq!(remainder, "query { b }"); } #[test] fn consume_full_text() { let (query, remainder) = consume_definition::("query { a }").unwrap(); assert!(matches!(query, Definition::Operation(_))); assert_eq!(remainder, ""); } #[test] fn consume_single_query_preceding_non_graphql() { let (query, remainder) = consume_definition::("query { a } where a > 1 => 10.0").unwrap(); assert!(matches!(query, Definition::Operation(_))); assert_eq!(remainder, "where a > 1 => 10.0"); } #[test] fn consume_fails_without_operation() { let err = consume_definition::("where a > 1 => 10.0") .expect_err("Expected parse to fail with an error"); let err = format!("{}", err); assert_eq!(err, "query parse error: Parse error at 1:1\nUnexpected `where[Name]`\nExpected {, query, mutation, subscription or fragment\n"); } #[test] fn recursion_too_deep() { let query = format!( "{}(b: {}{}){}", "{ a".repeat(30), "[".repeat(25), "]".repeat(25), "}".repeat(30) ); let result = parse_query::<&str>(&query); let err = format!("{}", result.unwrap_err()); assert_eq!( &err, "query parse error: Parse error at 1:114\nExpected ]\nRecursion limit exceeded\n" ) } } graphql-parser-0.4.1/src/query/minify.rs000064400000000000000000000040111046102023000163470ustar 00000000000000use crate::tokenizer::{Kind, Token, TokenStream}; use combine::StreamOnce; use thiserror::Error; /// Error minifying query #[derive(Error, Debug)] #[error("query minify error: {}", _0)] pub struct MinifyError(String); pub fn minify_query(source: String) -> Result { let mut bits: Vec<&str> = Vec::new(); let mut stream = TokenStream::new(source.as_str()); let mut prev_was_punctuator = false; loop { match stream.uncons() { Ok(x) => { let token: Token = x; let is_non_punctuator = token.kind != Kind::Punctuator; if prev_was_punctuator && is_non_punctuator { bits.push(" "); } bits.push(token.value); prev_was_punctuator = is_non_punctuator; } Err(ref e) if e == &combine::easy::Error::end_of_input() => break, Err(e) => return Err(MinifyError(e.to_string())), } } Ok(bits.join("")) } #[cfg(test)] mod tests { #[test] fn strip_ignored_characters() { let source = " query SomeQuery($foo: String!, $bar: String) { someField(foo: $foo, bar: $bar) { a b { ... on B { c d } } } } "; let minified = super::minify_query(source.to_string()).expect("minification failed"); assert_eq!( &minified, "query SomeQuery($foo:String!$bar:String){someField(foo:$foo bar:$bar){a b{...on B{c d}}}}" ); } #[test] fn unexpected_token() { let source = " query foo { bar; } "; let minified = super::minify_query(source.to_string()); assert!(minified.is_err()); assert_eq!( minified.unwrap_err().to_string(), "query minify error: Unexpected unexpected character ';'" ); } } graphql-parser-0.4.1/src/query/mod.rs000064400000000000000000000004001046102023000156310ustar 00000000000000//! Query language AST and parsing utilities //! mod ast; mod error; mod format; mod grammar; mod minify; pub use self::grammar::{parse_query, consume_definition}; pub use self::error::ParseError; pub use self::ast::*; pub use self::minify::minify_query; graphql-parser-0.4.1/src/schema/ast.rs000064400000000000000000000324551046102023000157530ustar 00000000000000use std::str::FromStr; use thiserror::Error; pub use crate::common::{Directive, Type, Value, Text}; use crate::position::Pos; #[derive(Debug, Clone, Default, PartialEq)] pub struct Document<'a, T: Text<'a>> where T: Text<'a> { pub definitions: Vec>, } impl<'a> Document<'a, String> { pub fn into_static(self) -> Document<'static, String> { // To support both reference and owned values in the AST, // all string data is represented with the ::common::Str<'a, T: Text<'a>> // wrapper type. // This type must carry the liftetime of the schema string, // and is stored in a PhantomData value on the Str type. // When using owned String types, the actual lifetime of // the Ast nodes is 'static, since no references are kept, // but the nodes will still carry the input lifetime. // To continue working with Document in a owned fasion // the lifetime needs to be transmuted to 'static. // // This is safe because no references are present. // Just the PhantomData lifetime reference is transmuted away. unsafe { std::mem::transmute::<_, Document<'static, String>>(self) } } } #[derive(Debug, Clone, PartialEq)] pub enum Definition<'a, T: Text<'a>> { SchemaDefinition(SchemaDefinition<'a, T>), TypeDefinition(TypeDefinition<'a, T>), TypeExtension(TypeExtension<'a, T>), DirectiveDefinition(DirectiveDefinition<'a, T>), } #[derive(Debug, Clone, Default, PartialEq)] pub struct SchemaDefinition<'a, T: Text<'a>> { pub position: Pos, pub directives: Vec>, pub query: Option, pub mutation: Option, pub subscription: Option, } #[derive(Debug, Clone, PartialEq)] pub enum TypeDefinition<'a, T: Text<'a>> { Scalar(ScalarType<'a, T>), Object(ObjectType<'a, T>), Interface(InterfaceType<'a, T>), Union(UnionType<'a, T>), Enum(EnumType<'a, T>), InputObject(InputObjectType<'a, T>), } #[derive(Debug, Clone, PartialEq)] pub enum TypeExtension<'a, T: Text<'a>> { Scalar(ScalarTypeExtension<'a, T>), Object(ObjectTypeExtension<'a, T>), Interface(InterfaceTypeExtension<'a, T>), Union(UnionTypeExtension<'a, T>), Enum(EnumTypeExtension<'a, T>), InputObject(InputObjectTypeExtension<'a, T>), } #[derive(Debug, Clone, PartialEq)] pub struct ScalarType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub directives: Vec>, } impl<'a, T> ScalarType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, directives: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct ScalarTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub directives: Vec>, } impl<'a, T> ScalarTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, directives: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct ObjectType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub implements_interfaces: Vec, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> ObjectType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, implements_interfaces: vec![], directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct ObjectTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub implements_interfaces: Vec, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> ObjectTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, implements_interfaces: vec![], directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct Field<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub arguments: Vec>, pub field_type: Type<'a, T>, pub directives: Vec>, } #[derive(Debug, Clone, PartialEq)] pub struct InputValue<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub value_type: Type<'a, T>, pub default_value: Option>, pub directives: Vec>, } #[derive(Debug, Clone, PartialEq)] pub struct InterfaceType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub implements_interfaces: Vec, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> InterfaceType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, implements_interfaces: vec![], directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct InterfaceTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub implements_interfaces: Vec, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> InterfaceTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, implements_interfaces: vec![], directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct UnionType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub directives: Vec>, pub types: Vec, } impl<'a, T> UnionType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, directives: vec![], types: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct UnionTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub directives: Vec>, pub types: Vec, } impl<'a, T> UnionTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, directives: vec![], types: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct EnumType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub directives: Vec>, pub values: Vec>, } impl<'a, T> EnumType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, directives: vec![], values: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct EnumValue<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub directives: Vec>, } impl<'a, T> EnumValue<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, directives: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct EnumTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub directives: Vec>, pub values: Vec>, } impl<'a, T> EnumTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, directives: vec![], values: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct InputObjectType<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> InputObjectType<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq)] pub struct InputObjectTypeExtension<'a, T: Text<'a>> { pub position: Pos, pub name: T::Value, pub directives: Vec>, pub fields: Vec>, } impl<'a, T> InputObjectTypeExtension<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), name, directives: vec![], fields: vec![], } } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum DirectiveLocation { // executable Query, Mutation, Subscription, Field, FragmentDefinition, FragmentSpread, InlineFragment, // type_system Schema, Scalar, Object, FieldDefinition, ArgumentDefinition, Interface, Union, Enum, EnumValue, InputObject, InputFieldDefinition, VariableDefinition, } #[derive(Debug, Clone, PartialEq)] pub struct DirectiveDefinition<'a, T: Text<'a>> { pub position: Pos, pub description: Option, pub name: T::Value, pub arguments: Vec>, pub repeatable: bool, pub locations: Vec, } impl<'a, T> DirectiveDefinition<'a, T> where T: Text<'a> { pub fn new(name: T::Value) -> Self { Self { position: Pos::default(), description: None, name, arguments: vec![], repeatable: false, locations: vec![], } } } impl DirectiveLocation { /// Returns GraphQL syntax compatible name of the directive pub fn as_str(&self) -> &'static str { use self::DirectiveLocation::*; match *self { Query => "QUERY", Mutation => "MUTATION", Subscription => "SUBSCRIPTION", Field => "FIELD", FragmentDefinition => "FRAGMENT_DEFINITION", FragmentSpread => "FRAGMENT_SPREAD", InlineFragment => "INLINE_FRAGMENT", Schema => "SCHEMA", Scalar => "SCALAR", Object => "OBJECT", FieldDefinition => "FIELD_DEFINITION", ArgumentDefinition => "ARGUMENT_DEFINITION", Interface => "INTERFACE", Union => "UNION", Enum => "ENUM", EnumValue => "ENUM_VALUE", InputObject => "INPUT_OBJECT", InputFieldDefinition => "INPUT_FIELD_DEFINITION", VariableDefinition => "VARIABLE_DEFINITION", } } /// Returns `true` if this location is for queries (execution) pub fn is_query(&self) -> bool { use self::DirectiveLocation::*; match *self { Query | Mutation | Subscription | Field | FragmentDefinition | FragmentSpread | InlineFragment => true, Schema | Scalar | Object | FieldDefinition | ArgumentDefinition | Interface | Union | Enum | EnumValue | InputObject | InputFieldDefinition | VariableDefinition => false, } } /// Returns `true` if this location is for schema pub fn is_schema(&self) -> bool { !self.is_query() } } #[derive(Debug, Error)] #[error("invalid directive location")] pub struct InvalidDirectiveLocation; impl FromStr for DirectiveLocation { type Err = InvalidDirectiveLocation; fn from_str(s: &str) -> Result { use self::DirectiveLocation::*; let val = match s { "QUERY" => Query, "MUTATION" => Mutation, "SUBSCRIPTION" => Subscription, "FIELD" => Field, "FRAGMENT_DEFINITION" => FragmentDefinition, "FRAGMENT_SPREAD" => FragmentSpread, "INLINE_FRAGMENT" => InlineFragment, "SCHEMA" => Schema, "SCALAR" => Scalar, "OBJECT" => Object, "FIELD_DEFINITION" => FieldDefinition, "ARGUMENT_DEFINITION" => ArgumentDefinition, "INTERFACE" => Interface, "UNION" => Union, "ENUM" => Enum, "ENUM_VALUE" => EnumValue, "INPUT_OBJECT" => InputObject, "INPUT_FIELD_DEFINITION" => InputFieldDefinition, "VARIABLE_DEFINITION" => VariableDefinition, _ => return Err(InvalidDirectiveLocation), }; Ok(val) } } graphql-parser-0.4.1/src/schema/error.rs000064400000000000000000000010631046102023000163040ustar 00000000000000use combine::easy::Errors; use thiserror::Error; use crate::tokenizer::Token; use crate::position::Pos; pub type InternalError<'a> = Errors, Token<'a>, Pos>; /// Error parsing schema /// /// This structure is opaque for forward compatibility. We are exploring a /// way to improve both error message and API. #[derive(Error, Debug)] #[error("schema parse error: {}", _0)] pub struct ParseError(String); impl<'a> From> for ParseError { fn from(e: InternalError<'a>) -> ParseError { ParseError(format!("{}", e)) } } graphql-parser-0.4.1/src/schema/format.rs000064400000000000000000000311441046102023000164460ustar 00000000000000use std::fmt; use crate::common::Text; use crate::format::{format_directives, Displayable, Formatter, Style}; use crate::schema::ast::*; impl<'a, T> Document<'a, T> where T: Text<'a>, { /// Format a document according to style pub fn format(&self, style: &Style) -> String { let mut formatter = Formatter::new(style); self.display(&mut formatter); formatter.into_string() } } fn to_string(v: &T) -> String { let style = Style::default(); let mut formatter = Formatter::new(&style); v.display(&mut formatter); formatter.into_string() } fn description(description: &Option, f: &mut Formatter) { if let Some(ref descr) = *description { f.indent(); f.write_quoted(descr.as_ref()); f.endline(); } } impl<'a, T> Displayable for Document<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { for item in &self.definitions { item.display(f); } } } impl<'a, T> Displayable for Definition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.margin(); match *self { Definition::SchemaDefinition(ref s) => s.display(f), Definition::TypeDefinition(ref t) => t.display(f), Definition::TypeExtension(ref e) => e.display(f), Definition::DirectiveDefinition(ref d) => d.display(f), } } } impl<'a, T> Displayable for SchemaDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("schema"); format_directives(&self.directives, f); f.write(" "); f.start_block(); if let Some(ref q) = self.query { f.indent(); f.write("query: "); f.write(q.as_ref()); f.endline(); } if let Some(ref m) = self.mutation { f.indent(); f.write("mutation: "); f.write(m.as_ref()); f.endline(); } if let Some(ref s) = self.subscription { f.indent(); f.write("subscription: "); f.write(s.as_ref()); f.endline(); } f.end_block(); } } impl<'a, T> Displayable for TypeDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { TypeDefinition::Scalar(ref s) => s.display(f), TypeDefinition::Object(ref o) => o.display(f), TypeDefinition::Interface(ref i) => i.display(f), TypeDefinition::Union(ref u) => u.display(f), TypeDefinition::Enum(ref e) => e.display(f), TypeDefinition::InputObject(ref i) => i.display(f), } } } impl<'a, T> Displayable for ScalarType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("scalar "); f.write(self.name.as_ref()); format_directives(&self.directives, f); f.endline(); } } impl<'a, T> Displayable for ScalarTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend scalar "); f.write(self.name.as_ref()); format_directives(&self.directives, f); f.endline(); } } fn format_fields<'a, T>(fields: &[Field<'a, T>], f: &mut Formatter) where T: Text<'a>, { if !fields.is_empty() { f.write(" "); f.start_block(); for fld in fields { fld.display(f); } f.end_block(); } else { f.endline(); } } impl<'a, T> Displayable for ObjectType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("type "); f.write(self.name.as_ref()); if !self.implements_interfaces.is_empty() { f.write(" implements "); f.write(self.implements_interfaces[0].as_ref()); for name in &self.implements_interfaces[1..] { f.write(" & "); f.write(name.as_ref()); } } format_directives(&self.directives, f); format_fields(&self.fields, f); } } impl<'a, T> Displayable for ObjectTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend type "); f.write(self.name.as_ref()); if !self.implements_interfaces.is_empty() { f.write(" implements "); f.write(self.implements_interfaces[0].as_ref()); for name in &self.implements_interfaces[1..] { f.write(" & "); f.write(name.as_ref()); } } format_directives(&self.directives, f); format_fields(&self.fields, f); } } impl<'a, T> Displayable for InputValue<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { if let Some(ref descr) = self.description { f.write_quoted(descr.as_ref()); f.write(" "); } f.write(self.name.as_ref()); f.write(": "); self.value_type.display(f); if let Some(ref def) = self.default_value { f.write(" = "); def.display(f); } format_directives(&self.directives, f); } } fn format_arguments<'a, T>(arguments: &[InputValue<'a, T>], f: &mut Formatter) where T: Text<'a>, { if !arguments.is_empty() { f.write("("); arguments[0].display(f); for arg in &arguments[1..] { f.write(", "); arg.display(f); } f.write(")"); } } impl<'a, T> Displayable for Field<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write(self.name.as_ref()); format_arguments(&self.arguments, f); f.write(": "); self.field_type.display(f); format_directives(&self.directives, f); f.endline(); } } impl<'a, T> Displayable for InterfaceType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("interface "); f.write(self.name.as_ref()); if !self.implements_interfaces.is_empty() { f.write(" implements "); f.write(self.implements_interfaces[0].as_ref()); for name in &self.implements_interfaces[1..] { f.write(" & "); f.write(name.as_ref()); } } format_directives(&self.directives, f); format_fields(&self.fields, f); } } impl<'a, T> Displayable for InterfaceTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend interface "); f.write(self.name.as_ref()); if !self.implements_interfaces.is_empty() { f.write(" implements "); f.write(self.implements_interfaces[0].as_ref()); for name in &self.implements_interfaces[1..] { f.write(" & "); f.write(name.as_ref()); } } format_directives(&self.directives, f); format_fields(&self.fields, f); } } impl<'a, T> Displayable for UnionType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("union "); f.write(self.name.as_ref()); format_directives(&self.directives, f); if !self.types.is_empty() { f.write(" = "); f.write(self.types[0].as_ref()); for typ in &self.types[1..] { f.write(" | "); f.write(typ.as_ref()); } } f.endline(); } } impl<'a, T> Displayable for UnionTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend union "); f.write(self.name.as_ref()); format_directives(&self.directives, f); if !self.types.is_empty() { f.write(" = "); f.write(self.types[0].as_ref()); for typ in &self.types[1..] { f.write(" | "); f.write(typ.as_ref()); } } f.endline(); } } impl<'a, T> Displayable for EnumType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("enum "); f.write(self.name.as_ref()); format_directives(&self.directives, f); if !self.values.is_empty() { f.write(" "); f.start_block(); for val in &self.values { f.indent(); if let Some(ref descr) = val.description { f.write_quoted(descr.as_ref()); f.write(" "); } f.write(val.name.as_ref()); format_directives(&val.directives, f); f.endline(); } f.end_block(); } else { f.endline(); } } } impl<'a, T> Displayable for EnumTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend enum "); f.write(self.name.as_ref()); format_directives(&self.directives, f); if !self.values.is_empty() { f.write(" "); f.start_block(); for val in &self.values { f.indent(); if let Some(ref descr) = val.description { f.write_quoted(descr.as_ref()); f.write(" "); } f.write(val.name.as_ref()); format_directives(&val.directives, f); f.endline(); } f.end_block(); } else { f.endline(); } } } fn format_inputs<'a, T>(fields: &[InputValue<'a, T>], f: &mut Formatter) where T: Text<'a>, { if !fields.is_empty() { f.write(" "); f.start_block(); for fld in fields { f.indent(); fld.display(f); f.endline(); } f.end_block(); } else { f.endline(); } } impl<'a, T> Displayable for InputObjectType<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("input "); f.write(self.name.as_ref()); format_directives(&self.directives, f); format_inputs(&self.fields, f); } } impl<'a, T> Displayable for InputObjectTypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { f.indent(); f.write("extend input "); f.write(self.name.as_ref()); format_directives(&self.directives, f); format_inputs(&self.fields, f); } } impl<'a, T> Displayable for TypeExtension<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { match *self { TypeExtension::Scalar(ref s) => s.display(f), TypeExtension::Object(ref o) => o.display(f), TypeExtension::Interface(ref i) => i.display(f), TypeExtension::Union(ref u) => u.display(f), TypeExtension::Enum(ref e) => e.display(f), TypeExtension::InputObject(ref i) => i.display(f), } } } impl<'a, T> Displayable for DirectiveDefinition<'a, T> where T: Text<'a>, { fn display(&self, f: &mut Formatter) { description(&self.description, f); f.indent(); f.write("directive @"); f.write(self.name.as_ref()); format_arguments(&self.arguments, f); if self.repeatable { f.write(" repeatable"); } if !self.locations.is_empty() { f.write(" on "); let mut first = true; for loc in &self.locations { if first { first = false; } else { f.write(" | "); } f.write(loc.as_str()); } } f.endline(); } } impl_display!( 'a Document, Definition, SchemaDefinition, TypeDefinition, TypeExtension, ScalarType, ScalarTypeExtension, ObjectType, ObjectTypeExtension, Field, InputValue, InterfaceType, InterfaceTypeExtension, UnionType, UnionTypeExtension, EnumType, EnumTypeExtension, InputObjectType, InputObjectTypeExtension, DirectiveDefinition, ); graphql-parser-0.4.1/src/schema/grammar.rs000064400000000000000000000521461046102023000166110ustar 00000000000000use combine::easy::{Error, Errors}; use combine::error::StreamError; use combine::sep_by1; use combine::{choice, eof, many, many1, optional, position}; use combine::{parser, Parser, StdParseResult}; use crate::common::{default_value, directives, parse_type, string, Text}; use crate::helpers::{ident, kind, name, punct}; use crate::schema::ast::*; use crate::schema::error::ParseError; use crate::tokenizer::{Kind as T, Token, TokenStream}; pub fn schema<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position().skip(ident("schema")), parser(directives), punct("{") .with(many((kind(T::Name).skip(punct(":")), name::<'a, S>()))) .skip(punct("}")), ) .flat_map( |(position, directives, operations): (_, _, Vec<(Token, _)>)| { let mut query = None; let mut mutation = None; let mut subscription = None; let mut err = Errors::empty(position); for (oper, type_name) in operations { match oper.value { "query" if query.is_some() => { err.add_error(Error::unexpected_static_message( "duplicate `query` operation", )); } "query" => { query = Some(type_name); } "mutation" if mutation.is_some() => { err.add_error(Error::unexpected_static_message( "duplicate `mutation` operation", )); } "mutation" => { mutation = Some(type_name); } "subscription" if subscription.is_some() => { err.add_error(Error::unexpected_static_message( "duplicate `subscription` operation", )); } "subscription" => { subscription = Some(type_name); } _ => { err.add_error(Error::unexpected_token(oper)); err.add_error(Error::expected_static_message("query")); err.add_error(Error::expected_static_message("mutation")); err.add_error(Error::expected_static_message("subscription")); } } } if !err.errors.is_empty() { return Err(err); } Ok(SchemaDefinition { position, directives, query, mutation, subscription, }) }, ) .parse_stream(input) .into_result() } pub fn scalar_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("scalar").with(name::<'a, T>()), parser(directives), ) .map(|(position, name, directives)| ScalarType { position, description: None, name, directives, }) .parse_stream(input) .into_result() } pub fn scalar_type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("scalar").with(name::<'a, T>()), parser(directives), ) .flat_map(|(position, name, directives)| { if directives.is_empty() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Scalar type extension should contain at least \ one directive.", )); return Err(e); } Ok(ScalarTypeExtension { position, name, directives, }) }) .parse_stream(input) .into_result() } pub fn implements_interfaces<'a, X>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where X: Text<'a>, { optional( ident("implements") .skip(optional(punct("&"))) .with(sep_by1(name::<'a, X>(), punct("&"))), ) .map(|opt| opt.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn input_value<'a, X>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where X: Text<'a>, { ( position(), optional(parser(string)), name::<'a, X>(), punct(":").with(parser(parse_type)), optional(punct("=").with(parser(default_value))), parser(directives), ) .map( |(position, description, name, value_type, default_value, directives)| InputValue { position, description, name, value_type, default_value, directives, }, ) .parse_stream(input) .into_result() } pub fn arguments_definition<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult>, TokenStream<'a>> where T: Text<'a>, { optional(punct("(").with(many1(parser(input_value))).skip(punct(")"))) .map(|v| v.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn field<'a, S>(input: &mut TokenStream<'a>) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position(), optional(parser(string)), name::<'a, S>(), parser(arguments_definition), punct(":").with(parser(parse_type)), parser(directives), ) .map( |(position, description, name, arguments, field_type, directives)| Field { position, description, name, arguments, field_type, directives, }, ) .parse_stream(input) .into_result() } pub fn fields<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult>, TokenStream<'a>> where S: Text<'a>, { optional(punct("{").with(many1(parser(field))).skip(punct("}"))) .map(|v| v.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn object_type<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position(), ident("type").with(name::<'a, S>()), parser(implements_interfaces::), parser(directives), parser(fields), ) .map(|(position, name, interfaces, directives, fields)| { ObjectType { position, name, directives, fields, implements_interfaces: interfaces, description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn object_type_extension<'a, S>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where S: Text<'a>, { ( position(), ident("type").with(name::<'a, S>()), parser(implements_interfaces::), parser(directives), parser(fields), ) .flat_map(|(position, name, interfaces, directives, fields)| { if interfaces.is_empty() && directives.is_empty() && fields.is_empty() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Object type extension should contain at least \ one interface, directive or field.", )); return Err(e); } Ok(ObjectTypeExtension { position, name, directives, fields, implements_interfaces: interfaces, }) }) .parse_stream(input) .into_result() } pub fn interface_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("interface").with(name::<'a, T>()), parser(implements_interfaces::), parser(directives), parser(fields), ) .map(|(position, name, interfaces, directives, fields)| { InterfaceType { position, name, implements_interfaces: interfaces, directives, fields, description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn interface_type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("interface").with(name::<'a, T>()), parser(implements_interfaces::), parser(directives), parser(fields), ) .flat_map(|(position, name, interfaces, directives, fields)| { if directives.is_empty() && fields.is_empty() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Interface type extension should contain at least \ one directive or field.", )); return Err(e); } Ok(InterfaceTypeExtension { position, name, implements_interfaces: interfaces, directives, fields, }) }) .parse_stream(input) .into_result() } pub fn union_members<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { optional(punct("|")) .with(sep_by1(name::<'a, T>(), punct("|"))) .parse_stream(input) .into_result() } pub fn union_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("union").with(name::<'a, T>()), parser(directives), optional(punct("=").with(parser(union_members::))), ) .map(|(position, name, directives, types)| { UnionType { position, name, directives, types: types.unwrap_or_else(Vec::new), description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn union_type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("union").with(name::<'a, T>()), parser(directives), optional(punct("=").with(parser(union_members::))), ) .flat_map(|(position, name, directives, types)| { if directives.is_empty() && types.is_none() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Union type extension should contain at least \ one directive or type.", )); return Err(e); } Ok(UnionTypeExtension { position, name, directives, types: types.unwrap_or_else(Vec::new), }) }) .parse_stream(input) .into_result() } pub fn enum_values<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult>, TokenStream<'a>> where T: Text<'a>, { punct("{") .with(many1( ( position(), optional(parser(string)), name::<'a, T>(), parser(directives), ) .map(|(position, description, name, directives)| EnumValue { position, description, name, directives, }), )) .skip(punct("}")) .parse_stream(input) .into_result() } pub fn enum_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("enum").with(name::<'a, T>()), parser(directives), optional(parser(enum_values)), ) .map(|(position, name, directives, values)| { EnumType { position, name, directives, values: values.unwrap_or_else(Vec::new), description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn enum_type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("enum").with(name::<'a, T>()), parser(directives), optional(parser(enum_values)), ) .flat_map(|(position, name, directives, values)| { if directives.is_empty() && values.is_none() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Enum type extension should contain at least \ one directive or value.", )); return Err(e); } Ok(EnumTypeExtension { position, name, directives, values: values.unwrap_or_else(Vec::new), }) }) .parse_stream(input) .into_result() } pub fn input_fields<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult>, TokenStream<'a>> where T: Text<'a>, { optional(punct("{").with(many1(parser(input_value))).skip(punct("}"))) .map(|v| v.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn input_object_type<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("input").with(name::<'a, T>()), parser(directives), parser(input_fields), ) .map(|(position, name, directives, fields)| { InputObjectType { position, name, directives, fields, description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn input_object_type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("input").with(name::<'a, T>()), parser(directives), parser(input_fields), ) .flat_map(|(position, name, directives, fields)| { if directives.is_empty() && fields.is_empty() { let mut e = Errors::empty(position); e.add_error(Error::expected_static_message( "Input object type extension should contain at least \ one directive or field.", )); return Err(e); } Ok(InputObjectTypeExtension { position, name, directives, fields, }) }) .parse_stream(input) .into_result() } pub fn directive_locations<'a>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> { optional(optional(punct("|")).with(sep_by1( kind(T::Name).and_then(|tok| tok.value.parse::()), punct("|"), ))) .map(|opt| opt.unwrap_or_default()) .parse_stream(input) .into_result() } pub fn directive_definition<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ( position(), ident("directive").and(punct("@")).with(name::<'a, T>()), parser(arguments_definition), optional(ident("repeatable")), ident("on").with(parser(directive_locations)), ) .map(|(position, name, arguments, repeatable, locations)| { DirectiveDefinition { position, name, arguments, locations, repeatable: repeatable.is_some(), description: None, // is filled in described_definition } }) .parse_stream(input) .into_result() } pub fn described_definition<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { use self::TypeDefinition::*; ( optional(parser(string)), choice(( choice(( parser(scalar_type).map(Scalar), parser(object_type).map(Object), parser(interface_type).map(Interface), parser(union_type).map(Union), parser(enum_type).map(Enum), parser(input_object_type).map(InputObject), )) .map(Definition::TypeDefinition), parser(directive_definition).map(Definition::DirectiveDefinition), )), ) // We can't set description inside type definition parser, because // that means parser will need to backtrace, and that in turn // means that error reporting is bad (along with performance) .map(|(descr, mut def)| { use crate::schema::ast::Definition::TypeDefinition as T; use crate::schema::ast::Definition::*; use crate::schema::ast::TypeDefinition::*; match def { T(Scalar(ref mut s)) => s.description = descr, T(Object(ref mut o)) => o.description = descr, T(Interface(ref mut i)) => i.description = descr, T(Union(ref mut u)) => u.description = descr, T(Enum(ref mut e)) => e.description = descr, T(InputObject(ref mut o)) => o.description = descr, DirectiveDefinition(ref mut d) => d.description = descr, SchemaDefinition(_) => unreachable!(), TypeExtension(_) => unreachable!(), } def }) .parse_stream(input) .into_result() } pub fn type_extension<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { ident("extend") .with(choice(( parser(scalar_type_extension).map(TypeExtension::Scalar), parser(object_type_extension).map(TypeExtension::Object), parser(interface_type_extension).map(TypeExtension::Interface), parser(union_type_extension).map(TypeExtension::Union), parser(enum_type_extension).map(TypeExtension::Enum), parser(input_object_type_extension).map(TypeExtension::InputObject), ))) .parse_stream(input) .into_result() } pub fn definition<'a, T>( input: &mut TokenStream<'a>, ) -> StdParseResult, TokenStream<'a>> where T: Text<'a>, { choice(( parser(schema).map(Definition::SchemaDefinition), parser(type_extension).map(Definition::TypeExtension), parser(described_definition), )) .parse_stream(input) .into_result() } /// Parses a piece of schema language and returns an AST pub fn parse_schema<'a, T>(s: &'a str) -> Result, ParseError> where T: Text<'a>, { let mut tokens = TokenStream::new(s); let (doc, _) = many1(parser(definition)) .map(|d| Document { definitions: d }) .skip(eof()) .parse_stream(&mut tokens) .into_result() .map_err(|e| e.into_inner().error)?; Ok(doc) } #[cfg(test)] mod test { use super::parse_schema; use crate::position::Pos; use crate::schema::grammar::*; fn ast(s: &str) -> Document { parse_schema::(s).unwrap().to_owned() } #[test] fn one_field() { assert_eq!( ast("schema { query: Query }"), Document { definitions: vec![Definition::SchemaDefinition(SchemaDefinition { position: Pos { line: 1, column: 1 }, directives: vec![], query: Some("Query".into()), mutation: None, subscription: None })], } ); } } graphql-parser-0.4.1/src/schema/mod.rs000064400000000000000000000002751046102023000157360ustar 00000000000000//! Schema definition language AST and utility //! mod ast; mod grammar; mod error; mod format; pub use self::ast::*; pub use self::error::ParseError; pub use self::grammar::parse_schema; graphql-parser-0.4.1/src/tokenizer.rs000064400000000000000000000471011046102023000157300ustar 00000000000000use std::fmt; use combine::easy::{Error, Errors, Info}; use combine::error::StreamError; use combine::stream::ResetStream; use combine::{Positioned, StreamOnce}; use crate::position::Pos; #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum Kind { Punctuator, Name, IntValue, FloatValue, StringValue, BlockString, } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub struct Token<'a> { pub kind: Kind, pub value: &'a str, } #[derive(Debug, PartialEq)] pub struct TokenStream<'a> { buf: &'a str, position: Pos, off: usize, next_state: Option<(usize, Token<'a>, usize, Pos)>, recursion_limit: usize, } impl TokenStream<'_> { pub(crate) fn offset(&self) -> usize { self.off } } #[derive(Clone, Debug, PartialEq)] pub struct Checkpoint { position: Pos, off: usize, } impl<'a> StreamOnce for TokenStream<'a> { type Token = Token<'a>; type Range = Token<'a>; type Position = Pos; type Error = Errors, Token<'a>, Pos>; fn uncons(&mut self) -> Result, Token<'a>>> { if let Some((at, tok, off, pos)) = self.next_state { if at == self.off { self.off = off; self.position = pos; return Ok(tok); } } let old_pos = self.off; let (kind, len) = self.take_token()?; let value = &self.buf[self.off - len..self.off]; self.skip_whitespace(); let token = Token { kind, value }; self.next_state = Some((old_pos, token, self.off, self.position)); Ok(token) } } impl<'a> Positioned for TokenStream<'a> { fn position(&self) -> Self::Position { self.position } } impl<'a> ResetStream for TokenStream<'a> { type Checkpoint = Checkpoint; fn checkpoint(&self) -> Self::Checkpoint { Checkpoint { position: self.position, off: self.off, } } fn reset(&mut self, checkpoint: Checkpoint) -> Result<(), Self::Error> { self.position = checkpoint.position; self.off = checkpoint.off; Ok(()) } } // NOTE: we expect that first character is always digit or minus, as returned // by tokenizer fn check_int(value: &str) -> bool { value == "0" || value == "-0" || (!value.starts_with('0') && value != "-" && !value.starts_with("-0") && value[1..].chars().all(|x| x.is_ascii_digit())) } fn check_dec(value: &str) -> bool { !value.is_empty() && value.chars().all(|x| x.is_ascii_digit()) } fn check_exp(value: &str) -> bool { if value.is_empty() { return false; } let first = value.chars().next().unwrap(); if first != '-' && first != '+' && (first <= '0' || first >= '9') { return false; } value[1..].chars().all(|x| x.is_ascii_digit()) } fn check_float(value: &str, exponent: Option, real: Option) -> bool { match (exponent, real) { (Some(e), Some(r)) if e < r => false, (Some(e), Some(r)) => { check_int(&value[..r]) && check_dec(&value[r + 1..e]) && check_exp(&value[e + 1..]) } (Some(e), None) => check_int(&value[..e]) && check_exp(&value[e + 1..]), (None, Some(r)) => check_int(&value[..r]) && check_dec(&value[r + 1..]), (None, None) => unreachable!(), } } impl<'a> TokenStream<'a> { pub fn new(s: &str) -> TokenStream { Self::with_recursion_limit(s, 50) } /// Specify a limit to recursive parsing. Note that increasing the limit /// from the default may represent a security issue since a maliciously /// crafted input may cause a stack overflow, crashing the process. pub(crate) fn with_recursion_limit(s: &str, recursion_limit: usize) -> TokenStream { let mut me = TokenStream { buf: s, position: Pos { line: 1, column: 1 }, off: 0, next_state: None, recursion_limit, }; me.skip_whitespace(); me } /// Convenience for the common case where a token does /// not span multiple lines. Infallible. #[inline] fn advance_token(&mut self, kind: Kind, size: usize) -> Result<(Kind, usize), T> { self.position.column += size; self.off += size; Ok((kind, size)) } fn take_token(&mut self) -> Result<(Kind, usize), Error, Token<'a>>> { use self::Kind::*; let mut iter = self.buf[self.off..].char_indices(); let cur_char = match iter.next() { Some((_, x)) => x, None => return Err(Error::end_of_input()), }; match cur_char { '(' | '[' | '{' => { // Check for recursion limit self.recursion_limit = self .recursion_limit .checked_sub(1) .ok_or_else(|| Error::message_static_message("Recursion limit exceeded"))?; self.advance_token(Punctuator, 1) } ')' | ']' | '}' => { // Notes on exceptional cases: // recursion_limit may exceed the original value specified // when constructing the Tokenizer. It may at first // seem like this would be a good place to handle that, // but instead this code allows this token to propagate up // to the parser which is better equipped to make specific // error messages about unmatched pairs. // The case where recursion limit would overflow but instead // saturates is just a specific case of the more general // occurrence above. self.recursion_limit = self.recursion_limit.saturating_add(1); self.advance_token(Punctuator, 1) } '!' | '$' | ':' | '=' | '@' | '|' | '&' => self.advance_token(Punctuator, 1), '.' => { if iter.as_str().starts_with("..") { self.advance_token(Punctuator, 3) } else { Err(Error::Unexpected(Info::Owned( format_args!( "bare dot {:?} is not supported, \ only \"...\"", cur_char ) .to_string(), ))) } } '_' | 'a'..='z' | 'A'..='Z' => { for (idx, cur_char) in iter.by_ref() { match cur_char { '_' | 'a'..='z' | 'A'..='Z' | '0'..='9' => continue, _ => return self.advance_token(Name, idx), } } let len = self.buf.len() - self.off; self.position.column += len; self.off += len; Ok((Name, len)) } '-' | '0'..='9' => { let mut exponent = None; let mut real = None; let len = loop { let (idx, cur_char) = match iter.next() { Some(pair) => pair, None => break self.buf.len() - self.off, }; match cur_char { // just scan for now, will validate later on ' ' | '\n' | '\r' | '\t' | ',' | '#' | '!' | '$' | ':' | '=' | '@' | '|' | '&' | '(' | ')' | '[' | ']' | '{' | '}' => break idx, '.' => real = Some(idx), 'e' | 'E' => exponent = Some(idx), _ => {} } }; if exponent.is_some() || real.is_some() { let value = &self.buf[self.off..][..len]; if !check_float(value, exponent, real) { return Err(Error::Unexpected(Info::Owned( format_args!("unsupported float {:?}", value).to_string(), ))); } self.position.column += len; self.off += len; Ok((FloatValue, len)) } else { let value = &self.buf[self.off..][..len]; if !check_int(value) { return Err(Error::Unexpected(Info::Owned( format_args!("unsupported integer {:?}", value).to_string(), ))); } self.advance_token(IntValue, len) } } '"' => { if iter.as_str().starts_with("\"\"") { let tail = &iter.as_str()[2..]; for (end_idx, _) in tail.match_indices("\"\"\"") { if !tail[..end_idx].ends_with('\\') { self.update_position(end_idx + 6); return Ok((BlockString, end_idx + 6)); } } Err(Error::Unexpected(Info::Owned( "unterminated block string value".to_string(), ))) } else { let mut nchars = 1; let mut escaped = false; for (idx, cur_char) in iter { nchars += 1; match cur_char { '"' if escaped => {} '"' => { self.position.column += nchars; self.off += idx + 1; return Ok((StringValue, idx + 1)); } '\n' => { return Err(Error::Unexpected(Info::Owned( "unterminated string value".to_string(), ))); } _ => {} } // if we aren't escaped and the current char is a \, we are now escaped escaped = !escaped && cur_char == '\\'; } Err(Error::Unexpected(Info::Owned( "unterminated string value".to_string(), ))) } } _ => Err(Error::Unexpected(Info::Owned( format_args!("unexpected character {:?}", cur_char).to_string(), ))), } } fn skip_whitespace(&mut self) { let mut iter = self.buf[self.off..].char_indices(); let idx = loop { let (idx, cur_char) = match iter.next() { Some(pair) => pair, None => break self.buf.len() - self.off, }; match cur_char { '\u{feff}' | '\r' => continue, '\t' => self.position.column += 8, '\n' => { self.position.column = 1; self.position.line += 1; } // comma is also entirely ignored in spec ' ' | ',' => { self.position.column += 1; continue; } //comment '#' => { for (_, cur_char) in iter.by_ref() { // TODO(tailhook) ensure SourceCharacter if cur_char == '\r' || cur_char == '\n' { self.position.column = 1; self.position.line += 1; break; } } continue; } _ => break idx, } }; self.off += idx; } fn update_position(&mut self, len: usize) { let val = &self.buf[self.off..][..len]; self.off += len; let lines = val.as_bytes().iter().filter(|&&x| x == b'\n').count(); self.position.line += lines; if lines > 0 { let line_offset = val.rfind('\n').unwrap() + 1; let num = val[line_offset..].chars().count(); self.position.column = num + 1; } else { let num = val.chars().count(); self.position.column += num; } } } impl<'a> fmt::Display for Token<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}[{:?}]", self.value, self.kind) } } #[cfg(test)] mod test { use super::Kind::*; use super::{Kind, TokenStream}; use combine::easy::Error; use combine::{Positioned, StreamOnce}; fn tok_str(s: &str) -> Vec<&str> { let mut r = Vec::new(); let mut s = TokenStream::new(s); loop { match s.uncons() { Ok(x) => r.push(x.value), Err(ref e) if e == &Error::end_of_input() => break, Err(e) => panic!("Parse error at {}: {}", s.position(), e), } } r } fn tok_typ(s: &str) -> Vec { let mut r = Vec::new(); let mut s = TokenStream::new(s); loop { match s.uncons() { Ok(x) => r.push(x.kind), Err(ref e) if e == &Error::end_of_input() => break, Err(e) => panic!("Parse error at {}: {}", s.position(), e), } } r } #[test] fn comments_and_commas() { assert_eq!(tok_str("# hello { world }"), &[] as &[&str]); assert_eq!(tok_str("# x\n,,,"), &[] as &[&str]); assert_eq!(tok_str(", ,, ,,, # x"), &[] as &[&str]); } #[test] fn simple() { assert_eq!(tok_str("a { b }"), ["a", "{", "b", "}"]); assert_eq!(tok_typ("a { b }"), [Name, Punctuator, Name, Punctuator]); } #[test] fn query() { assert_eq!( tok_str( "query Query { object { field } }" ), ["query", "Query", "{", "object", "{", "field", "}", "}"] ); } #[test] fn fragment() { assert_eq!(tok_str("a { ...b }"), ["a", "{", "...", "b", "}"]); } #[test] fn int() { assert_eq!(tok_str("0"), ["0"]); assert_eq!(tok_str("0,"), ["0"]); assert_eq!(tok_str("0# x"), ["0"]); assert_eq!(tok_typ("0"), [IntValue]); assert_eq!(tok_str("-0"), ["-0"]); assert_eq!(tok_typ("-0"), [IntValue]); assert_eq!(tok_str("-1"), ["-1"]); assert_eq!(tok_typ("-1"), [IntValue]); assert_eq!(tok_str("-132"), ["-132"]); assert_eq!(tok_typ("-132"), [IntValue]); assert_eq!(tok_str("132"), ["132"]); assert_eq!(tok_typ("132"), [IntValue]); assert_eq!( tok_str("a(x: 10) { b }"), ["a", "(", "x", ":", "10", ")", "{", "b", "}"] ); assert_eq!( tok_typ("a(x: 10) { b }"), [ Name, Punctuator, Name, Punctuator, IntValue, Punctuator, Punctuator, Name, Punctuator ] ); } // TODO(tailhook) fix errors in parser and check error message #[test] #[should_panic] fn zero_int() { tok_str("01"); } #[test] #[should_panic] fn zero_int4() { tok_str("00001"); } #[test] #[should_panic] fn minus_int() { tok_str("-"); } #[test] #[should_panic] fn minus_zero_int() { tok_str("-01"); } #[test] #[should_panic] fn minus_zero_int4() { tok_str("-00001"); } #[test] #[should_panic] fn letters_int() { tok_str("0bbc"); } #[test] fn float() { assert_eq!(tok_str("0.0"), ["0.0"]); assert_eq!(tok_typ("0.0"), [FloatValue]); assert_eq!(tok_str("-0.0"), ["-0.0"]); assert_eq!(tok_typ("-0.0"), [FloatValue]); assert_eq!(tok_str("-1.0"), ["-1.0"]); assert_eq!(tok_typ("-1.0"), [FloatValue]); assert_eq!(tok_str("-1.023"), ["-1.023"]); assert_eq!(tok_typ("-1.023"), [FloatValue]); assert_eq!(tok_str("-132.0"), ["-132.0"]); assert_eq!(tok_typ("-132.0"), [FloatValue]); assert_eq!(tok_str("132.0"), ["132.0"]); assert_eq!(tok_typ("132.0"), [FloatValue]); assert_eq!(tok_str("0e+0"), ["0e+0"]); assert_eq!(tok_typ("0e+0"), [FloatValue]); assert_eq!(tok_str("0.0e+0"), ["0.0e+0"]); assert_eq!(tok_typ("0.0e+0"), [FloatValue]); assert_eq!(tok_str("-0e+0"), ["-0e+0"]); assert_eq!(tok_typ("-0e+0"), [FloatValue]); assert_eq!(tok_str("-1e+0"), ["-1e+0"]); assert_eq!(tok_typ("-1e+0"), [FloatValue]); assert_eq!(tok_str("-132e+0"), ["-132e+0"]); assert_eq!(tok_typ("-132e+0"), [FloatValue]); assert_eq!(tok_str("132e+0"), ["132e+0"]); assert_eq!(tok_typ("132e+0"), [FloatValue]); assert_eq!( tok_str("a(x: 10.0) { b }"), ["a", "(", "x", ":", "10.0", ")", "{", "b", "}"] ); assert_eq!( tok_typ("a(x: 10.0) { b }"), [ Name, Punctuator, Name, Punctuator, FloatValue, Punctuator, Punctuator, Name, Punctuator ] ); assert_eq!(tok_str("1.23e4"), ["1.23e4"]); assert_eq!(tok_typ("1.23e4"), [FloatValue]); } // TODO(tailhook) fix errors in parser and check error message #[test] #[should_panic] fn no_int_float() { tok_str(".0"); } #[test] #[should_panic] fn no_int_float1() { tok_str(".1"); } #[test] #[should_panic] fn zero_float() { tok_str("01.0"); } #[test] #[should_panic] fn zero_float4() { tok_str("00001.0"); } #[test] #[should_panic] fn minus_float() { tok_str("-.0"); } #[test] #[should_panic] fn minus_zero_float() { tok_str("-01.0"); } #[test] #[should_panic] fn minus_zero_float4() { tok_str("-00001.0"); } #[test] #[should_panic] fn letters_float() { tok_str("0bbc.0"); } #[test] #[should_panic] fn letters_float2() { tok_str("0.bbc"); } #[test] #[should_panic] fn letters_float3() { tok_str("0.bbce0"); } #[test] #[should_panic] fn no_exp_sign_float() { tok_str("0e0"); } #[test] #[should_panic] fn unterminated_string() { tok_str(r#""hello\""#); } #[test] #[should_panic] fn extra_unterminated_string() { tok_str(r#""hello\\\""#); } #[test] fn string() { assert_eq!(tok_str(r#""""#), [r#""""#]); assert_eq!(tok_typ(r#""""#), [StringValue]); assert_eq!(tok_str(r#""hello""#), [r#""hello""#]); assert_eq!(tok_str(r#""hello\\""#), [r#""hello\\""#]); assert_eq!(tok_str(r#""hello\\\\""#), [r#""hello\\\\""#]); assert_eq!(tok_str(r#""he\\llo""#), [r#""he\\llo""#]); assert_eq!(tok_typ(r#""hello""#), [StringValue]); assert_eq!(tok_str(r#""my\"quote""#), [r#""my\"quote""#]); assert_eq!(tok_typ(r#""my\"quote""#), [StringValue]); } #[test] fn block_string() { assert_eq!(tok_str(r#""""""""#), [r#""""""""#]); assert_eq!(tok_typ(r#""""""""#), [BlockString]); assert_eq!(tok_str(r#""""hello""""#), [r#""""hello""""#]); assert_eq!(tok_typ(r#""""hello""""#), [BlockString]); assert_eq!(tok_str(r#""""my "quote" """"#), [r#""""my "quote" """"#]); assert_eq!(tok_typ(r#""""my "quote" """"#), [BlockString]); assert_eq!(tok_str(r#""""\"""quote" """"#), [r#""""\"""quote" """"#]); assert_eq!(tok_typ(r#""""\"""quote" """"#), [BlockString]); } } graphql-parser-0.4.1/tests/queries/directive_args.graphql000064400000000000000000000001001046102023000217360ustar 00000000000000query { node @dir(a: 1, b: "2", c: true, d: false, e: null) } graphql-parser-0.4.1/tests/queries/directive_args_multiline.graphql000064400000000000000000000001301046102023000240230ustar 00000000000000query { node @dir( a: 1, b: "2", c: true, d: false, e: null ) } graphql-parser-0.4.1/tests/queries/fragment.graphql000064400000000000000000000000431046102023000205550ustar 00000000000000fragment frag on Friend { node } graphql-parser-0.4.1/tests/queries/fragment_spread.graphql000064400000000000000000000000571046102023000221200ustar 00000000000000query { node { id ...something } } graphql-parser-0.4.1/tests/queries/inline_fragment.graphql000064400000000000000000000001011046102023000221060ustar 00000000000000query { node { id ... on User { name } } } graphql-parser-0.4.1/tests/queries/inline_fragment_dir.graphql000064400000000000000000000001101046102023000227440ustar 00000000000000query { node { id ... on User @defer { name } } } graphql-parser-0.4.1/tests/queries/kitchen-sink.graphql000064400000000000000000000017741046102023000213550ustar 00000000000000# Copyright (c) 2015-present, Facebook, Inc. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id , ... on User @defer { field2 { id , alias: field1(first:10, after:$foo,) @include(if: $foo) { id, ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation likeStory { like(story: 123) @defer { story { id } } } subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {key: "value", block: """ block string uses \""" """}) } { unnamed(truthy: true, falsey: false, nullish: null), query } graphql-parser-0.4.1/tests/queries/kitchen-sink_canonical.graphql000064400000000000000000000014751046102023000233620ustar 00000000000000query queryName($foo: ComplexType, $site: Site = MOBILE) { whoever123is: node(id: [123, 456]) { id ... on User @defer { field2 { id alias: field1(first: 10, after: $foo) @include(if: $foo) { id ...frag } } } ... @skip(unless: $foo) { id } ... { id } } } mutation likeStory { like(story: 123) @defer { story { id } } } subscription StoryLikeSubscription($input: StoryLikeSubscribeInput) { storyLikeSubscribe(input: $input) { story { likers { count } likeSentence { text } } } } fragment frag on Friend { foo(size: $size, bar: $b, obj: {block: "block string uses \"\"\"", key: "value"}) } { unnamed(truthy: true, falsey: false, nullish: null) query } graphql-parser-0.4.1/tests/queries/minimal.graphql000064400000000000000000000000101046102023000203720ustar 00000000000000{ a } graphql-parser-0.4.1/tests/queries/minimal_mutation.graphql000064400000000000000000000000261046102023000223210ustar 00000000000000mutation { notify } graphql-parser-0.4.1/tests/queries/minimal_query.graphql000064400000000000000000000000211046102023000216210ustar 00000000000000query { node } graphql-parser-0.4.1/tests/queries/mutation_directive.graphql000064400000000000000000000000371046102023000226530ustar 00000000000000mutation @directive { node } graphql-parser-0.4.1/tests/queries/mutation_nameless_vars.graphql000064400000000000000000000001321046102023000235330ustar 00000000000000mutation($first: Int, $second: Int) { field1(first: $first) field2(second: $second) } graphql-parser-0.4.1/tests/queries/named_query.graphql000064400000000000000000000000261046102023000212640ustar 00000000000000query Foo { field } graphql-parser-0.4.1/tests/queries/nested_selection.graphql000064400000000000000000000000361046102023000223030ustar 00000000000000query { node { id } } graphql-parser-0.4.1/tests/queries/query_aliases.graphql000064400000000000000000000000331046102023000216170ustar 00000000000000query { an_alias: node } graphql-parser-0.4.1/tests/queries/query_arguments.graphql000064400000000000000000000000301046102023000222000ustar 00000000000000query { node(id: 1) } graphql-parser-0.4.1/tests/queries/query_arguments_multiline.graphql000064400000000000000000000001021046102023000242620ustar 00000000000000query { node( id: 1 ) node( id: 1, one: 3 ) } graphql-parser-0.4.1/tests/queries/query_array_argument_multiline.graphql000064400000000000000000000001001046102023000252730ustar 00000000000000query { node( id: [ 5, 6, 7 ] ) } graphql-parser-0.4.1/tests/queries/query_directive.graphql000064400000000000000000000000341046102023000221550ustar 00000000000000query @directive { node } graphql-parser-0.4.1/tests/queries/query_list_argument.graphql000064400000000000000000000000521046102023000230540ustar 00000000000000query { node(id: 1, list: [123, 456]) } graphql-parser-0.4.1/tests/queries/query_nameless_vars.graphql000064400000000000000000000001271046102023000230440ustar 00000000000000query($first: Int, $second: Int) { field1(first: $first) field2(second: $second) } graphql-parser-0.4.1/tests/queries/query_nameless_vars_multiple_fields.graphql000064400000000000000000000005141046102023000263050ustar 00000000000000,,,,,,,,,,,,,,,,, query ,,,,,,, ($houseId: String!, $streetNumber: Int!) ,,,,,,,,,,,, { # comment ,,,,,,,,,,,,,,,,,, # commas should be fine house(id: $houseId) { id name lat lng } street(number: $streetNumber) { # this is a comment id } houseStreet(id: $houseId, number: $streetNumber) { id } } graphql-parser-0.4.1/tests/queries/query_nameless_vars_multiple_fields_canonical.graphql000064400000000000000000000003331046102023000303130ustar 00000000000000query($houseId: String!, $streetNumber: Int!) { house(id: $houseId) { id name lat lng } street(number: $streetNumber) { id } houseStreet(id: $houseId, number: $streetNumber) { id } } graphql-parser-0.4.1/tests/queries/query_object_argument.graphql000064400000000000000000000000651046102023000233530ustar 00000000000000query { node(id: 1, obj: {key1: 123, key2: 456}) } graphql-parser-0.4.1/tests/queries/query_object_argument_multiline.graphql000064400000000000000000000001231046102023000254300ustar 00000000000000query { node( id: 1, obj: { key1: 123, key2: 456 } ) } graphql-parser-0.4.1/tests/queries/query_var_default_float.graphql000064400000000000000000000000521046102023000236600ustar 00000000000000query Foo($site: Float = 0.5) { field } graphql-parser-0.4.1/tests/queries/query_var_default_list.graphql000064400000000000000000000000611046102023000235260ustar 00000000000000query Foo($site: [Int] = [123, 456]) { field } graphql-parser-0.4.1/tests/queries/query_var_default_object.graphql000064400000000000000000000000611046102023000240210ustar 00000000000000query Foo($site: Site = {url: null}) { field } graphql-parser-0.4.1/tests/queries/query_var_default_string.graphql000064400000000000000000000000601046102023000240600ustar 00000000000000query Foo($site: String = "string") { field } graphql-parser-0.4.1/tests/queries/query_var_defaults.graphql000064400000000000000000000000541046102023000226600ustar 00000000000000query Foo($site: Site = MOBILE) { field } graphql-parser-0.4.1/tests/queries/query_vars.graphql000064400000000000000000000000461046102023000211550ustar 00000000000000query Foo($arg: SomeType) { field } graphql-parser-0.4.1/tests/queries/string_literal.graphql000064400000000000000000000000361046102023000217760ustar 00000000000000query { node(id: "hello") } graphql-parser-0.4.1/tests/queries/subscription_directive.graphql000064400000000000000000000000431046102023000235340ustar 00000000000000subscription @directive { node } graphql-parser-0.4.1/tests/queries/triple_quoted_literal.graphql000064400000000000000000000000701046102023000233460ustar 00000000000000query { node(id: """ Hello, world! """) } graphql-parser-0.4.1/tests/query_errors/bad_args.txt000064400000000000000000000001741046102023000207660ustar 00000000000000query MyQuery { field1([something]) } --- query parse error: Parse error at 2:10 Unexpected `[[Punctuator]` Expected Name graphql-parser-0.4.1/tests/query_errors/invalid_curly_brace.txt000064400000000000000000000002261046102023000232220ustar 00000000000000querry MyQuery { field1 } --- query parse error: Parse error at 1:1 Unexpected `querry[Name]` Expected {, query, mutation, subscription or fragment graphql-parser-0.4.1/tests/query_errors.rs000064400000000000000000000014171046102023000170320ustar 00000000000000extern crate graphql_parser; #[cfg(test)] #[macro_use] extern crate pretty_assertions; use std::fs::File; use std::io::Read; use graphql_parser::parse_query; fn test_error(filename: &str) { let mut buf = String::with_capacity(1024); let path = format!("tests/query_errors/{}.txt", filename); let mut f = File::open(path).unwrap(); f.read_to_string(&mut buf).unwrap(); let mut iter = buf.splitn(2, "\n---\n"); let graphql = iter.next().unwrap(); let expected = iter.next().expect("file should contain error message"); let err = parse_query::(graphql).unwrap_err(); assert_eq!(err.to_string(), expected); } #[test] fn invalid_curly_brace() { test_error("invalid_curly_brace"); } #[test] fn bad_args() { test_error("bad_args"); } graphql-parser-0.4.1/tests/query_roundtrips.rs000064400000000000000000000075461046102023000177400ustar 00000000000000extern crate graphql_parser; #[cfg(test)] #[macro_use] extern crate pretty_assertions; use std::fs::File; use std::io::Read; use graphql_parser::{parse_query, Style}; fn roundtrip_multiline_args(filename: &str) { roundtrip(filename, Style::default().multiline_arguments(true)) } fn roundtrip_default(filename: &str) { roundtrip(filename, &Style::default()) } fn roundtrip(filename: &str, style: &Style) { let mut buf = String::with_capacity(1024); let path = format!("tests/queries/{}.graphql", filename); let mut f = File::open(path).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_query::(&buf).unwrap().to_owned(); assert_eq!(ast.format(style), buf); } fn roundtrip2(filename: &str) { let mut buf = String::with_capacity(1024); let source = format!("tests/queries/{}.graphql", filename); let target = format!("tests/queries/{}_canonical.graphql", filename); let mut f = File::open(source).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_query::(&buf).unwrap().to_owned(); let mut buf = String::with_capacity(1024); let mut f = File::open(target).unwrap(); f.read_to_string(&mut buf).unwrap(); assert_eq!(ast.to_string(), buf); } #[test] fn minimal() { roundtrip_default("minimal"); } #[test] fn minimal_query() { roundtrip_default("minimal_query"); } #[test] fn named_query() { roundtrip_default("named_query"); } #[test] fn query_vars() { roundtrip_default("query_vars"); } #[test] fn query_nameless_vars() { roundtrip_default("query_nameless_vars"); } #[test] fn query_nameless_vars_multiple_fields() { roundtrip2("query_nameless_vars_multiple_fields"); } #[test] fn query_var_defaults() { roundtrip_default("query_var_defaults"); } #[test] fn query_var_defaults1() { roundtrip_default("query_var_default_string"); } #[test] fn query_var_defaults2() { roundtrip_default("query_var_default_float"); } #[test] fn query_var_defaults3() { roundtrip_default("query_var_default_list"); } #[test] fn query_var_defaults4() { roundtrip_default("query_var_default_object"); } #[test] fn query_aliases() { roundtrip_default("query_aliases"); } #[test] fn query_arguments() { roundtrip_default("query_arguments"); } #[test] fn query_arguments_multiline() { roundtrip_multiline_args("query_arguments_multiline"); } #[test] fn query_directive() { roundtrip_default("query_directive"); } #[test] fn mutation_directive() { roundtrip_default("mutation_directive"); } #[test] fn mutation_nameless_vars() { roundtrip_default("mutation_nameless_vars"); } #[test] fn subscription_directive() { roundtrip_default("subscription_directive"); } #[test] fn string_literal() { roundtrip_default("string_literal"); } #[test] fn triple_quoted_literal() { roundtrip_default("triple_quoted_literal"); } #[test] fn query_list_arg() { roundtrip_default("query_list_argument"); } #[test] fn query_object_arg() { roundtrip_default("query_object_argument"); } #[test] fn query_object_arg_multiline() { roundtrip_multiline_args("query_object_argument_multiline"); } #[test] fn query_array_arg_multiline() { roundtrip_multiline_args("query_array_argument_multiline"); } #[test] fn nested_selection() { roundtrip_default("nested_selection"); } #[test] fn inline_fragment() { roundtrip_default("inline_fragment"); } #[test] fn inline_fragment_dir() { roundtrip_default("inline_fragment_dir"); } #[test] fn fragment_spread() { roundtrip_default("fragment_spread"); } #[test] fn minimal_mutation() { roundtrip_default("minimal_mutation"); } #[test] fn fragment() { roundtrip_default("fragment"); } #[test] fn directive_args() { roundtrip_default("directive_args"); } #[test] fn directive_args_multiline() { roundtrip_multiline_args("directive_args_multiline"); } #[test] fn kitchen_sink() { roundtrip2("kitchen-sink"); } graphql-parser-0.4.1/tests/schema_roundtrips.rs000064400000000000000000000046311046102023000200230ustar 00000000000000extern crate graphql_parser; #[cfg(test)] #[macro_use] extern crate pretty_assertions; use std::fs::File; use std::io::Read; use graphql_parser::parse_schema; fn roundtrip(filename: &str) { let mut buf = String::with_capacity(1024); let path = format!("tests/schemas/{}.graphql", filename); let mut f = File::open(path).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_schema::(&buf).unwrap().to_owned(); assert_eq!(ast.to_string(), buf); } fn roundtrip2(filename: &str) { let mut buf = String::with_capacity(1024); let source = format!("tests/schemas/{}.graphql", filename); let target = format!("tests/schemas/{}_canonical.graphql", filename); let mut f = File::open(source).unwrap(); f.read_to_string(&mut buf).unwrap(); let ast = parse_schema::(&buf).unwrap(); let mut buf = String::with_capacity(1024); let mut f = File::open(target).unwrap(); f.read_to_string(&mut buf).unwrap(); assert_eq!(ast.to_string(), buf); } #[test] fn minimal() { roundtrip("minimal"); } #[test] fn scalar_type() { roundtrip("scalar_type"); } #[test] fn extend_scalar() { roundtrip("extend_scalar"); } #[test] fn minimal_type() { roundtrip("minimal_type"); } #[test] fn implements() { roundtrip("implements"); } #[test] fn implements_amp() { roundtrip2("implements_amp"); } #[test] fn implements_interface() { roundtrip("implements_interface"); } #[test] fn simple_object() { roundtrip("simple_object"); } #[test] fn extend_object() { roundtrip("extend_object"); } #[test] fn interface() { roundtrip("interface"); } #[test] fn extend_interface() { roundtrip("extend_interface"); } #[test] fn union() { roundtrip("union"); } #[test] fn empty_union() { roundtrip("empty_union"); } #[test] fn union_extension() { roundtrip("union_extension"); } #[test] fn enum_type() { roundtrip("enum"); } #[test] fn extend_enum() { roundtrip("extend_enum"); } #[test] fn input_type() { roundtrip("input_type"); } #[test] fn extend_input() { roundtrip2("extend_input"); } #[test] fn directive() { roundtrip("directive"); } #[test] fn kitchen_sink() { roundtrip2("kitchen-sink"); } #[test] fn directive_descriptions() { roundtrip2("directive_descriptions"); } #[test] fn directive_variable_definition() { roundtrip("directive_variable_definition"); } #[test] fn repeatable() { roundtrip("repeatable") } graphql-parser-0.4.1/tests/schemas/directive.graphql000064400000000000000000000001131046102023000206740ustar 00000000000000directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT graphql-parser-0.4.1/tests/schemas/directive_descriptions.graphql000064400000000000000000000006451046102023000234740ustar 00000000000000""" Directs the executor to include this field or fragment only when the `if` argument is true. """ directive @include( """ Included when true. """ if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """ Directs the executor to skip this field or fragment when the `if` argument is true. """ directive @skip( """ Skipped when true. """ if: Boolean! ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT graphql-parser-0.4.1/tests/schemas/directive_descriptions_canonical.graphql000064400000000000000000000006151046102023000255000ustar 00000000000000""" Directs the executor to include this field or fragment only when the `if` argument is true. """ directive @include("Included when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT """ Directs the executor to skip this field or fragment when the `if` argument is true. """ directive @skip("Skipped when true." if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT graphql-parser-0.4.1/tests/schemas/directive_variable_definition.graphql000064400000000000000000000000571046102023000247600ustar 00000000000000directive @configurable on VARIABLE_DEFINITION graphql-parser-0.4.1/tests/schemas/empty_union.graphql000064400000000000000000000000251046102023000212660ustar 00000000000000union UndefinedUnion graphql-parser-0.4.1/tests/schemas/enum.graphql000064400000000000000000000000411046102023000176620ustar 00000000000000enum Site { DESKTOP MOBILE } graphql-parser-0.4.1/tests/schemas/extend_enum.graphql000064400000000000000000000000321046102023000212310ustar 00000000000000extend enum Site { VR } graphql-parser-0.4.1/tests/schemas/extend_input.graphql000064400000000000000000000000631046102023000214300ustar 00000000000000extend input InputType { other: Float = 1.23e4 } graphql-parser-0.4.1/tests/schemas/extend_input_canonical.graphql000064400000000000000000000000621046102023000234360ustar 00000000000000extend input InputType { other: Float = 12300 } graphql-parser-0.4.1/tests/schemas/extend_interface.graphql000064400000000000000000000002231046102023000222270ustar 00000000000000extend interface Bar { two(argument: InputType!): Type } extend interface Foo implements IOne & ITwo { three(argument: [InputType!]!): Type } graphql-parser-0.4.1/tests/schemas/extend_object.graphql000064400000000000000000000002051046102023000215350ustar 00000000000000extend type Foo { seven(argument: [String]): Type } extend type Bar implements IOne & ITwo { five(argument: [String!]!): Type } graphql-parser-0.4.1/tests/schemas/extend_scalar.graphql000064400000000000000000000000451046102023000215360ustar 00000000000000extend scalar CustomScalar @onScalar graphql-parser-0.4.1/tests/schemas/implements.graphql000064400000000000000000000000761046102023000211030ustar 00000000000000type Type1 implements IOne type Type1 implements IOne & ITwo graphql-parser-0.4.1/tests/schemas/implements_amp.graphql000064400000000000000000000001011046102023000217250ustar 00000000000000type Type1 implements & IOne & ITwo type Type2 implements & IOne graphql-parser-0.4.1/tests/schemas/implements_amp_canonical.graphql000064400000000000000000000000761046102023000237470ustar 00000000000000type Type1 implements IOne & ITwo type Type2 implements IOne graphql-parser-0.4.1/tests/schemas/implements_interface.graphql000064400000000000000000000001121046102023000231120ustar 00000000000000interface IOne implements ITwo interface IThree implements IFour & IFive graphql-parser-0.4.1/tests/schemas/input_type.graphql000064400000000000000000000000661046102023000211250ustar 00000000000000input InputType { key: String! answer: Int = 42 } graphql-parser-0.4.1/tests/schemas/interface.graphql000064400000000000000000000000361046102023000206620ustar 00000000000000interface Bar { one: Type } graphql-parser-0.4.1/tests/schemas/kitchen-sink.graphql000064400000000000000000000040371046102023000213160ustar 00000000000000# Copyright (c) 2015-present, Facebook, Inc. # # This source code is licensed under the MIT license found in the # LICENSE file in the root directory of this source tree. schema { query: QueryType mutation: MutationType } """ This is a description of the `Foo` type. """ type Foo implements Bar & Baz { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } type UndefinedType extend type Foo { seven(argument: [String]): Type } extend type Foo @onType interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } interface UndefinedInterface extend interface Bar { two(argument: InputType!): Type } extend interface Bar @onInterface union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B union AnnotatedUnionTwo @onUnion = | A | B union UndefinedUnion extend union Feed = Photo | Video extend union Feed @onUnion scalar CustomScalar scalar AnnotatedScalar @onScalar extend scalar CustomScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } enum UndefinedEnum extend enum Site { VR } extend enum Site @onEnum input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObject { annotatedField: Type @onField } input UndefinedInput extend input InputType { other: Float = 1.23e4 } extend input InputType @onInputObject directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include2(if: Boolean!) on | FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT graphql-parser-0.4.1/tests/schemas/kitchen-sink_canonical.graphql000064400000000000000000000035441046102023000233270ustar 00000000000000schema { query: QueryType mutation: MutationType } """ This is a description of the `Foo` type. """ type Foo implements Bar & Baz { one: Type two(argument: InputType!): Type three(argument: InputType, other: String): Int four(argument: String = "string"): String five(argument: [String] = ["string", "string"]): String six(argument: InputType = {key: "value"}): Type seven(argument: Int = null): Type } type AnnotatedObject @onObject(arg: "value") { annotatedField(arg: Type = "default" @onArg): Type @onField } type UndefinedType extend type Foo { seven(argument: [String]): Type } extend type Foo @onType interface Bar { one: Type four(argument: String = "string"): String } interface AnnotatedInterface @onInterface { annotatedField(arg: Type @onArg): Type @onField } interface UndefinedInterface extend interface Bar { two(argument: InputType!): Type } extend interface Bar @onInterface union Feed = Story | Article | Advert union AnnotatedUnion @onUnion = A | B union AnnotatedUnionTwo @onUnion = A | B union UndefinedUnion extend union Feed = Photo | Video extend union Feed @onUnion scalar CustomScalar scalar AnnotatedScalar @onScalar extend scalar CustomScalar @onScalar enum Site { DESKTOP MOBILE } enum AnnotatedEnum @onEnum { ANNOTATED_VALUE @onEnumValue OTHER_VALUE } enum UndefinedEnum extend enum Site { VR } extend enum Site @onEnum input InputType { key: String! answer: Int = 42 } input AnnotatedInput @onInputObject { annotatedField: Type @onField } input UndefinedInput extend input InputType { other: Float = 12300 } extend input InputType @onInputObject directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT directive @include2(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT graphql-parser-0.4.1/tests/schemas/minimal.graphql000064400000000000000000000000321046102023000203440ustar 00000000000000schema { query: Query } graphql-parser-0.4.1/tests/schemas/minimal_type.graphql000064400000000000000000000000231046102023000214050ustar 00000000000000type UndefinedType graphql-parser-0.4.1/tests/schemas/repeatable.graphql000064400000000000000000000000731046102023000210270ustar 00000000000000directive @filter(expression: String!) repeatable on FIELD graphql-parser-0.4.1/tests/schemas/scalar_type.graphql000064400000000000000000000001131046102023000212240ustar 00000000000000"This is the best scalar type" scalar BestType @perfectness(value: 100500) graphql-parser-0.4.1/tests/schemas/simple_object.graphql000064400000000000000000000000311046102023000215340ustar 00000000000000type Foo { bar: Type } graphql-parser-0.4.1/tests/schemas/union.graphql000064400000000000000000000000461046102023000200530ustar 00000000000000union Feed = Story | Article | Advert graphql-parser-0.4.1/tests/schemas/union_extension.graphql000064400000000000000000000000421046102023000221430ustar 00000000000000extend union Feed = Photo | Video graphql-parser-0.4.1/vagga.yaml000064400000000000000000000027111046102023000145300ustar 00000000000000commands: make: !Command description: Build the library container: ubuntu run: [cargo, build] make-wasm: !Command description: Build wasm library (just to check it's buildable) container: ubuntu run: [cargo, build, --target=wasm32-unknown-unknown] cargo: !Command description: Run arbitrary cargo command symlink-name: cargo container: ubuntu run: [cargo] test: !Command description: Run tests container: ubuntu run: [cargo, test] _bulk: !Command description: Run `bulk` command (for version bookkeeping) container: ubuntu run: [bulk] containers: ubuntu: setup: - !Ubuntu xenial - !Install [ca-certificates, git, build-essential, vim] - !TarInstall url: "https://static.rust-lang.org/dist/rust-1.42.0-x86_64-unknown-linux-gnu.tar.gz" script: "./install.sh --prefix=/usr \ --components=rustc,rust-std-x86_64-unknown-linux-gnu,cargo" - !TarInstall url: "https://static.rust-lang.org/dist/rust-std-1.42.0-wasm32-unknown-unknown.tar.gz" script: "./install.sh --prefix=/usr --components=rust-std-wasm32-unknown-unknown" - &bulk !Tar url: "https://github.com/tailhook/bulk/releases/download/v0.4.10/bulk-v0.4.10.tar.gz" sha256: 481513f8a0306a9857d045497fb5b50b50a51e9ff748909ecf7d2bda1de275ab path: / - !Sh | cargo install cargo-fix --root=/usr environ: HOME: /work/target RUST_BACKTRACE: 1