├── .gitignore ├── .rustfmt.toml ├── .travis.yml ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── build.rs ├── graphql-idl-parser-ffi ├── Cargo.toml ├── README.md ├── example │ └── example.c ├── includes │ └── gql-idl-parser.h ├── src │ ├── lib.rs │ └── types.rs └── test │ ├── clar.c │ ├── clar.h │ ├── clar │ ├── fixtures.h │ ├── fs.h │ ├── print.h │ └── sandbox.h │ ├── clar_test.h │ ├── enums.c │ ├── generate.py │ ├── helpers.c │ ├── helpers.h │ ├── input_objects.c │ ├── interfaces.c │ ├── main.c │ ├── objects.c │ ├── scalars.c │ └── unions.c ├── script ├── compile ├── fmt └── test ├── src ├── gqlidl.lalrpop ├── gqlidl.rs ├── lib.rs ├── macros.rs ├── tests.rs └── type_definition.rs └── test ├── enums.graphql ├── github.graphql ├── input_objects.graphql ├── interfaces.graphql ├── objects.graphql ├── scalars.graphql └── unions.graphql /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | graphql-idl-parser-ffi/example/example 4 | graphql-idl-parser-ffi/test/.clarcache 5 | graphql-idl-parser-ffi/test/clar.suite 6 | graphql-idl-parser-ffi/test/gql-idl-parser-test 7 | graphql-idl-parser-ffi/test/gql-idl-parser-test.dSYM/ 8 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | fn_args_layout = "Block" 2 | array_layout = "Block" 3 | control_style = "Rfc" 4 | where_style = "Rfc" 5 | generics_indent = "Block" 6 | fn_call_style = "Block" 7 | combine_control_expr = true 8 | fn_args_paren_newline = false 9 | max_width = 100 10 | error_on_line_overflow = false 11 | write_mode = "Overwrite" 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | cache: cargo 3 | sudo: false 4 | git: 5 | depth: 10 6 | script: 7 | - cargo test --verbose 8 | - script/test 9 | rust: 10 | - beta 11 | - stable 12 | matrix: 13 | fast_finish: true 14 | allow_failures: 15 | - rust: beta 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-idl-parser" 3 | version = "0.1.1" 4 | authors = ["Garen Torikian "] 5 | build = "build.rs" 6 | description = "A parser for the GraphQL IDL format." 7 | license = "MIT" 8 | repository = "https://github.com/gjtorikian/graphql-idl-parser" 9 | categories = ["text-processing"] 10 | 11 | [build-dependencies] 12 | lalrpop = "0.13.0" 13 | 14 | [dependencies] 15 | lalrpop-util = "0.13.0" 16 | regex = "0.2.1" 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Garen Torikian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-idl-parser 2 | 3 | A parser for GraphQL IDL files! This repository contains the code for both the Rust implementation, 4 | as well as the FFI dylib implementation, which allows you to generate a library for use in C projects 5 | (and other languages that play nice with C). See https://github.com/gjtorikian/graphql-idl-parser-ruby 6 | for a Ruby implementation! 7 | 8 | [![Build Status](https://travis-ci.org/gjtorikian/graphql-idl-parser.svg?branch=master)](https://travis-ci.org/gjtorikian/graphql-idl-parser) 9 | 10 | ## Why? 11 | 12 | Two reasons: 13 | 14 | * I have plenty of experience doing Bison/LALR grammars in C, and I desperately wanted to learn some Rust. 15 | * Most of the GraphQL tooling, I'm sorry to say, appears to be in JavaScript. While that's cool, I 16 | really believe that when it comes to creating a tool ecosystem, base implementations ought to be in 17 | a language that can be consumed by other projects. In other words, the world doesn't need different GraphQL 18 | IDL parsers in Node, and Ruby, and Python, and Java, and Scala. The base format should be in something 19 | like C/C++/Rust, and then those higher level languages ought to make use of that one foundational library. 20 | This ensures consistency and correctness, no matter what your backend is written in. 21 | 22 | ## Usage 23 | 24 | Add this to your `Cargo.toml`: 25 | 26 | ``` toml 27 | [dependencies] 28 | graphql-idl-parser = "^0.1" 29 | ``` 30 | 31 | and this to your crate root: 32 | 33 | ``` rust 34 | extern crate graphql_idl_parser; 35 | ``` 36 | 37 | After that, simply feed in a GraphQL IDL string to `gqlidl::parse_schema`, like: 38 | 39 | ``` rust 40 | let definitions = gqlidl::parse_schema(contents.as_str()).unwrap(); 41 | for def in definitions { 42 | // ... 43 | } 44 | ``` 45 | 46 | Note that this library does _not_ validate the format if your IDL. If you have multiple types with 47 | the same name, for example, they will be happily consumed as unique types. However, the parser will 48 | become irate if it comes across a malformed or unknown token. 49 | 50 | ## Todo: 51 | 52 | - [ ] More documentation? 53 | - [ ] Support more of the IDL besides just types 54 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main() { 4 | lalrpop::process_root().unwrap(); 5 | } 6 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphql-idl-parser-ffi" 3 | version = "0.1.3" 4 | repository = "https://github.com/gjtorikian/graphql-idl-parser" 5 | license = "MIT" 6 | authors = ["Garen Torikian "] 7 | description = "An FFI interface for the GraphQL IDL format parser." 8 | 9 | [lib] 10 | name = "graphqlidlparser" 11 | crate_type = ["dylib"] 12 | 13 | [dependencies] 14 | graphql-idl-parser = "0.1.1" 15 | libc = "^0.2" 16 | 17 | [workspace] 18 | exclude = ["example"] 19 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/README.md: -------------------------------------------------------------------------------- 1 | # C API for graphql-idl-parser 2 | 3 | This is an FFI binding to the `graphql-idl-parser` crate, which allows you to use 4 | that crate in your C project. 5 | 6 | The header file (`includes/gql-idl-parser.h`) serves as the primary API documentation of 7 | this library. T 8 | 9 | ## Usage 10 | 11 | There are examples on how to use this in C in the `example` and `test` sub-directories. 12 | 13 | Simply running `cargo build --release` should generate the `.so` or `.dylib` for you. 14 | 15 | Assuming you have a C compiler, then this should work to run the example: 16 | 17 | ``` console 18 | $ git clone git://github.com/gjtorikian/graphql-idl-parser 19 | $ ./compile 20 | ``` 21 | 22 | Here's a general usage example: 23 | 24 | ``` c 25 | #include "gql-idl-parser.h" 26 | 27 | int main() 28 | { 29 | GraphQLTypes* types = NULL; 30 | size_t types_len = 0; 31 | uint8_t err; 32 | 33 | err = gqlidl_parse_schema("# Yeah yeah\nscalar DateTime\ntype OMG {}", &types, &types_len); 34 | 35 | if (err > 0) { 36 | printf("Error: Return code %d", err); 37 | exit(err); 38 | } 39 | 40 | for (size_t i = 0; i < types_len; i++) { 41 | // ... 42 | } 43 | } 44 | ``` 45 | 46 | ## TODO: 47 | 48 | - [ ] Run Valgrind 49 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/example/example.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "gql-idl-parser.h" 6 | 7 | int main() 8 | { 9 | GraphQLTypes* types = NULL; 10 | size_t types_len = 0; 11 | uint8_t err; 12 | 13 | err = gqlidl_parse_schema("# Yeah yeah\nscalar DateTime\ntype OMG {}", &types, &types_len); 14 | 15 | if (err > 0) { 16 | printf("Error: Return code %d", err); 17 | exit(err); 18 | } 19 | 20 | for (size_t i = 0; i < types_len; i++) { 21 | printf("typename: %s\n", types[i].typename); 22 | printf("desc: %s\n", types[i].scalar_type.description); 23 | printf("name: %s\n", types[i].scalar_type.name); 24 | if (strncmp(types[i].typename, "scalar", 6) == 0) { 25 | printf("a scalar!"); 26 | } 27 | if (strncmp(types[i].typename, "object", 6) == 0) { 28 | printf("an object!"); 29 | } 30 | } 31 | 32 | return err; 33 | } 34 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/includes/gql-idl-parser.h: -------------------------------------------------------------------------------- 1 | #ifndef _GQL_IDL_PARSER_H 2 | #define _GQL_IDL_PARSER_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | typedef struct array_of_strings { 12 | size_t length; 13 | const char** data; 14 | } array_and_size; 15 | 16 | typedef struct FieldType { 17 | const char* name; 18 | const char* info; 19 | } FieldType; 20 | 21 | typedef struct GraphQLDirectiveArgument { 22 | const char* name; 23 | const char* value; 24 | } GraphQLDirectiveArgument; 25 | 26 | typedef struct array_of_directive_arguments { 27 | size_t length; 28 | const GraphQLDirectiveArgument* data; 29 | } array_of_directive_arguments; 30 | 31 | typedef struct GraphQLDirective { 32 | const char* name; 33 | struct array_of_directive_arguments arguments; 34 | } GraphQLDirective; 35 | 36 | typedef struct array_of_directives { 37 | size_t length; 38 | const GraphQLDirective* data; 39 | } array_of_directives; 40 | 41 | typedef struct GraphQLArgument { 42 | const char* description; 43 | const char* name; 44 | FieldType type_info; 45 | const char* default_value; 46 | struct array_of_directives directives; 47 | } GraphQLArgument; 48 | 49 | typedef struct array_of_arguments { 50 | size_t length; 51 | const GraphQLArgument* data; 52 | } array_of_arguments; 53 | 54 | typedef struct GraphQLField { 55 | const char* description; 56 | const char* name; 57 | FieldType type_info; 58 | struct array_of_arguments arguments; 59 | struct array_of_directives directives; 60 | } GraphQLField; 61 | 62 | typedef struct array_of_fields { 63 | size_t length; 64 | const GraphQLField* data; 65 | } array_of_fields; 66 | 67 | typedef struct GraphQLValue { 68 | const char* description; 69 | const char* name; 70 | struct array_of_directives directives; 71 | } GraphQLValue; 72 | 73 | typedef struct array_of_values { 74 | size_t length; 75 | const GraphQLValue* data; 76 | } array_of_values; 77 | 78 | typedef struct GraphQLScalar { 79 | const char* description; 80 | const char* name; 81 | } GraphQLScalar; 82 | 83 | typedef struct GraphQLObject { 84 | const char* description; 85 | const char* name; 86 | struct array_of_strings implements; 87 | struct array_of_directives directives; 88 | struct array_of_fields fields; 89 | } GraphQLObject; 90 | 91 | typedef struct GraphQLEnum { 92 | const char* description; 93 | const char* name; 94 | struct array_of_directives directives; 95 | struct array_of_values values; 96 | } GraphQLEnum; 97 | 98 | typedef struct GraphQLInterface { 99 | const char* description; 100 | const char* name; 101 | struct array_of_directives directives; 102 | struct array_of_fields fields; 103 | } GraphQLInterface; 104 | 105 | typedef struct GraphQLUnion { 106 | const char* description; 107 | const char* name; 108 | struct array_of_directives directives; 109 | struct array_of_strings values; 110 | } GraphQLUnion; 111 | 112 | typedef struct GraphQLInputObject { 113 | const char* description; 114 | const char* name; 115 | struct array_of_directives directives; 116 | struct array_of_fields fields; 117 | } GraphQLInputObject; 118 | 119 | typedef struct GraphQLTypes { 120 | const char* typename; 121 | union { 122 | GraphQLScalar scalar_type; 123 | GraphQLObject object_type; 124 | GraphQLEnum enum_type; 125 | GraphQLInterface interface_type; 126 | GraphQLUnion union_type; 127 | GraphQLInputObject input_object_type; 128 | }; 129 | } GraphQLTypes; 130 | 131 | /* This is the actual method exposed by Rust FFI */ 132 | uint8_t gqlidl_parse_schema(const char* schema, GraphQLTypes** types, size_t* types_len); 133 | 134 | #ifdef __cplusplus 135 | } 136 | #endif 137 | 138 | #endif 139 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | extern crate graphql_idl_parser; 3 | 4 | mod types; 5 | 6 | use types::*; 7 | 8 | use libc::{c_char, size_t}; 9 | use std::ffi::CStr; 10 | use std::mem; 11 | use std::io::{self, Write}; 12 | 13 | #[repr(C)] 14 | pub union GraphQLType { 15 | scalar: Scalar, 16 | object: Object, 17 | enum_type: Enum, 18 | interface: Interface, 19 | union: Union, 20 | input_object: InputObject, 21 | } 22 | 23 | #[no_mangle] 24 | #[allow(unused)] 25 | pub extern "C" fn gqlidl_parse_schema( 26 | schema: *const c_char, 27 | types: *mut *mut GraphQLType, 28 | types_len: *mut size_t, 29 | ) -> u8 { 30 | // Convert C string to Rust string 31 | let c_schema = unsafe { 32 | assert!(!schema.is_null()); 33 | CStr::from_ptr(schema) 34 | }; 35 | let r_schema = c_schema.to_str().unwrap(); 36 | 37 | match graphql_idl_parser::gqlidl::parse_schema(r_schema) { 38 | Ok(vec) => { 39 | let mut tmp_vec: Vec = vec.into_iter() 40 | .map(|mut v| { 41 | let s = match v.typename() { 42 | "scalar" => { 43 | let d = Scalar::new(v.typename(), v.description(), v.name()); 44 | return GraphQLType { scalar: d }; 45 | } 46 | "object" => { 47 | let d = Object::new( 48 | v.typename(), 49 | v.description(), 50 | v.name(), 51 | v.implements(), 52 | v.directives(), 53 | v.fields(), 54 | ); 55 | return GraphQLType { object: d }; 56 | } 57 | "enum" => { 58 | let d = Enum::new( 59 | v.typename(), 60 | v.description(), 61 | v.name(), 62 | v.directives(), 63 | v.values(), 64 | ); 65 | return GraphQLType { enum_type: d }; 66 | } 67 | "interface" => { 68 | let d = Interface::new( 69 | v.typename(), 70 | v.description(), 71 | v.name(), 72 | v.directives(), 73 | v.fields(), 74 | ); 75 | return GraphQLType { interface: d }; 76 | } 77 | "union" => { 78 | let d = Union::new( 79 | v.typename(), 80 | v.description(), 81 | v.name(), 82 | v.directives(), 83 | v.types(), 84 | ); 85 | return GraphQLType { union: d }; 86 | } 87 | "input_object" => { 88 | let d = InputObject::new( 89 | v.typename(), 90 | v.description(), 91 | v.name(), 92 | v.directives(), 93 | v.fields(), 94 | ); 95 | return GraphQLType { input_object: d }; 96 | } 97 | _ => panic!("Unknown typename: {}", v.typename()), 98 | }; 99 | 100 | mem::forget(v); 101 | s 102 | }) 103 | .collect(); 104 | 105 | tmp_vec.shrink_to_fit(); 106 | assert!(tmp_vec.len() == tmp_vec.capacity()); 107 | 108 | // Return number of types 109 | unsafe { 110 | *types_len = tmp_vec.len() as size_t; 111 | } 112 | 113 | // Return pointer to data 114 | unsafe { 115 | *types = tmp_vec.as_mut_ptr(); 116 | } 117 | 118 | // Prevent memory from being deallocated 119 | mem::forget(tmp_vec); 120 | 121 | 0 122 | } 123 | Err(err) => { 124 | writeln!(io::stderr(), "Catastrophic error: {:?}", err).unwrap(); 125 | 1 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/src/types.rs: -------------------------------------------------------------------------------- 1 | extern crate graphql_idl_parser; 2 | 3 | use graphql_idl_parser::type_definition::{GraphQLDirective, GraphQLField, GraphQLValue}; 4 | 5 | use libc::{c_char, c_void, size_t}; 6 | use std::ffi::CString; 7 | use std::{mem, ptr}; 8 | 9 | fn convert_optional_string_to_cstr(string: Option<&str>) -> *const c_char { 10 | match string { 11 | Some(string) => CString::new(string).unwrap().into_raw(), 12 | None => ptr::null(), 13 | } 14 | } 15 | 16 | #[derive(Copy)] 17 | #[repr(C)] 18 | pub struct Scalar { 19 | pub typename: *const c_char, 20 | pub description: *const c_char, 21 | pub name: *const c_char, 22 | } 23 | 24 | impl Clone for Scalar { 25 | fn clone(&self) -> Scalar { 26 | *self 27 | } 28 | } 29 | 30 | impl Scalar { 31 | pub fn new(typename: &str, description: Option<&str>, name: &str) -> Scalar { 32 | Scalar { 33 | typename: CString::new(typename).unwrap().into_raw(), 34 | description: convert_optional_string_to_cstr(description), 35 | name: CString::new(name).unwrap().into_raw(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Copy)] 41 | #[repr(C)] 42 | pub struct Object { 43 | typename: *const c_char, 44 | description: *const c_char, 45 | name: *const c_char, 46 | implements: ArrayOfCStrings, 47 | directives: ArrayOfDirectives, 48 | fields: ArrayOfFields, 49 | } 50 | 51 | impl Clone for Object { 52 | fn clone(&self) -> Object { 53 | *self 54 | } 55 | } 56 | 57 | impl Object { 58 | pub fn new( 59 | typename: &str, 60 | description: Option<&str>, 61 | name: &str, 62 | implements: Option>, 63 | directives: Option>, 64 | fields: Option>, 65 | ) -> Object { 66 | Object { 67 | typename: CString::new(typename).unwrap().into_raw(), 68 | description: convert_optional_string_to_cstr(description), 69 | name: CString::new(name).unwrap().into_raw(), 70 | implements: match implements { 71 | None => ArrayOfCStrings::from_vec(vec![]), 72 | Some(implements) => ArrayOfCStrings::from_vec(implements), 73 | }, 74 | directives: match directives { 75 | None => ArrayOfDirectives::from_vec(vec![]), 76 | Some(directives) => ArrayOfDirectives::from_vec(directives), 77 | }, 78 | fields: match fields { 79 | None => ArrayOfFields::from_vec(vec![]), 80 | Some(fields) => ArrayOfFields::from_vec(fields), 81 | }, 82 | } 83 | } 84 | } 85 | 86 | 87 | #[derive(Copy)] 88 | #[repr(C)] 89 | pub struct Enum { 90 | typename: *const c_char, 91 | description: *const c_char, 92 | name: *const c_char, 93 | directives: ArrayOfDirectives, 94 | values: ArrayOfValues, 95 | } 96 | 97 | impl Clone for Enum { 98 | fn clone(&self) -> Enum { 99 | *self 100 | } 101 | } 102 | 103 | impl Enum { 104 | pub fn new( 105 | typename: &str, 106 | description: Option<&str>, 107 | name: &str, 108 | directives: Option>, 109 | values: Option>, 110 | ) -> Enum { 111 | Enum { 112 | typename: CString::new(typename).unwrap().into_raw(), 113 | description: convert_optional_string_to_cstr(description), 114 | name: CString::new(name).unwrap().into_raw(), 115 | directives: match directives { 116 | None => ArrayOfDirectives::from_vec(vec![]), 117 | Some(directives) => ArrayOfDirectives::from_vec(directives), 118 | }, 119 | values: match values { 120 | None => ArrayOfValues::from_vec(vec![]), 121 | Some(values) => ArrayOfValues::from_vec(values), 122 | }, 123 | } 124 | } 125 | } 126 | 127 | #[derive(Copy)] 128 | #[repr(C)] 129 | pub struct Interface { 130 | typename: *const c_char, 131 | description: *const c_char, 132 | name: *const c_char, 133 | directives: ArrayOfDirectives, 134 | fields: ArrayOfFields, 135 | } 136 | 137 | impl Clone for Interface { 138 | fn clone(&self) -> Interface { 139 | *self 140 | } 141 | } 142 | 143 | impl Interface { 144 | pub fn new( 145 | typename: &str, 146 | description: Option<&str>, 147 | name: &str, 148 | directives: Option>, 149 | fields: Option>, 150 | ) -> Interface { 151 | Interface { 152 | typename: CString::new(typename).unwrap().into_raw(), 153 | description: convert_optional_string_to_cstr(description), 154 | name: CString::new(name).unwrap().into_raw(), 155 | directives: match directives { 156 | None => ArrayOfDirectives::from_vec(vec![]), 157 | Some(directives) => ArrayOfDirectives::from_vec(directives), 158 | }, 159 | fields: match fields { 160 | None => ArrayOfFields::from_vec(vec![]), 161 | Some(fields) => ArrayOfFields::from_vec(fields), 162 | }, 163 | } 164 | } 165 | } 166 | 167 | #[derive(Copy)] 168 | #[repr(C)] 169 | pub struct Union { 170 | typename: *const c_char, 171 | description: *const c_char, 172 | name: *const c_char, 173 | directives: ArrayOfDirectives, 174 | types: ArrayOfCStrings, 175 | } 176 | 177 | impl Clone for Union { 178 | fn clone(&self) -> Union { 179 | *self 180 | } 181 | } 182 | 183 | impl Union { 184 | pub fn new( 185 | typename: &str, 186 | description: Option<&str>, 187 | name: &str, 188 | directives: Option>, 189 | types: Option>, 190 | ) -> Union { 191 | Union { 192 | typename: CString::new(typename).unwrap().into_raw(), 193 | description: convert_optional_string_to_cstr(description), 194 | name: CString::new(name).unwrap().into_raw(), 195 | directives: match directives { 196 | None => ArrayOfDirectives::from_vec(vec![]), 197 | Some(directives) => ArrayOfDirectives::from_vec(directives), 198 | }, 199 | types: match types { 200 | None => ArrayOfCStrings::from_vec(vec![]), 201 | Some(types) => ArrayOfCStrings::from_vec(types), 202 | }, 203 | } 204 | } 205 | } 206 | 207 | #[derive(Copy)] 208 | #[repr(C)] 209 | pub struct InputObject { 210 | typename: *const c_char, 211 | description: *const c_char, 212 | name: *const c_char, 213 | directives: ArrayOfDirectives, 214 | fields: ArrayOfFields, 215 | } 216 | 217 | impl Clone for InputObject { 218 | fn clone(&self) -> InputObject { 219 | *self 220 | } 221 | } 222 | 223 | impl InputObject { 224 | pub fn new( 225 | typename: &str, 226 | description: Option<&str>, 227 | name: &str, 228 | directives: Option>, 229 | fields: Option>, 230 | ) -> InputObject { 231 | InputObject { 232 | typename: CString::new(typename).unwrap().into_raw(), 233 | description: convert_optional_string_to_cstr(description), 234 | name: CString::new(name).unwrap().into_raw(), 235 | directives: match directives { 236 | None => ArrayOfDirectives::from_vec(vec![]), 237 | Some(directives) => ArrayOfDirectives::from_vec(directives), 238 | }, 239 | fields: match fields { 240 | None => ArrayOfFields::from_vec(vec![]), 241 | Some(fields) => ArrayOfFields::from_vec(fields), 242 | }, 243 | } 244 | } 245 | } 246 | 247 | #[repr(C)] 248 | #[derive(Copy, Clone)] 249 | struct FieldType { 250 | name: *const c_char, 251 | type_info: *const c_char, 252 | } 253 | 254 | #[repr(C)] 255 | #[derive(Copy, Clone)] 256 | struct Argument { 257 | description: *const c_char, 258 | name: *const c_char, 259 | type_info: FieldType, 260 | default_value: *const c_char, 261 | directives: ArrayOfDirectives, 262 | } 263 | 264 | #[repr(C)] 265 | #[derive(Copy, Clone)] 266 | struct ArrayOfArguments { 267 | length: size_t, 268 | values: *const *const c_void, 269 | } 270 | 271 | #[repr(C)] 272 | #[derive(Copy, Clone)] 273 | struct Field { 274 | description: *const c_char, 275 | name: *const c_char, 276 | type_info: FieldType, 277 | arguments: ArrayOfArguments, 278 | directives: ArrayOfDirectives, 279 | } 280 | 281 | #[repr(C)] 282 | #[derive(Copy, Clone)] 283 | struct ArrayOfFields { 284 | length: size_t, 285 | values: *const *const c_void, 286 | } 287 | 288 | impl ArrayOfFields { 289 | fn from_vec(vec: Vec) -> ArrayOfFields { 290 | let mut field_vec: Vec = vec![]; 291 | 292 | for v in vec { 293 | let mut argument_vec: Vec = vec![]; 294 | match v.arguments() { 295 | None => {} 296 | Some(arguments) => for arg in arguments { 297 | argument_vec.push(Argument { 298 | name: CString::new(arg.name()).unwrap().into_raw(), 299 | description: convert_optional_string_to_cstr(arg.description()), 300 | type_info: FieldType { 301 | name: CString::new(arg.typeinfo().name()).unwrap().into_raw(), 302 | type_info: CString::new(arg.typeinfo().info()).unwrap().into_raw(), 303 | }, 304 | default_value: convert_optional_string_to_cstr(arg.default()), 305 | directives: match v.directives() { 306 | None => ArrayOfDirectives::from_vec(vec![]), 307 | Some(directives) => ArrayOfDirectives::from_vec(directives), 308 | }, 309 | }); 310 | }, 311 | } 312 | 313 | argument_vec.shrink_to_fit(); 314 | 315 | let arguments = ArrayOfArguments { 316 | length: argument_vec.len() as size_t, 317 | values: argument_vec.as_ptr() as *const *const c_void, 318 | }; 319 | 320 | field_vec.push(Field { 321 | description: convert_optional_string_to_cstr(v.description()), 322 | name: CString::new(v.name()).unwrap().into_raw(), 323 | type_info: FieldType { 324 | name: CString::new(v.typeinfo().name()).unwrap().into_raw(), 325 | type_info: CString::new(v.typeinfo().info()).unwrap().into_raw(), 326 | }, 327 | arguments: arguments, 328 | directives: match v.directives() { 329 | None => ArrayOfDirectives::from_vec(vec![]), 330 | Some(directives) => ArrayOfDirectives::from_vec(directives), 331 | }, 332 | }); 333 | 334 | mem::forget(argument_vec); 335 | } 336 | field_vec.shrink_to_fit(); 337 | 338 | let array = ArrayOfFields { 339 | length: field_vec.len() as size_t, 340 | values: field_vec.as_ptr() as *const *const c_void, 341 | }; 342 | 343 | mem::forget(field_vec); 344 | array 345 | } 346 | } 347 | 348 | #[repr(C)] 349 | #[derive(Copy, Clone)] 350 | struct ArrayOfCStrings { 351 | length: size_t, 352 | values: *const *const c_char, 353 | } 354 | 355 | impl ArrayOfCStrings { 356 | fn from_vec(vec: Vec) -> ArrayOfCStrings { 357 | let mut cstr_vec: Vec<*const c_char> = vec![]; 358 | for s in vec { 359 | let cstr = CString::new(s).unwrap(); 360 | cstr_vec.push(cstr.into_raw() as *const c_char); 361 | } 362 | cstr_vec.shrink_to_fit(); 363 | 364 | let array = ArrayOfCStrings { 365 | length: cstr_vec.len() as size_t, 366 | values: cstr_vec.as_ptr() as *const *const c_char, 367 | }; 368 | 369 | mem::forget(cstr_vec); 370 | array 371 | } 372 | } 373 | 374 | #[repr(C)] 375 | #[derive(Copy, Clone)] 376 | struct Value { 377 | description: *const c_char, 378 | name: *const c_char, 379 | directives: ArrayOfDirectives, 380 | } 381 | 382 | #[repr(C)] 383 | #[derive(Copy, Clone)] 384 | struct ArrayOfValues { 385 | length: size_t, 386 | values: *const *const c_void, 387 | } 388 | 389 | impl ArrayOfValues { 390 | fn from_vec(vec: Vec) -> ArrayOfValues { 391 | let mut value_vec: Vec = vec![]; 392 | 393 | for v in vec { 394 | value_vec.push(Value { 395 | name: CString::new(v.name()).unwrap().into_raw(), 396 | description: convert_optional_string_to_cstr(v.description()), 397 | directives: match v.directives() { 398 | None => ArrayOfDirectives::from_vec(vec![]), 399 | Some(directives) => ArrayOfDirectives::from_vec(directives), 400 | }, 401 | }); 402 | } 403 | value_vec.shrink_to_fit(); 404 | 405 | let array = ArrayOfValues { 406 | length: value_vec.len() as size_t, 407 | values: value_vec.as_ptr() as *const *const c_void, 408 | }; 409 | 410 | mem::forget(value_vec); 411 | 412 | array 413 | } 414 | } 415 | 416 | #[repr(C)] 417 | #[derive(Copy, Clone)] 418 | struct Directive { 419 | name: *const c_char, 420 | arguments: ArrayOfDirectiveArguments, 421 | } 422 | 423 | #[repr(C)] 424 | #[derive(Copy, Clone)] 425 | struct ArrayOfDirectives { 426 | length: size_t, 427 | values: *const *const c_void, 428 | } 429 | 430 | #[repr(C)] 431 | #[derive(Copy, Clone)] 432 | struct DirectiveArgument { 433 | name: *const c_char, 434 | value: *const c_char, 435 | } 436 | 437 | #[repr(C)] 438 | #[derive(Copy, Clone)] 439 | struct ArrayOfDirectiveArguments { 440 | length: size_t, 441 | values: *const *const c_void, 442 | } 443 | 444 | impl ArrayOfDirectives { 445 | fn from_vec(vec: Vec) -> ArrayOfDirectives { 446 | let mut value_vec: Vec = vec![]; 447 | 448 | for v in vec { 449 | let mut argument_vec: Vec = vec![]; 450 | match v.arguments() { 451 | None => {} 452 | Some(arguments) => for arg in arguments { 453 | argument_vec.push(DirectiveArgument { 454 | name: CString::new(arg.name()).unwrap().into_raw(), 455 | value: convert_optional_string_to_cstr(arg.value()), 456 | }); 457 | }, 458 | } 459 | 460 | argument_vec.shrink_to_fit(); 461 | 462 | let arguments = ArrayOfDirectiveArguments { 463 | length: argument_vec.len() as size_t, 464 | values: argument_vec.as_ptr() as *const *const c_void, 465 | }; 466 | 467 | value_vec.push(Directive { 468 | name: CString::new(v.name()).unwrap().into_raw(), 469 | arguments: arguments, 470 | }); 471 | 472 | mem::forget(argument_vec); 473 | } 474 | value_vec.shrink_to_fit(); 475 | 476 | let array = ArrayOfDirectives { 477 | length: value_vec.len() as size_t, 478 | values: value_vec.as_ptr() as *const *const c_void, 479 | }; 480 | 481 | mem::forget(value_vec); 482 | 483 | array 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /* required for sandboxing */ 17 | #include 18 | #include 19 | 20 | #ifdef _WIN32 21 | # include 22 | # include 23 | # include 24 | # include 25 | 26 | # define _MAIN_CC __cdecl 27 | 28 | # ifndef stat 29 | # define stat(path, st) _stat(path, st) 30 | # endif 31 | # ifndef mkdir 32 | # define mkdir(path, mode) _mkdir(path) 33 | # endif 34 | # ifndef chdir 35 | # define chdir(path) _chdir(path) 36 | # endif 37 | # ifndef access 38 | # define access(path, mode) _access(path, mode) 39 | # endif 40 | # ifndef strdup 41 | # define strdup(str) _strdup(str) 42 | # endif 43 | # ifndef strcasecmp 44 | # define strcasecmp(a,b) _stricmp(a,b) 45 | # endif 46 | 47 | # ifndef __MINGW32__ 48 | # pragma comment(lib, "shell32") 49 | # ifndef strncpy 50 | # define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE) 51 | # endif 52 | # ifndef W_OK 53 | # define W_OK 02 54 | # endif 55 | # ifndef S_ISDIR 56 | # define S_ISDIR(x) ((x & _S_IFDIR) != 0) 57 | # endif 58 | # define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__) 59 | # else 60 | # define p_snprintf snprintf 61 | # endif 62 | 63 | # ifndef PRIuZ 64 | # define PRIuZ "Iu" 65 | # endif 66 | # ifndef PRIxZ 67 | # define PRIxZ "Ix" 68 | # endif 69 | 70 | # ifdef _MSC_VER 71 | typedef struct stat STAT_T; 72 | # else 73 | typedef struct _stat STAT_T; 74 | # endif 75 | #else 76 | # include /* waitpid(2) */ 77 | # include 78 | # define _MAIN_CC 79 | # define p_snprintf snprintf 80 | # ifndef PRIuZ 81 | # define PRIuZ "zu" 82 | # endif 83 | # ifndef PRIxZ 84 | # define PRIxZ "zx" 85 | # endif 86 | typedef struct stat STAT_T; 87 | #endif 88 | 89 | #include "clar.h" 90 | 91 | static void fs_rm(const char *_source); 92 | static void fs_copy(const char *_source, const char *dest); 93 | 94 | static const char * 95 | fixture_path(const char *base, const char *fixture_name); 96 | 97 | struct clar_error { 98 | const char *test; 99 | int test_number; 100 | const char *suite; 101 | const char *file; 102 | int line_number; 103 | const char *error_msg; 104 | char *description; 105 | 106 | struct clar_error *next; 107 | }; 108 | 109 | static struct { 110 | int argc; 111 | char **argv; 112 | 113 | enum cl_test_status test_status; 114 | const char *active_test; 115 | const char *active_suite; 116 | 117 | int total_skipped; 118 | int total_errors; 119 | 120 | int tests_ran; 121 | int suites_ran; 122 | 123 | int report_errors_only; 124 | int exit_on_error; 125 | int report_suite_names; 126 | 127 | struct clar_error *errors; 128 | struct clar_error *last_error; 129 | 130 | void (*local_cleanup)(void *); 131 | void *local_cleanup_payload; 132 | 133 | jmp_buf trampoline; 134 | int trampoline_enabled; 135 | 136 | cl_trace_cb *pfn_trace_cb; 137 | void *trace_payload; 138 | 139 | } _clar; 140 | 141 | struct clar_func { 142 | const char *name; 143 | void (*ptr)(void); 144 | }; 145 | 146 | struct clar_suite { 147 | const char *name; 148 | struct clar_func initialize; 149 | struct clar_func cleanup; 150 | const struct clar_func *tests; 151 | size_t test_count; 152 | int enabled; 153 | }; 154 | 155 | /* From clar_print_*.c */ 156 | static void clar_print_init(int test_count, int suite_count, const char *suite_names); 157 | static void clar_print_shutdown(int test_count, int suite_count, int error_count); 158 | static void clar_print_error(int num, const struct clar_error *error); 159 | static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status failed); 160 | static void clar_print_onsuite(const char *suite_name, int suite_index); 161 | static void clar_print_onabort(const char *msg, ...); 162 | 163 | /* From clar_sandbox.c */ 164 | static void clar_unsandbox(void); 165 | static int clar_sandbox(void); 166 | 167 | /* Load the declarations for the test suite */ 168 | #include "clar.suite" 169 | 170 | 171 | #define CL_TRACE(ev) \ 172 | do { \ 173 | if (_clar.pfn_trace_cb) \ 174 | _clar.pfn_trace_cb(ev, \ 175 | _clar.active_suite, \ 176 | _clar.active_test, \ 177 | _clar.trace_payload); \ 178 | } while (0) 179 | 180 | void cl_trace_register(cl_trace_cb *cb, void *payload) 181 | { 182 | _clar.pfn_trace_cb = cb; 183 | _clar.trace_payload = payload; 184 | } 185 | 186 | 187 | /* Core test functions */ 188 | static void 189 | clar_report_errors(void) 190 | { 191 | int i = 1; 192 | struct clar_error *error, *next; 193 | 194 | error = _clar.errors; 195 | while (error != NULL) { 196 | next = error->next; 197 | clar_print_error(i++, error); 198 | free(error->description); 199 | free(error); 200 | error = next; 201 | } 202 | 203 | _clar.errors = _clar.last_error = NULL; 204 | } 205 | 206 | static void 207 | clar_run_test( 208 | const struct clar_func *test, 209 | const struct clar_func *initialize, 210 | const struct clar_func *cleanup) 211 | { 212 | _clar.test_status = CL_TEST_OK; 213 | _clar.trampoline_enabled = 1; 214 | 215 | CL_TRACE(CL_TRACE__TEST__BEGIN); 216 | 217 | if (setjmp(_clar.trampoline) == 0) { 218 | if (initialize->ptr != NULL) { 219 | initialize->ptr(); 220 | } 221 | 222 | CL_TRACE(CL_TRACE__TEST__RUN_BEGIN); 223 | test->ptr(); 224 | CL_TRACE(CL_TRACE__TEST__RUN_END); 225 | } 226 | 227 | _clar.trampoline_enabled = 0; 228 | 229 | if (_clar.local_cleanup != NULL) { 230 | _clar.local_cleanup(_clar.local_cleanup_payload); 231 | } 232 | 233 | if (cleanup->ptr != NULL) { 234 | cleanup->ptr(); 235 | } 236 | 237 | CL_TRACE(CL_TRACE__TEST__END); 238 | 239 | _clar.tests_ran++; 240 | 241 | /* remove any local-set cleanup methods */ 242 | _clar.local_cleanup = NULL; 243 | _clar.local_cleanup_payload = NULL; 244 | 245 | if (_clar.report_errors_only) { 246 | clar_report_errors(); 247 | } else { 248 | clar_print_ontest(test->name, _clar.tests_ran, _clar.test_status); 249 | } 250 | } 251 | 252 | static void 253 | clar_run_suite(const struct clar_suite *suite, const char *filter) 254 | { 255 | const struct clar_func *test = suite->tests; 256 | size_t i, matchlen; 257 | 258 | if (!suite->enabled) { 259 | return; 260 | } 261 | 262 | if (_clar.exit_on_error && _clar.total_errors) { 263 | return; 264 | } 265 | 266 | if (!_clar.report_errors_only) { 267 | clar_print_onsuite(suite->name, ++_clar.suites_ran); 268 | } 269 | 270 | _clar.active_suite = suite->name; 271 | _clar.active_test = NULL; 272 | CL_TRACE(CL_TRACE__SUITE_BEGIN); 273 | 274 | if (filter) { 275 | size_t suitelen = strlen(suite->name); 276 | matchlen = strlen(filter); 277 | if (matchlen <= suitelen) { 278 | filter = NULL; 279 | } else { 280 | filter += suitelen; 281 | while (*filter == ':') { 282 | ++filter; 283 | } 284 | matchlen = strlen(filter); 285 | } 286 | } 287 | 288 | for (i = 0; i < suite->test_count; ++i) { 289 | if (filter && strncmp(test[i].name, filter, matchlen)) { 290 | continue; 291 | } 292 | 293 | _clar.active_test = test[i].name; 294 | clar_run_test(&test[i], &suite->initialize, &suite->cleanup); 295 | 296 | if (_clar.exit_on_error && _clar.total_errors) { 297 | return; 298 | } 299 | } 300 | 301 | _clar.active_test = NULL; 302 | CL_TRACE(CL_TRACE__SUITE_END); 303 | } 304 | 305 | static void 306 | clar_usage(const char *arg) 307 | { 308 | printf("Usage: %s [options]\n\n", arg); 309 | printf("Options:\n"); 310 | printf(" -sname\tRun only the suite with `name` (can go to individual test name)\n"); 311 | printf(" -iname\tInclude the suite with `name`\n"); 312 | printf(" -xname\tExclude the suite with `name`\n"); 313 | printf(" -v \tIncrease verbosity (show suite names)\n"); 314 | printf(" -q \tOnly report tests that had an error\n"); 315 | printf(" -Q \tQuit as soon as a test fails\n"); 316 | printf(" -l \tPrint suite names\n"); 317 | exit(-1); 318 | } 319 | 320 | static void 321 | clar_parse_args(int argc, char **argv) 322 | { 323 | int i; 324 | 325 | for (i = 1; i < argc; ++i) { 326 | char *argument = argv[i]; 327 | 328 | if (argument[0] != '-') { 329 | clar_usage(argv[0]); 330 | } 331 | 332 | switch (argument[1]) { 333 | case 's': 334 | case 'i': 335 | case 'x': { /* given suite name */ 336 | int offset = (argument[2] == '=') ? 3 : 2, found = 0; 337 | char action = argument[1]; 338 | size_t j, arglen, suitelen, cmplen; 339 | 340 | argument += offset; 341 | arglen = strlen(argument); 342 | 343 | if (arglen == 0) { 344 | clar_usage(argv[0]); 345 | } 346 | 347 | for (j = 0; j < _clar_suite_count; ++j) { 348 | suitelen = strlen(_clar_suites[j].name); 349 | cmplen = (arglen < suitelen) ? arglen : suitelen; 350 | 351 | if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { 352 | int exact = (arglen >= suitelen); 353 | 354 | /* Do we have a real suite prefix separated by a 355 | * trailing '::' or just a matching substring? */ 356 | if (arglen > suitelen && (argument[suitelen] != ':' 357 | || argument[suitelen + 1] != ':')) { 358 | continue; 359 | } 360 | 361 | ++found; 362 | 363 | if (!exact) { 364 | _clar.report_suite_names = 1; 365 | } 366 | 367 | switch (action) { 368 | case 's': 369 | _clar_suites[j].enabled = 1; 370 | clar_run_suite(&_clar_suites[j], argument); 371 | break; 372 | case 'i': 373 | _clar_suites[j].enabled = 1; 374 | break; 375 | case 'x': 376 | _clar_suites[j].enabled = 0; 377 | break; 378 | } 379 | 380 | if (exact) { 381 | break; 382 | } 383 | } 384 | } 385 | 386 | if (!found) { 387 | clar_print_onabort("No suite matching '%s' found.\n", argument); 388 | exit(-1); 389 | } 390 | break; 391 | } 392 | 393 | case 'q': 394 | _clar.report_errors_only = 1; 395 | break; 396 | 397 | case 'Q': 398 | _clar.exit_on_error = 1; 399 | break; 400 | 401 | case 'l': { 402 | size_t j; 403 | printf("Test suites (use -s to run just one):\n"); 404 | for (j = 0; j < _clar_suite_count; ++j) { 405 | printf(" %3d: %s\n", (int)j, _clar_suites[j].name); 406 | } 407 | 408 | exit(0); 409 | } 410 | 411 | case 'v': 412 | _clar.report_suite_names = 1; 413 | break; 414 | 415 | default: 416 | clar_usage(argv[0]); 417 | } 418 | } 419 | } 420 | 421 | void 422 | clar_test_init(int argc, char **argv) 423 | { 424 | clar_print_init( 425 | (int)_clar_callback_count, 426 | (int)_clar_suite_count, 427 | "" 428 | ); 429 | 430 | if (clar_sandbox() < 0) { 431 | clar_print_onabort("Failed to sandbox the test runner.\n"); 432 | exit(-1); 433 | } 434 | 435 | _clar.argc = argc; 436 | _clar.argv = argv; 437 | } 438 | 439 | int 440 | clar_test_run() 441 | { 442 | if (_clar.argc > 1) { 443 | clar_parse_args(_clar.argc, _clar.argv); 444 | } 445 | 446 | if (!_clar.suites_ran) { 447 | size_t i; 448 | for (i = 0; i < _clar_suite_count; ++i) { 449 | clar_run_suite(&_clar_suites[i], NULL); 450 | } 451 | } 452 | 453 | return _clar.total_errors; 454 | } 455 | 456 | void 457 | clar_test_shutdown() 458 | { 459 | clar_print_shutdown( 460 | _clar.tests_ran, 461 | (int)_clar_suite_count, 462 | _clar.total_errors 463 | ); 464 | 465 | clar_unsandbox(); 466 | } 467 | 468 | int 469 | clar_test(int argc, char **argv) 470 | { 471 | int errors; 472 | 473 | clar_test_init(argc, argv); 474 | errors = clar_test_run(); 475 | clar_test_shutdown(); 476 | 477 | return errors; 478 | } 479 | 480 | static void abort_test(void) 481 | { 482 | if (!_clar.trampoline_enabled) { 483 | clar_print_onabort( 484 | "Fatal error: a cleanup method raised an exception."); 485 | clar_report_errors(); 486 | exit(-1); 487 | } 488 | 489 | CL_TRACE(CL_TRACE__TEST__LONGJMP); 490 | longjmp(_clar.trampoline, -1); 491 | } 492 | 493 | void clar__skip(void) 494 | { 495 | _clar.test_status = CL_TEST_SKIP; 496 | _clar.total_skipped++; 497 | abort_test(); 498 | } 499 | 500 | void clar__fail( 501 | const char *file, 502 | int line, 503 | const char *error_msg, 504 | const char *description, 505 | int should_abort) 506 | { 507 | struct clar_error *error = calloc(1, sizeof(struct clar_error)); 508 | 509 | if (_clar.errors == NULL) { 510 | _clar.errors = error; 511 | } 512 | 513 | if (_clar.last_error != NULL) { 514 | _clar.last_error->next = error; 515 | } 516 | 517 | _clar.last_error = error; 518 | 519 | error->test = _clar.active_test; 520 | error->test_number = _clar.tests_ran; 521 | error->suite = _clar.active_suite; 522 | error->file = file; 523 | error->line_number = line; 524 | error->error_msg = error_msg; 525 | 526 | if (description != NULL) { 527 | error->description = strdup(description); 528 | } 529 | 530 | _clar.total_errors++; 531 | _clar.test_status = CL_TEST_FAILURE; 532 | 533 | if (should_abort) { 534 | abort_test(); 535 | } 536 | } 537 | 538 | void clar__assert( 539 | int condition, 540 | const char *file, 541 | int line, 542 | const char *error_msg, 543 | const char *description, 544 | int should_abort) 545 | { 546 | if (condition) { 547 | return; 548 | } 549 | 550 | clar__fail(file, line, error_msg, description, should_abort); 551 | } 552 | 553 | void clar__assert_equal( 554 | const char *file, 555 | int line, 556 | const char *err, 557 | int should_abort, 558 | const char *fmt, 559 | ...) 560 | { 561 | va_list args; 562 | char buf[4096]; 563 | int is_equal = 1; 564 | 565 | va_start(args, fmt); 566 | 567 | if (!strcmp("%s", fmt)) { 568 | const char *s1 = va_arg(args, const char *); 569 | const char *s2 = va_arg(args, const char *); 570 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2); 571 | 572 | if (!is_equal) { 573 | if (s1 && s2) { 574 | int pos; 575 | for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos) 576 | /* find differing byte offset */; 577 | p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)", 578 | s1, s2, pos); 579 | } else { 580 | p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2); 581 | } 582 | } 583 | } else if(!strcmp("%.*s", fmt)) { 584 | const char *s1 = va_arg(args, const char *); 585 | const char *s2 = va_arg(args, const char *); 586 | int len = va_arg(args, int); 587 | is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len); 588 | 589 | if (!is_equal) { 590 | if (s1 && s2) { 591 | int pos; 592 | for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos) 593 | /* find differing byte offset */; 594 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)", 595 | len, s1, len, s2, pos); 596 | } else { 597 | p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2); 598 | } 599 | } 600 | } else if (!strcmp("%ls", fmt)) { 601 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 602 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 603 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2); 604 | 605 | if (!is_equal) { 606 | if (wcs1 && wcs2) { 607 | int pos; 608 | for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos) 609 | /* find differing byte offset */; 610 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)", 611 | wcs1, wcs2, pos); 612 | } else { 613 | p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2); 614 | } 615 | } 616 | } else if(!strcmp("%.*ls", fmt)) { 617 | const wchar_t *wcs1 = va_arg(args, const wchar_t *); 618 | const wchar_t *wcs2 = va_arg(args, const wchar_t *); 619 | int len = va_arg(args, int); 620 | is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len); 621 | 622 | if (!is_equal) { 623 | if (wcs1 && wcs2) { 624 | int pos; 625 | for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos) 626 | /* find differing byte offset */; 627 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)", 628 | len, wcs1, len, wcs2, pos); 629 | } else { 630 | p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2); 631 | } 632 | } 633 | } else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) { 634 | size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t); 635 | is_equal = (sz1 == sz2); 636 | if (!is_equal) { 637 | int offset = p_snprintf(buf, sizeof(buf), fmt, sz1); 638 | strncat(buf, " != ", sizeof(buf) - offset); 639 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2); 640 | } 641 | } else if (!strcmp("%p", fmt)) { 642 | void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *); 643 | is_equal = (p1 == p2); 644 | if (!is_equal) { 645 | p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2); 646 | } 647 | } else { 648 | int i1 = va_arg(args, int), i2 = va_arg(args, int); 649 | is_equal = (i1 == i2); 650 | if (!is_equal) { 651 | int offset = p_snprintf(buf, sizeof(buf), fmt, i1); 652 | strncat(buf, " != ", sizeof(buf) - offset); 653 | p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2); 654 | } 655 | } 656 | 657 | va_end(args); 658 | 659 | if (!is_equal) { 660 | clar__fail(file, line, err, buf, should_abort); 661 | } 662 | } 663 | 664 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque) 665 | { 666 | _clar.local_cleanup = cleanup; 667 | _clar.local_cleanup_payload = opaque; 668 | } 669 | 670 | #include "clar/sandbox.h" 671 | #include "clar/fixtures.h" 672 | #include "clar/fs.h" 673 | #include "clar/print.h" 674 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #ifndef __CLAR_TEST_H__ 8 | #define __CLAR_TEST_H__ 9 | 10 | #include 11 | 12 | enum cl_test_status { 13 | CL_TEST_OK, 14 | CL_TEST_FAILURE, 15 | CL_TEST_SKIP 16 | }; 17 | 18 | void clar_test_init(int argc, char *argv[]); 19 | int clar_test_run(void); 20 | void clar_test_shutdown(void); 21 | 22 | int clar_test(int argc, char *argv[]); 23 | 24 | const char *clar_sandbox_path(void); 25 | 26 | void cl_set_cleanup(void (*cleanup)(void *), void *opaque); 27 | void cl_fs_cleanup(void); 28 | 29 | /** 30 | * cl_trace_* is a hook to provide a simple global tracing 31 | * mechanism. 32 | * 33 | * The goal here is to let main() provide clar-proper 34 | * with a callback to optionally write log info for 35 | * test operations into the same stream used by their 36 | * actual tests. This would let them print test names 37 | * and maybe performance data as they choose. 38 | * 39 | * The goal is NOT to alter the flow of control or to 40 | * override test selection/skipping. (So the callback 41 | * does not return a value.) 42 | * 43 | * The goal is NOT to duplicate the existing 44 | * pass/fail/skip reporting. (So the callback 45 | * does not accept a status/errorcode argument.) 46 | * 47 | */ 48 | typedef enum cl_trace_event { 49 | CL_TRACE__SUITE_BEGIN, 50 | CL_TRACE__SUITE_END, 51 | CL_TRACE__TEST__BEGIN, 52 | CL_TRACE__TEST__END, 53 | CL_TRACE__TEST__RUN_BEGIN, 54 | CL_TRACE__TEST__RUN_END, 55 | CL_TRACE__TEST__LONGJMP, 56 | } cl_trace_event; 57 | 58 | typedef void (cl_trace_cb)( 59 | cl_trace_event ev, 60 | const char *suite_name, 61 | const char *test_name, 62 | void *payload); 63 | 64 | /** 65 | * Register a callback into CLAR to send global trace events. 66 | * Pass NULL to disable. 67 | */ 68 | void cl_trace_register(cl_trace_cb *cb, void *payload); 69 | 70 | 71 | #ifdef CLAR_FIXTURE_PATH 72 | const char *cl_fixture(const char *fixture_name); 73 | void cl_fixture_sandbox(const char *fixture_name); 74 | void cl_fixture_cleanup(const char *fixture_name); 75 | #endif 76 | 77 | /** 78 | * Assertion macros with explicit error message 79 | */ 80 | #define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 1) 81 | #define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 1) 82 | #define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 1) 83 | 84 | /** 85 | * Check macros with explicit error message 86 | */ 87 | #define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __LINE__, "Function call failed: " #expr, desc, 0) 88 | #define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __LINE__, "Expected function call to fail: " #expr, desc, 0) 89 | #define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __LINE__, "Expression is not true: " #expr, desc, 0) 90 | 91 | /** 92 | * Assertion macros with no error message 93 | */ 94 | #define cl_must_pass(expr) cl_must_pass_(expr, NULL) 95 | #define cl_must_fail(expr) cl_must_fail_(expr, NULL) 96 | #define cl_assert(expr) cl_assert_(expr, NULL) 97 | 98 | /** 99 | * Check macros with no error message 100 | */ 101 | #define cl_check_pass(expr) cl_check_pass_(expr, NULL) 102 | #define cl_check_fail(expr) cl_check_fail_(expr, NULL) 103 | #define cl_check(expr) cl_check_(expr, NULL) 104 | 105 | /** 106 | * Forced failure/warning 107 | */ 108 | #define cl_fail(desc) clar__fail(__FILE__, __LINE__, "Test failed.", desc, 1) 109 | #define cl_warning(desc) clar__fail(__FILE__, __LINE__, "Warning during test execution:", desc, 0) 110 | 111 | #define cl_skip() clar__skip() 112 | 113 | /** 114 | * Typed assertion macros 115 | */ 116 | #define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2)) 117 | #define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2)) 118 | 119 | #define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2)) 120 | #define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2)) 121 | 122 | #define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len)) 123 | #define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len)) 124 | 125 | #define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 126 | #define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len)) 127 | 128 | #define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2)) 129 | #define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2)) 130 | #define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2)) 131 | 132 | #define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0)) 133 | 134 | #define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2)) 135 | 136 | void clar__skip(void); 137 | 138 | void clar__fail( 139 | const char *file, 140 | int line, 141 | const char *error, 142 | const char *description, 143 | int should_abort); 144 | 145 | void clar__assert( 146 | int condition, 147 | const char *file, 148 | int line, 149 | const char *error, 150 | const char *description, 151 | int should_abort); 152 | 153 | void clar__assert_equal( 154 | const char *file, 155 | int line, 156 | const char *err, 157 | int should_abort, 158 | const char *fmt, 159 | ...); 160 | 161 | #endif 162 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar/fixtures.h: -------------------------------------------------------------------------------- 1 | static const char * 2 | fixture_path(const char *base, const char *fixture_name) 3 | { 4 | static char _path[4096]; 5 | size_t root_len; 6 | 7 | root_len = strlen(base); 8 | strncpy(_path, base, sizeof(_path)); 9 | 10 | if (_path[root_len - 1] != '/') { 11 | _path[root_len++] = '/'; 12 | } 13 | 14 | if (fixture_name[0] == '/') { 15 | fixture_name++; 16 | } 17 | 18 | strncpy(_path + root_len, 19 | fixture_name, 20 | sizeof(_path) - root_len); 21 | 22 | return _path; 23 | } 24 | 25 | static const char * 26 | fixture_basename(const char *fixture_name) 27 | { 28 | const char *p; 29 | 30 | for (p = fixture_name; *p; p++) { 31 | if (p[0] == '/' && p[1] && p[1] != '/') { 32 | fixture_name = p+1; 33 | } 34 | } 35 | 36 | return fixture_name; 37 | } 38 | 39 | #ifdef CLAR_FIXTURE_PATH 40 | const char *cl_fixture(const char *fixture_name) 41 | { 42 | return fixture_path(CLAR_FIXTURE_PATH, fixture_name); 43 | } 44 | 45 | void cl_fixture_sandbox(const char *fixture_name) 46 | { 47 | fs_copy(cl_fixture(fixture_name), _clar_path); 48 | } 49 | 50 | void cl_fixture_cleanup(const char *fixture_name) 51 | { 52 | fs_rm(fixture_path(_clar_path, fixture_basename(fixture_name))); 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar/fs.h: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | 3 | #define RM_RETRY_COUNT 5 4 | #define RM_RETRY_DELAY 10 5 | 6 | #ifdef __MINGW32__ 7 | 8 | /* These security-enhanced functions are not available 9 | * in MinGW, so just use the vanilla ones */ 10 | #define wcscpy_s(a, b, c) wcscpy((a), (c)) 11 | #define wcscat_s(a, b, c) wcscat((a), (c)) 12 | 13 | #endif /* __MINGW32__ */ 14 | 15 | static int 16 | fs__dotordotdot(WCHAR *_tocheck) 17 | { 18 | return _tocheck[0] == '.' && 19 | (_tocheck[1] == '\0' || 20 | (_tocheck[1] == '.' && _tocheck[2] == '\0')); 21 | } 22 | 23 | static int 24 | fs_rmdir_rmdir(WCHAR *_wpath) 25 | { 26 | unsigned retries = 1; 27 | 28 | while (!RemoveDirectoryW(_wpath)) { 29 | /* Only retry when we have retries remaining, and the 30 | * error was ERROR_DIR_NOT_EMPTY. */ 31 | if (retries++ > RM_RETRY_COUNT || 32 | ERROR_DIR_NOT_EMPTY != GetLastError()) { 33 | return -1; 34 | } 35 | 36 | /* Give whatever has a handle to a child item some time 37 | * to release it before trying again */ 38 | Sleep(RM_RETRY_DELAY * retries * retries); 39 | } 40 | 41 | return 0; 42 | } 43 | 44 | static void 45 | fs_rmdir_helper(WCHAR *_wsource) 46 | { 47 | WCHAR buffer[MAX_PATH]; 48 | HANDLE find_handle; 49 | WIN32_FIND_DATAW find_data; 50 | size_t buffer_prefix_len; 51 | 52 | /* Set up the buffer and capture the length */ 53 | wcscpy_s(buffer, MAX_PATH, _wsource); 54 | wcscat_s(buffer, MAX_PATH, L"\\"); 55 | buffer_prefix_len = wcslen(buffer); 56 | 57 | /* FindFirstFile needs a wildcard to match multiple items */ 58 | wcscat_s(buffer, MAX_PATH, L"*"); 59 | find_handle = FindFirstFileW(buffer, &find_data); 60 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 61 | 62 | do { 63 | /* FindFirstFile/FindNextFile gives back . and .. 64 | * entries at the beginning */ 65 | if (fs__dotordotdot(find_data.cFileName)) { 66 | continue; 67 | } 68 | 69 | wcscpy_s(buffer + buffer_prefix_len, MAX_PATH - buffer_prefix_len, find_data.cFileName); 70 | 71 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) { 72 | fs_rmdir_helper(buffer); 73 | } else { 74 | /* If set, the +R bit must be cleared before deleting */ 75 | if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes) { 76 | cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY)); 77 | } 78 | 79 | cl_assert(DeleteFileW(buffer)); 80 | } 81 | } while (FindNextFileW(find_handle, &find_data)); 82 | 83 | /* Ensure that we successfully completed the enumeration */ 84 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 85 | 86 | /* Close the find handle */ 87 | FindClose(find_handle); 88 | 89 | /* Now that the directory is empty, remove it */ 90 | cl_assert(0 == fs_rmdir_rmdir(_wsource)); 91 | } 92 | 93 | static int 94 | fs_rm_wait(WCHAR *_wpath) 95 | { 96 | unsigned retries = 1; 97 | DWORD last_error; 98 | 99 | do { 100 | if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath)) { 101 | last_error = GetLastError(); 102 | } else { 103 | last_error = ERROR_SUCCESS; 104 | } 105 | 106 | /* Is the item gone? */ 107 | if (ERROR_FILE_NOT_FOUND == last_error || 108 | ERROR_PATH_NOT_FOUND == last_error) { 109 | return 0; 110 | } 111 | 112 | Sleep(RM_RETRY_DELAY * retries * retries); 113 | } while (retries++ <= RM_RETRY_COUNT); 114 | 115 | return -1; 116 | } 117 | 118 | static void 119 | fs_rm(const char *_source) 120 | { 121 | WCHAR wsource[MAX_PATH]; 122 | DWORD attrs; 123 | 124 | /* The input path is UTF-8. Convert it to wide characters 125 | * for use with the Windows API */ 126 | cl_assert(MultiByteToWideChar(CP_UTF8, 127 | MB_ERR_INVALID_CHARS, 128 | _source, 129 | -1, /* Indicates NULL termination */ 130 | wsource, 131 | MAX_PATH)); 132 | 133 | /* Does the item exist? If not, we have no work to do */ 134 | attrs = GetFileAttributesW(wsource); 135 | 136 | if (INVALID_FILE_ATTRIBUTES == attrs) { 137 | return; 138 | } 139 | 140 | if (FILE_ATTRIBUTE_DIRECTORY & attrs) { 141 | fs_rmdir_helper(wsource); 142 | } else { 143 | /* The item is a file. Strip the +R bit */ 144 | if (FILE_ATTRIBUTE_READONLY & attrs) { 145 | cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY)); 146 | } 147 | 148 | cl_assert(DeleteFileW(wsource)); 149 | } 150 | 151 | /* Wait for the DeleteFile or RemoveDirectory call to complete */ 152 | cl_assert(0 == fs_rm_wait(wsource)); 153 | } 154 | 155 | static void 156 | fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest) 157 | { 158 | WCHAR buf_source[MAX_PATH], buf_dest[MAX_PATH]; 159 | HANDLE find_handle; 160 | WIN32_FIND_DATAW find_data; 161 | size_t buf_source_prefix_len, buf_dest_prefix_len; 162 | 163 | wcscpy_s(buf_source, MAX_PATH, _wsource); 164 | wcscat_s(buf_source, MAX_PATH, L"\\"); 165 | buf_source_prefix_len = wcslen(buf_source); 166 | 167 | wcscpy_s(buf_dest, MAX_PATH, _wdest); 168 | wcscat_s(buf_dest, MAX_PATH, L"\\"); 169 | buf_dest_prefix_len = wcslen(buf_dest); 170 | 171 | /* Get an enumerator for the items in the source. */ 172 | wcscat_s(buf_source, MAX_PATH, L"*"); 173 | find_handle = FindFirstFileW(buf_source, &find_data); 174 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 175 | 176 | /* Create the target directory. */ 177 | cl_assert(CreateDirectoryW(_wdest, NULL)); 178 | 179 | do { 180 | /* FindFirstFile/FindNextFile gives back . and .. 181 | * entries at the beginning */ 182 | if (fs__dotordotdot(find_data.cFileName)) { 183 | continue; 184 | } 185 | 186 | wcscpy_s(buf_source + buf_source_prefix_len, MAX_PATH - buf_source_prefix_len, find_data.cFileName); 187 | wcscpy_s(buf_dest + buf_dest_prefix_len, MAX_PATH - buf_dest_prefix_len, find_data.cFileName); 188 | 189 | if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes) { 190 | fs_copydir_helper(buf_source, buf_dest); 191 | } else { 192 | cl_assert(CopyFileW(buf_source, buf_dest, TRUE)); 193 | } 194 | } while (FindNextFileW(find_handle, &find_data)); 195 | 196 | /* Ensure that we successfully completed the enumeration */ 197 | cl_assert(ERROR_NO_MORE_FILES == GetLastError()); 198 | 199 | /* Close the find handle */ 200 | FindClose(find_handle); 201 | } 202 | 203 | static void 204 | fs_copy(const char *_source, const char *_dest) 205 | { 206 | WCHAR wsource[MAX_PATH], wdest[MAX_PATH]; 207 | DWORD source_attrs, dest_attrs; 208 | HANDLE find_handle; 209 | WIN32_FIND_DATAW find_data; 210 | 211 | /* The input paths are UTF-8. Convert them to wide characters 212 | * for use with the Windows API. */ 213 | cl_assert(MultiByteToWideChar(CP_UTF8, 214 | MB_ERR_INVALID_CHARS, 215 | _source, 216 | -1, 217 | wsource, 218 | MAX_PATH)); 219 | 220 | cl_assert(MultiByteToWideChar(CP_UTF8, 221 | MB_ERR_INVALID_CHARS, 222 | _dest, 223 | -1, 224 | wdest, 225 | MAX_PATH)); 226 | 227 | /* Check the source for existence */ 228 | source_attrs = GetFileAttributesW(wsource); 229 | cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs); 230 | 231 | /* Check the target for existence */ 232 | dest_attrs = GetFileAttributesW(wdest); 233 | 234 | if (INVALID_FILE_ATTRIBUTES != dest_attrs) { 235 | /* Target exists; append last path part of source to target. 236 | * Use FindFirstFile to parse the path */ 237 | find_handle = FindFirstFileW(wsource, &find_data); 238 | cl_assert(INVALID_HANDLE_VALUE != find_handle); 239 | wcscat_s(wdest, MAX_PATH, L"\\"); 240 | wcscat_s(wdest, MAX_PATH, find_data.cFileName); 241 | FindClose(find_handle); 242 | 243 | /* Check the new target for existence */ 244 | cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest)); 245 | } 246 | 247 | if (FILE_ATTRIBUTE_DIRECTORY & source_attrs) { 248 | fs_copydir_helper(wsource, wdest); 249 | } else { 250 | cl_assert(CopyFileW(wsource, wdest, TRUE)); 251 | } 252 | } 253 | 254 | void 255 | cl_fs_cleanup(void) 256 | { 257 | fs_rm(fixture_path(_clar_path, "*")); 258 | } 259 | 260 | #else 261 | 262 | #include 263 | #include 264 | 265 | static int 266 | shell_out(char * const argv[]) 267 | { 268 | int status, piderr; 269 | pid_t pid; 270 | 271 | pid = fork(); 272 | 273 | if (pid < 0) { 274 | fprintf(stderr, 275 | "System error: `fork()` call failed (%d) - %s\n", 276 | errno, strerror(errno)); 277 | exit(-1); 278 | } 279 | 280 | if (pid == 0) { 281 | execv(argv[0], argv); 282 | } 283 | 284 | do { 285 | piderr = waitpid(pid, &status, WUNTRACED); 286 | } while (piderr < 0 && (errno == EAGAIN || errno == EINTR)); 287 | 288 | return WEXITSTATUS(status); 289 | } 290 | 291 | static void 292 | fs_copy(const char *_source, const char *dest) 293 | { 294 | char *argv[5]; 295 | char *source; 296 | size_t source_len; 297 | 298 | source = strdup(_source); 299 | source_len = strlen(source); 300 | 301 | if (source[source_len - 1] == '/') { 302 | source[source_len - 1] = 0; 303 | } 304 | 305 | argv[0] = "/bin/cp"; 306 | argv[1] = "-R"; 307 | argv[2] = source; 308 | argv[3] = (char *)dest; 309 | argv[4] = NULL; 310 | 311 | cl_must_pass_( 312 | shell_out(argv), 313 | "Failed to copy test fixtures to sandbox" 314 | ); 315 | 316 | free(source); 317 | } 318 | 319 | static void 320 | fs_rm(const char *source) 321 | { 322 | char *argv[4]; 323 | 324 | argv[0] = "/bin/rm"; 325 | argv[1] = "-Rf"; 326 | argv[2] = (char *)source; 327 | argv[3] = NULL; 328 | 329 | cl_must_pass_( 330 | shell_out(argv), 331 | "Failed to cleanup the sandbox" 332 | ); 333 | } 334 | 335 | void 336 | cl_fs_cleanup(void) 337 | { 338 | clar_unsandbox(); 339 | clar_sandbox(); 340 | } 341 | #endif 342 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar/print.h: -------------------------------------------------------------------------------- 1 | 2 | static void clar_print_init(int test_count, int suite_count, const char *suite_names) 3 | { 4 | (void)test_count; 5 | printf("Loaded %d suites: %s\n", (int)suite_count, suite_names); 6 | printf("Started\n"); 7 | } 8 | 9 | static void clar_print_shutdown(int test_count, int suite_count, int error_count) 10 | { 11 | (void)test_count; 12 | (void)suite_count; 13 | (void)error_count; 14 | 15 | printf("\n\n"); 16 | clar_report_errors(); 17 | } 18 | 19 | static void clar_print_error(int num, const struct clar_error *error) 20 | { 21 | printf(" %d) Failure:\n", num); 22 | 23 | printf("%s::%s [%s:%d]\n", 24 | error->suite, 25 | error->test, 26 | error->file, 27 | error->line_number); 28 | 29 | printf(" %s\n", error->error_msg); 30 | 31 | if (error->description != NULL) { 32 | printf(" %s\n", error->description); 33 | } 34 | 35 | printf("\n"); 36 | fflush(stdout); 37 | } 38 | 39 | static void clar_print_ontest(const char *test_name, int test_number, enum cl_test_status status) 40 | { 41 | (void)test_name; 42 | (void)test_number; 43 | 44 | switch(status) { 45 | case CL_TEST_OK: 46 | printf("."); 47 | break; 48 | case CL_TEST_FAILURE: 49 | printf("F"); 50 | break; 51 | case CL_TEST_SKIP: 52 | printf("S"); 53 | break; 54 | } 55 | 56 | fflush(stdout); 57 | } 58 | 59 | static void clar_print_onsuite(const char *suite_name, int suite_index) 60 | { 61 | if (_clar.report_suite_names) { 62 | printf("\n%s", suite_name); 63 | } 64 | 65 | (void)suite_index; 66 | } 67 | 68 | static void clar_print_onabort(const char *msg, ...) 69 | { 70 | va_list argp; 71 | va_start(argp, msg); 72 | vfprintf(stderr, msg, argp); 73 | va_end(argp); 74 | } 75 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar/sandbox.h: -------------------------------------------------------------------------------- 1 | static char _clar_path[4096]; 2 | 3 | static int 4 | is_valid_tmp_path(const char *path) 5 | { 6 | STAT_T st; 7 | 8 | if (stat(path, &st) != 0) { 9 | return 0; 10 | } 11 | 12 | if (!S_ISDIR(st.st_mode)) { 13 | return 0; 14 | } 15 | 16 | return (access(path, W_OK) == 0); 17 | } 18 | 19 | static int 20 | find_tmp_path(char *buffer, size_t length) 21 | { 22 | #ifndef _WIN32 23 | static const size_t var_count = 5; 24 | static const char *env_vars[] = { 25 | "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE" 26 | }; 27 | 28 | size_t i; 29 | 30 | for (i = 0; i < var_count; ++i) { 31 | const char *env = getenv(env_vars[i]); 32 | if (!env) { 33 | continue; 34 | } 35 | 36 | if (is_valid_tmp_path(env)) { 37 | strncpy(buffer, env, length); 38 | return 0; 39 | } 40 | } 41 | 42 | /* If the environment doesn't say anything, try to use /tmp */ 43 | if (is_valid_tmp_path("/tmp")) { 44 | strncpy(buffer, "/tmp", length); 45 | return 0; 46 | } 47 | 48 | #else 49 | DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length); 50 | if (env_len > 0 && env_len < (DWORD)length) { 51 | return 0; 52 | } 53 | 54 | if (GetTempPath((DWORD)length, buffer)) { 55 | return 0; 56 | } 57 | #endif 58 | 59 | /* This system doesn't like us, try to use the current directory */ 60 | if (is_valid_tmp_path(".")) { 61 | strncpy(buffer, ".", length); 62 | return 0; 63 | } 64 | 65 | return -1; 66 | } 67 | 68 | static void clar_unsandbox(void) 69 | { 70 | if (_clar_path[0] == '\0') { 71 | return; 72 | } 73 | 74 | cl_must_pass(chdir("..")); 75 | 76 | fs_rm(_clar_path); 77 | } 78 | 79 | static int build_sandbox_path(void) 80 | { 81 | #ifdef CLAR_TMPDIR 82 | const char path_tail[] = CLAR_TMPDIR "_XXXXXX"; 83 | #else 84 | const char path_tail[] = "clar_tmp_XXXXXX"; 85 | #endif 86 | 87 | size_t len; 88 | 89 | if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0) { 90 | return -1; 91 | } 92 | 93 | len = strlen(_clar_path); 94 | 95 | #ifdef _WIN32 96 | { /* normalize path to POSIX forward slashes */ 97 | size_t i; 98 | for (i = 0; i < len; ++i) { 99 | if (_clar_path[i] == '\\') { 100 | _clar_path[i] = '/'; 101 | } 102 | } 103 | } 104 | #endif 105 | 106 | if (_clar_path[len - 1] != '/') { 107 | _clar_path[len++] = '/'; 108 | } 109 | 110 | strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len); 111 | 112 | #if defined(__MINGW32__) 113 | if (_mktemp(_clar_path) == NULL) { 114 | return -1; 115 | } 116 | 117 | if (mkdir(_clar_path, 0700) != 0) { 118 | return -1; 119 | } 120 | #elif defined(_WIN32) 121 | if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0) { 122 | return -1; 123 | } 124 | 125 | if (mkdir(_clar_path, 0700) != 0) { 126 | return -1; 127 | } 128 | #else 129 | if (mkdtemp(_clar_path) == NULL) { 130 | return -1; 131 | } 132 | #endif 133 | 134 | return 0; 135 | } 136 | 137 | static int clar_sandbox(void) 138 | { 139 | if (_clar_path[0] == '\0' && build_sandbox_path() < 0) { 140 | return -1; 141 | } 142 | 143 | if (chdir(_clar_path) != 0) { 144 | return -1; 145 | } 146 | 147 | return 0; 148 | } 149 | 150 | const char *clar_sandbox_path(void) 151 | { 152 | return _clar_path; 153 | } 154 | 155 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/clar_test.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | #ifndef __CLAR_TEST__ 8 | #define __CLAR_TEST__ 9 | 10 | /* Import the standard clar helper functions */ 11 | #include "clar.h" 12 | 13 | /* Your custom shared includes / defines here */ 14 | extern int global_test_counter; 15 | 16 | #include "helpers.h" 17 | #include "gql-idl-parser.h" 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/enums.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_enums__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_enums__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_enums__inline(void) 21 | { 22 | fixture = read_fixture("enums.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "enum"); 33 | cl_assert_equal_s("ProjectState", types[0].enum_type.name); 34 | cl_assert_equal_s("State of the project; either 'open' or 'closed'", types[0].enum_type.description); 35 | cl_assert_equal_i(2, types[0].enum_type.values.length); 36 | cl_assert_equal_s("CLOSED", types[0].enum_type.values.data[0].name); 37 | cl_assert_equal_s("The project is closed.", types[0].enum_type.values.data[0].description); 38 | cl_assert_equal_s("OPEN", types[0].enum_type.values.data[1].name); 39 | cl_assert_equal_s("The project is open.", types[0].enum_type.values.data[1].description); 40 | } 41 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright (c) Vicent Marti. All rights reserved. 4 | # 5 | # This file is part of clar, distributed under the ISC license. 6 | # For full terms see the included COPYING file. 7 | # 8 | 9 | from __future__ import with_statement 10 | from string import Template 11 | import re, fnmatch, os, codecs, pickle 12 | 13 | class Module(object): 14 | class Template(object): 15 | def __init__(self, module): 16 | self.module = module 17 | 18 | def _render_callback(self, cb): 19 | if not cb: 20 | return ' { NULL, NULL }' 21 | return ' { "%s", &%s }' % (cb['short_name'], cb['symbol']) 22 | 23 | class DeclarationTemplate(Template): 24 | def render(self): 25 | out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n" 26 | 27 | if self.module.initialize: 28 | out += "extern %s;\n" % self.module.initialize['declaration'] 29 | 30 | if self.module.cleanup: 31 | out += "extern %s;\n" % self.module.cleanup['declaration'] 32 | 33 | return out 34 | 35 | class CallbacksTemplate(Template): 36 | def render(self): 37 | out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name 38 | out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks) 39 | out += "\n};\n" 40 | return out 41 | 42 | class InfoTemplate(Template): 43 | def render(self): 44 | return Template( 45 | r""" 46 | { 47 | "${clean_name}", 48 | ${initialize}, 49 | ${cleanup}, 50 | ${cb_ptr}, ${cb_count}, ${enabled} 51 | }""" 52 | ).substitute( 53 | clean_name = self.module.clean_name(), 54 | initialize = self._render_callback(self.module.initialize), 55 | cleanup = self._render_callback(self.module.cleanup), 56 | cb_ptr = "_clar_cb_%s" % self.module.name, 57 | cb_count = len(self.module.callbacks), 58 | enabled = int(self.module.enabled) 59 | ) 60 | 61 | def __init__(self, name): 62 | self.name = name 63 | 64 | self.mtime = 0 65 | self.enabled = True 66 | self.modified = False 67 | 68 | def clean_name(self): 69 | return self.name.replace("_", "::") 70 | 71 | def _skip_comments(self, text): 72 | SKIP_COMMENTS_REGEX = re.compile( 73 | r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"', 74 | re.DOTALL | re.MULTILINE) 75 | 76 | def _replacer(match): 77 | s = match.group(0) 78 | return "" if s.startswith('/') else s 79 | 80 | return re.sub(SKIP_COMMENTS_REGEX, _replacer, text) 81 | 82 | def parse(self, contents): 83 | TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{" 84 | 85 | contents = self._skip_comments(contents) 86 | regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE) 87 | 88 | self.callbacks = [] 89 | self.initialize = None 90 | self.cleanup = None 91 | 92 | for (declaration, symbol, short_name) in regex.findall(contents): 93 | data = { 94 | "short_name" : short_name, 95 | "declaration" : declaration, 96 | "symbol" : symbol 97 | } 98 | 99 | if short_name == 'initialize': 100 | self.initialize = data 101 | elif short_name == 'cleanup': 102 | self.cleanup = data 103 | else: 104 | self.callbacks.append(data) 105 | 106 | return self.callbacks != [] 107 | 108 | def refresh(self, path): 109 | self.modified = False 110 | 111 | try: 112 | st = os.stat(path) 113 | 114 | # Not modified 115 | if st.st_mtime == self.mtime: 116 | return True 117 | 118 | self.modified = True 119 | self.mtime = st.st_mtime 120 | 121 | with codecs.open(path, encoding='utf-8') as fp: 122 | raw_content = fp.read() 123 | 124 | except IOError: 125 | return False 126 | 127 | return self.parse(raw_content) 128 | 129 | class TestSuite(object): 130 | 131 | def __init__(self, path): 132 | self.path = path 133 | 134 | def should_generate(self, path): 135 | if not os.path.isfile(path): 136 | return True 137 | 138 | if any(module.modified for module in self.modules.values()): 139 | return True 140 | 141 | return False 142 | 143 | def find_modules(self): 144 | modules = [] 145 | for root, _, files in os.walk(self.path): 146 | module_root = root[len(self.path):] 147 | module_root = [c for c in module_root.split(os.sep) if c] 148 | 149 | tests_in_module = fnmatch.filter(files, "*.c") 150 | 151 | for test_file in tests_in_module: 152 | full_path = os.path.join(root, test_file) 153 | module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_") 154 | 155 | modules.append((full_path, module_name)) 156 | 157 | return modules 158 | 159 | def load_cache(self): 160 | path = os.path.join(self.path, '.clarcache') 161 | cache = {} 162 | 163 | try: 164 | fp = open(path, 'rb') 165 | cache = pickle.load(fp) 166 | fp.close() 167 | except (IOError, ValueError): 168 | pass 169 | 170 | return cache 171 | 172 | def save_cache(self): 173 | path = os.path.join(self.path, '.clarcache') 174 | with open(path, 'wb') as cache: 175 | pickle.dump(self.modules, cache) 176 | 177 | def load(self, force = False): 178 | module_data = self.find_modules() 179 | self.modules = {} if force else self.load_cache() 180 | 181 | for path, name in module_data: 182 | if name not in self.modules: 183 | self.modules[name] = Module(name) 184 | 185 | if not self.modules[name].refresh(path): 186 | del self.modules[name] 187 | 188 | def disable(self, excluded): 189 | for exclude in excluded: 190 | for module in self.modules.values(): 191 | name = module.clean_name() 192 | if name.startswith(exclude): 193 | module.enabled = False 194 | module.modified = True 195 | 196 | def suite_count(self): 197 | return len(self.modules) 198 | 199 | def callback_count(self): 200 | return sum(len(module.callbacks) for module in self.modules.values()) 201 | 202 | def write(self): 203 | output = os.path.join(self.path, 'clar.suite') 204 | 205 | if not self.should_generate(output): 206 | return False 207 | 208 | with open(output, 'w') as data: 209 | for module in self.modules.values(): 210 | t = Module.DeclarationTemplate(module) 211 | data.write(t.render()) 212 | 213 | for module in self.modules.values(): 214 | t = Module.CallbacksTemplate(module) 215 | data.write(t.render()) 216 | 217 | suites = "static struct clar_suite _clar_suites[] = {" + ','.join( 218 | Module.InfoTemplate(module).render() for module in sorted(self.modules.values(), key=lambda module: module.name) 219 | ) + "\n};\n" 220 | 221 | data.write(suites) 222 | 223 | data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count()) 224 | data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count()) 225 | 226 | self.save_cache() 227 | return True 228 | 229 | if __name__ == '__main__': 230 | from optparse import OptionParser 231 | 232 | parser = OptionParser() 233 | parser.add_option('-f', '--force', action="store_true", dest='force', default=False) 234 | parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[]) 235 | 236 | options, args = parser.parse_args() 237 | 238 | for path in args or ['.']: 239 | suite = TestSuite(path) 240 | suite.load(options.force) 241 | suite.disable(options.excluded) 242 | if suite.write(): 243 | print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count())) 244 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/helpers.c: -------------------------------------------------------------------------------- 1 | #include "helpers.h" 2 | #include "clar.h" 3 | #include 4 | #include 5 | #include 6 | 7 | char *file2strl(const char *path, unsigned int *file_len_out) 8 | { 9 | FILE *file; 10 | 11 | if (!(file = fopen(path, "rb"))) { 12 | fprintf(stderr, "Unable to open file %s\n", path); 13 | return NULL; 14 | } 15 | 16 | if (-1 == fseek(file, 0, SEEK_END)) { 17 | fprintf(stderr, "Unable to seek file %s\n", path); 18 | return NULL; 19 | } 20 | 21 | int file_len; 22 | if (-1 == (file_len = ftell(file))) { 23 | fprintf(stderr, "Unable to ftell() file %s\n", path); 24 | return NULL; 25 | } 26 | 27 | if (-1 == fseek(file, 0, SEEK_SET)) { 28 | fprintf(stderr, "Unable to seek file %s\n", path); 29 | return NULL; 30 | } 31 | 32 | char *contents; 33 | if (!(contents = malloc(file_len + 1))) { 34 | fprintf(stderr, "Memory error!\n"); 35 | fclose(file); 36 | return NULL; 37 | } 38 | 39 | fread(contents, file_len, 1, file); 40 | fclose(file); 41 | 42 | contents[file_len] = '\0'; 43 | 44 | if (file_len_out) { 45 | *file_len_out = file_len + 1; 46 | } 47 | 48 | return contents; 49 | } 50 | 51 | char *file2str(const char *path) 52 | { 53 | return file2strl(path,NULL); 54 | } 55 | 56 | char * read_fixture(const char *filename) 57 | { 58 | const char *tex = cl_fixture(filename); 59 | return file2str(tex); 60 | } 61 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef __HELPERS__ 2 | #define __HELPERS__ 3 | 4 | char * read_fixture(const char *filename); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/input_objects.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_input_objects__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_input_objects__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_input_objects__inline(void) 21 | { 22 | fixture = read_fixture("input_objects.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "input_object"); 33 | cl_assert_equal_s("UpdateTopicsInput", types[0].input_object_type.name); 34 | cl_assert_equal_s("Autogenerated input type of UpdateTopics", types[0].input_object_type.description); 35 | cl_assert_equal_i(3, types[0].input_object_type.fields.length); 36 | cl_assert_equal_s("clientMutationId", types[0].input_object_type.fields.data[0].name); 37 | cl_assert_equal_s("A unique identifier for the client performing the mutation.", types[0].input_object_type.fields.data[0].description); 38 | cl_assert_equal_s("String", types[0].input_object_type.fields.data[0].type_info.name); 39 | cl_assert_equal_s("", types[0].input_object_type.fields.data[0].type_info.info); 40 | cl_assert_equal_s("repositoryId", types[0].input_object_type.fields.data[1].name); 41 | cl_assert_equal_s("The Node ID of the repository.", types[0].input_object_type.fields.data[1].description); 42 | cl_assert_equal_s("ID", types[0].input_object_type.fields.data[1].type_info.name); 43 | cl_assert_equal_s("!", types[0].input_object_type.fields.data[1].type_info.info); 44 | cl_assert_equal_s("topicNames", types[0].input_object_type.fields.data[2].name); 45 | cl_assert_equal_s("An array of topic names.", types[0].input_object_type.fields.data[2].description); 46 | cl_assert_equal_s("String", types[0].input_object_type.fields.data[2].type_info.name); 47 | cl_assert_equal_s("[!]!", types[0].input_object_type.fields.data[2].type_info.info); 48 | } 49 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/interfaces.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_interfaces__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_interfaces__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_interfaces__inline(void) 21 | { 22 | fixture = read_fixture("interfaces.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "interface"); 33 | cl_assert_equal_s("Closable", types[0].interface_type.name); 34 | cl_assert_equal_s("An object that can be closed", types[0].interface_type.description); 35 | cl_assert_equal_i(1, types[0].interface_type.fields.length); 36 | cl_assert_equal_s("closed", types[0].interface_type.fields.data[0].name); 37 | cl_assert_equal_s("`true` if the object is closed (definition of closed may depend on type)", types[0].interface_type.fields.data[0].description); 38 | cl_assert_equal_s("Boolean", types[0].interface_type.fields.data[0].type_info.name); 39 | cl_assert_equal_s("!", types[0].interface_type.fields.data[0].type_info.info); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) Vicent Marti. All rights reserved. 3 | * 4 | * This file is part of clar, distributed under the ISC license. 5 | * For full terms see the included COPYING file. 6 | */ 7 | 8 | #include "clar_test.h" 9 | 10 | /* 11 | * Minimal main() for clar tests. 12 | * 13 | * Modify this with any application specific setup or teardown that you need. 14 | * The only required line is the call to `clar_test(argc, argv)`, which will 15 | * execute the test suite. If you want to check the return value of the test 16 | * application, main() should return the same value returned by clar_test(). 17 | */ 18 | 19 | int global_test_counter = 0; 20 | 21 | #ifdef _WIN32 22 | int __cdecl main(int argc, char *argv[]) 23 | #else 24 | int main(int argc, char *argv[]) 25 | #endif 26 | { 27 | global_test_counter = 0; 28 | 29 | /* Run the test suite */ 30 | int ret = clar_test(argc, argv); 31 | 32 | /* cl_assert_equal_i(3, global_test_counter); */ 33 | 34 | return ret; 35 | } 36 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/objects.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_objects__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_objects__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_objects__inline(void) 21 | { 22 | fixture = read_fixture("objects.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "object"); 33 | cl_assert_equal_s(NULL, types[0].object_type.description); 34 | cl_assert_equal_s("CodeOfConduct", types[0].object_type.name); 35 | 36 | cl_assert_equal_s(types[1].typename, "object"); 37 | cl_assert_equal_s("The Code of Conduct for a repository", types[1].object_type.description); 38 | cl_assert_equal_s("CodeOfConduct", types[1].object_type.name); 39 | 40 | cl_assert_equal_s(types[2].typename, "object"); 41 | cl_assert_equal_s(NULL, types[2].object_type.description); 42 | cl_assert_equal_s("PushAllowance", types[2].object_type.name); 43 | cl_assert_equal_i(1, types[2].object_type.implements.length); 44 | cl_assert_equal_s("Node", types[2].object_type.implements.data[0]); 45 | 46 | cl_assert_equal_s(types[3].typename, "object"); 47 | cl_assert_equal_s(NULL, types[3].object_type.description); 48 | cl_assert_equal_s("Release", types[3].object_type.name); 49 | cl_assert_equal_i(2, types[3].object_type.implements.length); 50 | cl_assert_equal_s("Node", types[3].object_type.implements.data[0]); 51 | cl_assert_equal_s("UniformResourceLocatable", types[3].object_type.implements.data[1]); 52 | 53 | cl_assert_equal_s(types[4].typename, "object"); 54 | cl_assert_equal_s("The Code of Conduct for a repository", types[4].object_type.description); 55 | cl_assert_equal_s("CodeOfConduct", types[4].object_type.name); 56 | cl_assert_equal_i(1, types[4].object_type.fields.length); 57 | cl_assert_equal_s("body", types[4].object_type.fields.data[0].name); 58 | cl_assert_equal_s(NULL, types[4].object_type.fields.data[0].description); 59 | cl_assert_equal_i(0, types[4].object_type.fields.data[0].directives.length); 60 | 61 | cl_assert_equal_s(types[5].typename, "object"); 62 | cl_assert_equal_s("The Code of Conduct for a repository", types[5].object_type.description); 63 | cl_assert_equal_s("CodeOfConduct", types[5].object_type.name); 64 | cl_assert_equal_i(1, types[5].object_type.fields.length); 65 | cl_assert_equal_s("body", types[5].object_type.fields.data[0].name); 66 | cl_assert_equal_s("The body of the CoC", types[5].object_type.fields.data[0].description); 67 | cl_assert_equal_i(0, types[5].object_type.fields.data[0].directives.length); 68 | 69 | cl_assert_equal_s(types[6].typename, "object"); 70 | cl_assert_equal_s("key", types[6].object_type.fields.data[0].name); 71 | cl_assert_equal_s("String", types[6].object_type.fields.data[0].type_info.name); 72 | cl_assert_equal_s("!", types[6].object_type.fields.data[0].type_info.info); 73 | 74 | cl_assert_equal_s(types[7].typename, "object"); 75 | cl_assert_equal_s("edges", types[7].object_type.fields.data[0].name); 76 | cl_assert_equal_s("CommitCommentEdge", types[7].object_type.fields.data[0].type_info.name); 77 | cl_assert_equal_s("[]", types[7].object_type.fields.data[0].type_info.info); 78 | 79 | cl_assert_equal_s(types[8].typename, "object"); 80 | cl_assert_equal_s("suggestedReviewers", types[8].object_type.fields.data[0].name); 81 | cl_assert_equal_s("SuggestedReviewer", types[8].object_type.fields.data[0].type_info.name); 82 | cl_assert_equal_s("[]!", types[8].object_type.fields.data[0].type_info.info); 83 | 84 | cl_assert_equal_s(types[9].typename, "object"); 85 | cl_assert_equal_s("viewerCannotUpdateReasons", types[9].object_type.fields.data[0].name); 86 | cl_assert_equal_s("CommentCannotUpdateReason", types[9].object_type.fields.data[0].type_info.name); 87 | cl_assert_equal_s("[!]!", types[9].object_type.fields.data[0].type_info.info); 88 | 89 | cl_assert_equal_s(types[10].typename, "object"); 90 | cl_assert_equal_s("followers", types[10].object_type.fields.data[0].name); 91 | cl_assert_equal_s("FollowerConnection", types[10].object_type.fields.data[0].type_info.name); 92 | cl_assert_equal_s("!", types[10].object_type.fields.data[0].type_info.info); 93 | cl_assert_equal_i(2, types[10].object_type.fields.data[0].arguments.length); 94 | cl_assert_equal_s("Returns the elements in the list that come after the specified global ID.", types[10].object_type.fields.data[0].arguments.data[0].description); 95 | cl_assert_equal_s("after", types[10].object_type.fields.data[0].arguments.data[0].name); 96 | cl_assert_equal_s("String", types[10].object_type.fields.data[0].arguments.data[0].type_info.name); 97 | cl_assert_equal_s("", types[10].object_type.fields.data[0].arguments.data[0].type_info.info); 98 | cl_assert_equal_s("Returns the first _n_ elements from the list.", types[10].object_type.fields.data[0].arguments.data[1].description); 99 | cl_assert_equal_s("first", types[10].object_type.fields.data[0].arguments.data[1].name); 100 | cl_assert_equal_s("Int", types[10].object_type.fields.data[0].arguments.data[1].type_info.name); 101 | cl_assert_equal_s("!", types[10].object_type.fields.data[0].arguments.data[1].type_info.info); 102 | 103 | cl_assert_equal_s(types[11].typename, "object"); 104 | cl_assert_equal_s("User", types[11].object_type.name); 105 | cl_assert_equal_i(1, types[11].object_type.fields.data[0].directives.length); 106 | cl_assert_equal_s("deprecated", types[11].object_type.fields.data[0].directives.data[0].name); 107 | cl_assert_equal_i(0, types[11].object_type.fields.data[0].directives.data[0].arguments.length); 108 | 109 | cl_assert_equal_s(types[12].typename, "object"); 110 | cl_assert_equal_s("Issue", types[12].object_type.name); 111 | cl_assert_equal_i(0, types[12].object_type.fields.data[0].arguments.length); 112 | cl_assert_equal_i(1, types[12].object_type.fields.data[0].directives.length); 113 | cl_assert_equal_s("deprecated", types[12].object_type.fields.data[0].directives.data[0].name); 114 | cl_assert_equal_i(1, types[12].object_type.fields.data[0].directives.data[0].arguments.length); 115 | cl_assert_equal_s("reason", types[12].object_type.fields.data[0].directives.data[0].arguments.data[0].name); 116 | cl_assert_equal_s("Exposed database IDs will eventually be removed in favor of global Relay IDs.", types[12].object_type.fields.data[0].directives.data[0].arguments.data[0].value); 117 | 118 | cl_assert_equal_s(types[13].typename, "object"); 119 | cl_assert_equal_s("Team", types[13].object_type.name); 120 | cl_assert_equal_i(1, types[13].object_type.implements.length); 121 | cl_assert_equal_s("Node", types[13].object_type.implements.data[0]); 122 | 123 | cl_assert_equal_i(1, types[13].object_type.fields.length); 124 | cl_assert_equal_s("childTeams", types[13].object_type.fields.data[0].name); 125 | cl_assert_equal_i(1, types[13].object_type.fields.data[0].arguments.length); 126 | cl_assert_equal_s("immediateOnly", types[13].object_type.fields.data[0].arguments.data[0].name); 127 | cl_assert_equal_s("Boolean", types[13].object_type.fields.data[0].arguments.data[0].type_info.name); 128 | cl_assert_equal_s("", types[13].object_type.fields.data[0].arguments.data[0].type_info.info); 129 | cl_assert_equal_s("true", types[13].object_type.fields.data[0].arguments.data[0].default_value); 130 | } 131 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/scalars.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_scalars__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_scalars__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_scalars__inline(void) 21 | { 22 | fixture = read_fixture("scalars.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "scalar"); 33 | cl_assert_equal_s(NULL, types[0].scalar_type.description); 34 | cl_assert_equal_s("DateTime", types[0].scalar_type.name); 35 | 36 | cl_assert_equal_s(types[1].typename, "scalar"); 37 | cl_assert_equal_s("An ISO-8601 encoded UTC date string.", types[1].scalar_type.description); 38 | cl_assert_equal_s("DateTime", types[1].scalar_type.name); 39 | } 40 | -------------------------------------------------------------------------------- /graphql-idl-parser-ffi/test/unions.c: -------------------------------------------------------------------------------- 1 | #include "clar.h" 2 | #include "clar_test.h" 3 | #include 4 | #include 5 | 6 | static char *fixture; 7 | 8 | void test_unions__initialize(void) 9 | { 10 | global_test_counter++; 11 | } 12 | 13 | void test_unions__cleanup(void) 14 | { 15 | if (fixture != NULL) { 16 | free(fixture); 17 | } 18 | } 19 | 20 | void test_unions__inline(void) 21 | { 22 | fixture = read_fixture("unions.graphql"); 23 | 24 | GraphQLTypes* types = NULL; 25 | size_t types_len = 0; 26 | uint8_t err; 27 | 28 | err = gqlidl_parse_schema(fixture, &types, &types_len); 29 | 30 | cl_assert_equal_i(err, 0); 31 | 32 | cl_assert_equal_s(types[0].typename, "union"); 33 | cl_assert_equal_s("ReferencedSubject", types[0].union_type.name); 34 | cl_assert_equal_s("Any referencable object", types[0].union_type.description); 35 | cl_assert_equal_i(2, types[0].union_type.values.length); 36 | cl_assert_equal_s("Issue", types[0].union_type.values.data[0]); 37 | cl_assert_equal_s("PullRequest", types[0].union_type.values.data[1]); 38 | } 39 | -------------------------------------------------------------------------------- /script/compile: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | cd graphql-idl-parser-ffi 6 | 7 | cargo update 8 | cargo build 9 | gcc -O3 -DDEBUG -o example/example example/example.c -std=c11 -Wextra -Wall -pedantic -Werror -Wfatal-errors -Wstrict-aliasing -Iincludes -Ltarget/debug -lgraphqlidlparser 10 | 11 | ./example/example 12 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | rustup default nightly 6 | 7 | echo "Formatting lib..." 8 | sudo cargo fmt 9 | 10 | echo "Formatting ffi..." 11 | cd graphql-idl-parser-ffi 12 | sudo cargo fmt 13 | cd .. 14 | 15 | echo "Formatting C..." 16 | for file in `find graphql-idl-parser-ffi/example graphql-idl-parser-ffi/include -name '*.c' -o -name '*.h'` ; do 17 | astyle --indent=spaces=2 --style=1tbs --keep-one-line-blocks -n $file 18 | done 19 | 20 | rustup default stable 21 | 22 | git add . 23 | git commit -m "Formatting" 24 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | test_dir=$(pwd)/test 6 | 7 | cd graphql-idl-parser-ffi 8 | 9 | lib_dir=$(pwd)/target/debug 10 | includes_dir=$(pwd)/includes 11 | 12 | cargo update 13 | cargo build 14 | 15 | cd test 16 | 17 | python generate.py . 18 | 19 | if [[ "$OSTYPE" == "darwin"* ]]; then 20 | ld_extra="-Wl,-rpath,$lib_dir" 21 | else 22 | ld_extra="-Wl,-rpath=$lib_dir" 23 | fi 24 | 25 | # 26 | gcc -I. -I$includes_dir -L$lib_dir $ld_extra -Wall -o gql-idl-parser-test clar.c main.c helpers.c scalars.c objects.c enums.c interfaces.c unions.c input_objects.c -lgraphqlidlparser -DCLAR_FIXTURE_PATH=\"$test_dir\" 27 | 28 | ./gql-idl-parser-test 29 | 30 | # valgrind --leak-check=full --dsymutil=yes --error-exitcode=1 ./gql-idl-parser-test >/dev/null 31 | -------------------------------------------------------------------------------- /src/gqlidl.lalrpop: -------------------------------------------------------------------------------- 1 | use type_definition::*; 2 | 3 | grammar; 4 | 5 | // TODO: missing others types 6 | pub schema: Vec = 7 | => <>; 8 | 9 | TypeDefinition: TypeDefinition = { 10 | ScalarTypeDefinition, 11 | ObjectTypeDefinition, 12 | EnumTypeDefinition, 13 | InterfaceTypeDefinition, 14 | UnionTypeDefinition, 15 | InputObjectTypeDefinition, 16 | }; 17 | 18 | ScalarTypeDefinition: TypeDefinition = { 19 | "scalar" => { 20 | TypeDefinition::ScalarType(GraphQLScalar::new(d, n)) 21 | } 22 | }; 23 | 24 | ObjectTypeDefinition: TypeDefinition = { 25 | "type" "{" "}" => { 26 | TypeDefinition::ObjectType(GraphQLObject::new(d, n, i, r, f)) 27 | }, 28 | }; 29 | 30 | EnumTypeDefinition: TypeDefinition = { 31 | "enum" "{" )+> "}" => { 32 | TypeDefinition::EnumType(GraphQLEnum::new(d, n, r, f)) 33 | } 34 | }; 35 | 36 | InterfaceTypeDefinition: TypeDefinition = { 37 | "interface" "{" "}" => { 38 | TypeDefinition::InterfaceType(GraphQLInterface::new(d, n, r, f)) 39 | }, 40 | 41 | }; 42 | 43 | UnionTypeDefinition: TypeDefinition = { 44 | "union" => { 45 | // This is dumb, but there seemes to be some ambiguity conflict with "=" 46 | let res: Vec = t.split("|").map(|s| s.trim().to_string()).collect(); 47 | TypeDefinition::UnionType(GraphQLUnion::new(d, n, r, res)) 48 | } 49 | }; 50 | 51 | InputObjectTypeDefinition: TypeDefinition = { 52 | "input" "{" "}" => { 53 | TypeDefinition::InputObjectType(GraphQLInputObject::new(d, n, r, f)) 54 | }, 55 | }; 56 | 57 | ImplementsInterfaces: Vec = { 58 | "implements" > => { 59 | i 60 | } 61 | }; 62 | 63 | Fields: Vec = { 64 | => { 65 | f 66 | } 67 | }; 68 | 69 | FieldDefinition: GraphQLField = { 70 | ":" => { 71 | GraphQLField::new(d, n, t, a, r) 72 | } 73 | }; 74 | 75 | EnumValueDefinition: GraphQLValue = { 76 | => { 77 | GraphQLValue::new(d, n, r) 78 | } 79 | }; 80 | 81 | FieldType: FieldType = { 82 | => { 83 | FieldType { 84 | name: n, 85 | info: match r { 86 | None => TypeInfo::Nullable, 87 | Some(r) => TypeInfo::NonNullable, 88 | } 89 | } 90 | }, 91 | "[" "]" => { 92 | FieldType { 93 | name: n, 94 | info: match r { 95 | None => match l { 96 | None => TypeInfo::NullableListNullableElements, 97 | Some(l) => TypeInfo::NonNullableListNullableElements 98 | }, 99 | Some(r) => match l { 100 | None => TypeInfo::NonNullableListNullableElements, 101 | Some(l) => TypeInfo::NonNullableListNonNullableElements 102 | } 103 | } 104 | } 105 | } 106 | }; 107 | 108 | ArgumentsDefinition: Vec = { 109 | "(" ")" => { 110 | i 111 | } 112 | }; 113 | 114 | InputValueDefinition: GraphQLArgument = { 115 | ":" => { 116 | GraphQLArgument::new(d, n, t, v, r) 117 | } 118 | }; 119 | 120 | Directives: Vec = { 121 | => { 122 | d 123 | } 124 | }; 125 | 126 | Directive: GraphQLDirective = { 127 | "@" => { 128 | GraphQLDirective::new(n, d) 129 | } 130 | }; 131 | 132 | DirectiveArgumentsDefinition: Vec = { 133 | "(" ")" => { 134 | d 135 | } 136 | }; 137 | 138 | DirectiveArgumentDefinition: GraphQLDirectiveArgument = { 139 | => { 140 | GraphQLDirectiveArgument::new(n, q) 141 | }, 142 | }; 143 | 144 | DirectiveArgumentTypeDefinition: String = { 145 | ":" => { 146 | q 147 | } 148 | }; 149 | 150 | Comma: Vec = { 151 | Comma1? => <>.unwrap_or(vec![]) 152 | }; 153 | 154 | Comma1: Vec = { 155 | => vec![t], 156 | > "," => { 157 | let mut v = v; 158 | v.push(t); 159 | v 160 | } 161 | }; 162 | 163 | Pipe: Vec = { 164 | Pipe1? => <>.unwrap_or(vec![]) 165 | }; 166 | 167 | Pipe1: Vec = { 168 | => vec![t], 169 | > "|" => { 170 | let mut v = v; 171 | v.push(t); 172 | v 173 | } 174 | }; 175 | 176 | Description: String = { 177 | => { 178 | d.join(" ") 179 | } 180 | }; 181 | 182 | // NOTE: These are all gross, can they be improved? 183 | Name: String = r"[_A-Za-z][_0-9A-Za-z]*" => <>.to_owned(); 184 | 185 | EQUALS_SIGN: String = => "".to_string(); 186 | 187 | DefaultValue: String = => s[1..s.len()].trim().to_owned(); 188 | CommentedString: String = => s[1..s.len()].trim().to_owned(); 189 | QuotedString: String = => s[1..s.len()-1].to_owned(); 190 | 191 | FieldName: String = { 192 | r"[_A-Za-z][_0-9A-Za-z]*" => <>.to_owned(), 193 | "scalar" => "scalar".to_owned(), 194 | "type" => "type".to_owned(), 195 | "enum" => "enum".to_owned(), 196 | "interface" => "interface".to_owned(), 197 | "union" => "union".to_owned(), 198 | "input" => "input".to_owned(), 199 | }; 200 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate regex; 2 | 3 | #[macro_use] 4 | mod macros; 5 | pub mod gqlidl; // synthesized by LALRPOP 6 | pub mod type_definition; 7 | 8 | #[cfg(test)] 9 | mod tests; 10 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_graphql_meta_methods { 2 | ($($type_: ty),*) => { 3 | $( 4 | impl $type_ { 5 | pub fn description(&self) -> Option<&str> { self.description.as_ref().map(|s| s.as_ref()) } 6 | 7 | pub fn name(&self) -> &str { self.name.as_ref() } 8 | } 9 | )* 10 | }; 11 | } 12 | 13 | macro_rules! impl_graphql_directive_methods { 14 | ($($type_: ty),*) => { 15 | $( 16 | impl $type_ { 17 | pub fn directives(&self) -> Option> { 18 | self.directives.clone() 19 | } 20 | } 21 | )* 22 | }; 23 | } 24 | 25 | macro_rules! impl_graphql_type_methods { 26 | ($($type_: ty),*) => { 27 | $( 28 | impl $type_ { 29 | pub fn typeinfo(&self) -> FieldType { 30 | self.typeinfo.to_owned() 31 | } 32 | } 33 | )* 34 | }; 35 | } 36 | 37 | macro_rules! impl_graphql_objects_common_methods { 38 | ( 39 | $( 40 | $x:ident:$y:ident 41 | ),* 42 | ) => { 43 | pub fn description(&self) -> Option<&str> { 44 | match *self { 45 | $( 46 | TypeDefinition::$x($y{ ref description, .. }) => { 47 | description.as_ref().map(|s| s.as_ref()) 48 | } 49 | ),* 50 | } 51 | } 52 | 53 | pub fn name(&self) -> &str { 54 | match *self { 55 | $( 56 | TypeDefinition::$x($y{ ref name, .. }) => &name 57 | ),* 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | 4 | use gqlidl; 5 | 6 | #[test] 7 | #[allow(unused)] 8 | fn sanity_check() { 9 | let mut array = [ 10 | "scalars", 11 | "objects", 12 | "enums", 13 | "interfaces", 14 | "unions", 15 | "input_objects", 16 | ]; 17 | let mut contents = String::new(); 18 | for (_, f) in array.iter_mut().enumerate() { 19 | let mut file = File::open(format!("test/{}.graphql", f)).expect("Unable to open file"); 20 | file.read_to_string(&mut contents); 21 | } 22 | let definitions = gqlidl::parse_schema(contents.as_str()).unwrap(); 23 | } 24 | 25 | #[test] 26 | #[allow(unused)] 27 | fn github_sanity_check() { 28 | let mut file = File::open("test/github.graphql").expect("Unable to open file"); 29 | let mut contents = String::new(); 30 | file.read_to_string(&mut contents); 31 | 32 | let definitions = gqlidl::parse_schema(contents.as_str()).unwrap(); 33 | } 34 | 35 | #[test] 36 | fn scalar_no_description() { 37 | let def = gqlidl::parse_schema("scalar DateTime") 38 | .unwrap() 39 | .pop() 40 | .unwrap(); 41 | 42 | assert_eq!(None, def.description()); 43 | assert_eq!("scalar", def.typename()); 44 | assert_eq!("DateTime", def.name()); 45 | } 46 | 47 | #[test] 48 | fn scalar_with_description() { 49 | let def = gqlidl::parse_schema("# An ISO-8601 encoded UTC date string.\nscalar DateTime") 50 | .unwrap() 51 | .pop() 52 | .unwrap(); 53 | 54 | assert_eq!( 55 | "An ISO-8601 encoded UTC date string.", 56 | def.description().unwrap() 57 | ); 58 | assert_eq!("scalar", def.typename()); 59 | assert_eq!("DateTime", def.name()); 60 | } 61 | 62 | #[test] 63 | fn scalar_with_tricky_description() { 64 | let def = gqlidl::parse_schema( 65 | "# An ISO-8601 encoded UTC, scalar, date string.\nscalar DateTime", 66 | ).unwrap() 67 | .pop() 68 | .unwrap(); 69 | 70 | assert_eq!( 71 | "An ISO-8601 encoded UTC, scalar, date string.", 72 | def.description().unwrap() 73 | ); 74 | assert_eq!("scalar", def.typename()); 75 | assert_eq!("DateTime", def.name()); 76 | } 77 | 78 | #[test] 79 | fn type_no_description() { 80 | let def = gqlidl::parse_schema("type CodeOfConduct {}") 81 | .unwrap() 82 | .pop() 83 | .unwrap(); 84 | 85 | assert_eq!(None, def.description()); 86 | assert_eq!("object", def.typename()); 87 | assert_eq!("CodeOfConduct", def.name()); 88 | } 89 | 90 | #[test] 91 | fn type_with_description() { 92 | let def = gqlidl::parse_schema( 93 | "# The Code of Conduct for a repository\ntype CodeOfConduct {}", 94 | ).unwrap() 95 | .pop() 96 | .unwrap(); 97 | 98 | assert_eq!( 99 | "The Code of Conduct for a repository", 100 | def.description().unwrap() 101 | ); 102 | assert_eq!("object", def.typename()); 103 | assert_eq!("CodeOfConduct", def.name()); 104 | assert_eq!(None, def.implements()); 105 | } 106 | 107 | #[test] 108 | fn type_with_one_implements() { 109 | let def = gqlidl::parse_schema("type PushAllowance implements Node {}") 110 | .unwrap() 111 | .pop() 112 | .unwrap(); 113 | 114 | assert_eq!(None, def.description()); 115 | assert_eq!("object", def.typename()); 116 | assert_eq!("PushAllowance", def.name()); 117 | 118 | let implement = def.implements().unwrap().pop().unwrap(); 119 | 120 | assert_eq!("Node", implement); 121 | } 122 | 123 | #[test] 124 | fn type_with_multiple_implements() { 125 | let def = gqlidl::parse_schema("type Release implements Node, UniformResourceLocatable {}") 126 | .unwrap() 127 | .pop() 128 | .unwrap(); 129 | 130 | assert_eq!(None, def.description()); 131 | assert_eq!("object", def.typename()); 132 | assert_eq!("Release", def.name()); 133 | 134 | let mut implement = def.implements().unwrap().remove(0); 135 | 136 | assert_eq!("Node", implement); 137 | implement = def.implements().unwrap().remove(1); 138 | assert_eq!("UniformResourceLocatable", implement); 139 | } 140 | 141 | #[test] 142 | fn type_with_field() { 143 | let def = gqlidl::parse_schema( 144 | "# The Code of Conduct for a repository\ntype CodeOfConduct { body: String }", 145 | ).unwrap() 146 | .pop() 147 | .unwrap(); 148 | 149 | assert_eq!( 150 | "The Code of Conduct for a repository", 151 | def.description().unwrap() 152 | ); 153 | assert_eq!("object", def.typename()); 154 | assert_eq!("CodeOfConduct", def.name()); 155 | 156 | let field = def.fields().unwrap().pop().unwrap(); 157 | 158 | assert_eq!(None, field.description()); 159 | assert_eq!("body", field.name()); 160 | assert_eq!("String", field.typeinfo().name()); 161 | assert_eq!("", field.typeinfo().info()); 162 | assert_eq!(None, field.arguments()); 163 | } 164 | 165 | #[test] 166 | fn type_with_field_and_description() { 167 | let def = gqlidl::parse_schema( 168 | "# The Code of Conduct for a repository\ntype CodeOfConduct { \n# The body of the CoC\n body: String }", 169 | ).unwrap() 170 | .pop() 171 | .unwrap(); 172 | 173 | assert_eq!( 174 | "The Code of Conduct for a repository", 175 | def.description().unwrap() 176 | ); 177 | assert_eq!("object", def.typename()); 178 | assert_eq!("CodeOfConduct", def.name()); 179 | 180 | let field = def.fields().unwrap().pop().unwrap(); 181 | 182 | assert_eq!("The body of the CoC", field.description().unwrap()); 183 | assert_eq!("body", field.name()); 184 | assert_eq!("String", field.typeinfo().name()); 185 | assert_eq!("", field.typeinfo().info()); 186 | } 187 | 188 | #[test] 189 | fn type_with_required_field() { 190 | let def = gqlidl::parse_schema( 191 | "# The Code of Conduct for a repository\ntype CodeOfConduct { key: String! }", 192 | ).unwrap() 193 | .pop() 194 | .unwrap(); 195 | 196 | assert_eq!( 197 | "The Code of Conduct for a repository", 198 | def.description().unwrap() 199 | ); 200 | assert_eq!("object", def.typename()); 201 | assert_eq!("CodeOfConduct", def.name()); 202 | 203 | let field = def.fields().unwrap().pop().unwrap(); 204 | 205 | assert_eq!(None, field.description()); 206 | assert_eq!("key", field.name()); 207 | assert_eq!("String", field.typeinfo().name()); 208 | assert_eq!("!", field.typeinfo().info()); 209 | } 210 | 211 | #[test] 212 | fn type_with_nullable_field_list() { 213 | let def = gqlidl::parse_schema( 214 | "type CommitCommentConnection { edges: [CommitCommentEdge] }", 215 | ).unwrap() 216 | .pop() 217 | .unwrap(); 218 | 219 | assert_eq!(None, def.description()); 220 | assert_eq!("object", def.typename()); 221 | assert_eq!("CommitCommentConnection", def.name()); 222 | 223 | let field = def.fields().unwrap().pop().unwrap(); 224 | 225 | assert_eq!(None, field.description()); 226 | assert_eq!("edges", field.name()); 227 | assert_eq!("CommitCommentEdge", field.typeinfo().name()); 228 | assert_eq!("[]", field.typeinfo().info()); 229 | } 230 | 231 | #[test] 232 | fn type_with_non_nullable_field_non_nullable_list() { 233 | let def = gqlidl::parse_schema( 234 | "type CommitComment { viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! }", 235 | ).unwrap() 236 | .pop() 237 | .unwrap(); 238 | 239 | assert_eq!(None, def.description()); 240 | assert_eq!("object", def.typename()); 241 | assert_eq!("CommitComment", def.name()); 242 | 243 | let field = def.fields().unwrap().pop().unwrap(); 244 | 245 | assert_eq!(None, field.description()); 246 | assert_eq!("viewerCannotUpdateReasons", field.name()); 247 | assert_eq!("CommentCannotUpdateReason", field.typeinfo().name()); 248 | assert_eq!("[!]!", field.typeinfo().info()); 249 | } 250 | 251 | #[test] 252 | fn type_with_nullable_field_non_nullable_list() { 253 | let def = gqlidl::parse_schema( 254 | "type PullRequest { suggestedReviewers: [SuggestedReviewer]! }", 255 | ).unwrap() 256 | .pop() 257 | .unwrap(); 258 | 259 | assert_eq!(None, def.description()); 260 | assert_eq!("object", def.typename()); 261 | assert_eq!("PullRequest", def.name()); 262 | 263 | let field = def.fields().unwrap().pop().unwrap(); 264 | 265 | assert_eq!(None, field.description()); 266 | assert_eq!("suggestedReviewers", field.name()); 267 | assert_eq!("SuggestedReviewer", field.typeinfo().name()); 268 | assert_eq!("[]!", field.typeinfo().info()); 269 | } 270 | 271 | #[test] 272 | fn type_with_non_nullable_connection() { 273 | let def = gqlidl::parse_schema( 274 | "type User { 275 | # A list of users the given user is followed by. 276 | followers( 277 | # Returns the elements in the list that come after the specified global ID. 278 | after: String 279 | 280 | # Returns the first _n_ elements from the list. 281 | first: Int! 282 | ): FollowerConnection! 283 | } 284 | ", 285 | ).unwrap() 286 | .pop() 287 | .unwrap(); 288 | 289 | assert_eq!(None, def.description()); 290 | assert_eq!("object", def.typename()); 291 | assert_eq!("User", def.name()); 292 | 293 | let connection = def.fields().unwrap().pop().unwrap(); 294 | 295 | assert_eq!( 296 | "A list of users the given user is followed by.", 297 | connection.description().unwrap() 298 | ); 299 | assert_eq!("followers", connection.name()); 300 | assert_eq!("FollowerConnection", connection.typeinfo().name()); 301 | assert_eq!("!", connection.typeinfo().info()); 302 | 303 | let mut argument = connection.arguments().unwrap().remove(0); 304 | 305 | assert_eq!( 306 | "Returns the elements in the list that come after the specified global ID.", 307 | argument.description().unwrap() 308 | ); 309 | assert_eq!("String", argument.typeinfo().name()); 310 | assert_eq!("", argument.typeinfo().info()); 311 | 312 | argument = connection.arguments().unwrap().remove(1); 313 | 314 | assert_eq!( 315 | "Returns the first _n_ elements from the list.", 316 | argument.description().unwrap() 317 | ); 318 | assert_eq!("Int", argument.typeinfo().name()); 319 | assert_eq!("!", argument.typeinfo().info()); 320 | } 321 | 322 | #[test] 323 | fn type_with_deprecated_field() { 324 | let def = gqlidl::parse_schema( 325 | "type User { 326 | databaseId: Int @deprecated 327 | } 328 | ", 329 | ).unwrap() 330 | .pop() 331 | .unwrap(); 332 | 333 | assert_eq!(None, def.description()); 334 | assert_eq!("object", def.typename()); 335 | assert_eq!("User", def.name()); 336 | 337 | let field = def.fields().unwrap().pop().unwrap(); 338 | 339 | assert_eq!("databaseId", field.name()); 340 | assert_eq!("Int", field.typeinfo().name()); 341 | assert_eq!("", field.typeinfo().info()); 342 | let directive = field.directives().unwrap().pop().unwrap(); 343 | assert_eq!("deprecated", directive.name()); 344 | assert_eq!(None, directive.arguments()); 345 | } 346 | 347 | #[test] 348 | fn type_with_deprecated_field_and_reason() { 349 | let def = gqlidl::parse_schema( 350 | "type User { 351 | databaseId: Int @deprecated(reason: \"Exposed database IDs will eventually be removed in favor of global Relay IDs.\") 352 | } 353 | ", 354 | ).unwrap() 355 | .pop() 356 | .unwrap(); 357 | 358 | assert_eq!(None, def.description()); 359 | assert_eq!("object", def.typename()); 360 | assert_eq!("User", def.name()); 361 | 362 | let field = def.fields().unwrap().pop().unwrap(); 363 | 364 | assert_eq!("databaseId", field.name()); 365 | assert_eq!("Int", field.typeinfo().name()); 366 | assert_eq!("", field.typeinfo().info()); 367 | let directive = field.directives().unwrap().pop().unwrap(); 368 | assert_eq!("deprecated", directive.name()); 369 | let arg = directive.arguments().unwrap().pop().unwrap(); 370 | assert_eq!("reason", arg.name()); 371 | assert_eq!( 372 | "Exposed database IDs will eventually be removed in favor of global Relay IDs.", 373 | arg.value().unwrap() 374 | ); 375 | } 376 | 377 | #[test] 378 | fn type_with_multiline_field_description() { 379 | let def = gqlidl::parse_schema( 380 | " 381 | # Represents a range of information from a Git blame. 382 | type BlameRange { 383 | # Identifies the recency of the change, from 1 (new) to 10 (old). This is 384 | # calculated as a 2-quantile and determines the length of distance between the 385 | # median age of all the changes in the file and the recency of the current 386 | # range's change. 387 | age: Int! 388 | } 389 | ", 390 | ).unwrap() 391 | .pop() 392 | .unwrap(); 393 | 394 | let field = def.fields().unwrap().remove(0); 395 | 396 | assert_eq!( 397 | "Identifies the recency of the change, from 1 (new) to 10 (old). This is calculated as a 2-quantile and determines the length of distance between the median age of all the changes in the file and the recency of the current range's change.", 398 | field.description().unwrap() 399 | ); 400 | assert_eq!("age", field.name()); 401 | assert_eq!("Int", field.typeinfo().name()); 402 | assert_eq!("!", field.typeinfo().info()); 403 | } 404 | 405 | #[test] 406 | fn enum_with_fields() { 407 | let def = gqlidl::parse_schema( 408 | " 409 | # State of the project; either 'open' or 'closed' 410 | enum ProjectState { 411 | # The project is closed. 412 | CLOSED 413 | 414 | # The project is open. 415 | OPEN 416 | } 417 | ", 418 | ).unwrap() 419 | .pop() 420 | .unwrap(); 421 | 422 | assert_eq!( 423 | "State of the project; either 'open' or 'closed'", 424 | def.description().unwrap() 425 | ); 426 | assert_eq!("enum", def.typename()); 427 | assert_eq!("ProjectState", def.name()); 428 | 429 | let mut value = def.values().unwrap().remove(0); 430 | 431 | assert_eq!("The project is closed.", value.description().unwrap()); 432 | assert_eq!("CLOSED", value.name()); 433 | 434 | value = def.values().unwrap().remove(1); 435 | 436 | assert_eq!("The project is open.", value.description().unwrap()); 437 | assert_eq!("OPEN", value.name()); 438 | } 439 | 440 | #[test] 441 | fn interface_with_field() { 442 | let def = gqlidl::parse_schema( 443 | " 444 | # An object that can be closed 445 | interface Closable { 446 | # `true` if the object is closed (definition of closed may depend on type) 447 | closed: Boolean! 448 | } 449 | ", 450 | ).unwrap() 451 | .pop() 452 | .unwrap(); 453 | 454 | assert_eq!("An object that can be closed", def.description().unwrap()); 455 | assert_eq!("interface", def.typename()); 456 | assert_eq!("Closable", def.name()); 457 | 458 | let field = def.fields().unwrap().pop().unwrap(); 459 | 460 | assert_eq!( 461 | "`true` if the object is closed (definition of closed may depend on type)", 462 | field.description().unwrap() 463 | ); 464 | assert_eq!("closed", field.name()); 465 | assert_eq!("Boolean", field.typeinfo().name()); 466 | assert_eq!("!", field.typeinfo().info()); 467 | } 468 | 469 | #[test] 470 | fn union_with_descriptions() { 471 | let def = gqlidl::parse_schema( 472 | " 473 | # Any referencable object 474 | union ReferencedSubject = Issue | PullRequest 475 | ", 476 | ).unwrap() 477 | .pop() 478 | .unwrap(); 479 | 480 | assert_eq!("Any referencable object", def.description().unwrap()); 481 | assert_eq!("union", def.typename()); 482 | assert_eq!("ReferencedSubject", def.name()); 483 | 484 | let mut _type = def.types().unwrap().remove(0); 485 | 486 | assert_eq!("Issue", _type); 487 | 488 | _type = def.types().unwrap().remove(1); 489 | 490 | assert_eq!("PullRequest", _type); 491 | } 492 | 493 | #[test] 494 | fn input_object_with_descriptions() { 495 | let def = gqlidl::parse_schema( 496 | " 497 | # Autogenerated input type of UpdateTopics 498 | input UpdateTopicsInput { 499 | # A unique identifier for the client performing the mutation. 500 | clientMutationId: String 501 | 502 | # An array of topic names. 503 | topicNames: [String!]! 504 | } 505 | ", 506 | ).unwrap() 507 | .pop() 508 | .unwrap(); 509 | 510 | assert_eq!( 511 | "Autogenerated input type of UpdateTopics", 512 | def.description().unwrap() 513 | ); 514 | assert_eq!("input_object", def.typename()); 515 | assert_eq!("UpdateTopicsInput", def.name()); 516 | 517 | let mut field = def.fields().unwrap().remove(0); 518 | 519 | assert_eq!( 520 | "A unique identifier for the client performing the mutation.", 521 | field.description().unwrap() 522 | ); 523 | assert_eq!("clientMutationId", field.name()); 524 | assert_eq!("String", field.typeinfo().name()); 525 | assert_eq!("", field.typeinfo().info()); 526 | 527 | field = def.fields().unwrap().remove(1); 528 | 529 | assert_eq!("An array of topic names.", field.description().unwrap()); 530 | assert_eq!("topicNames", field.name()); 531 | assert_eq!("String", field.typeinfo().name()); 532 | assert_eq!("[!]!", field.typeinfo().info()); 533 | } 534 | 535 | #[test] 536 | fn input_object_with_special_field_name() { 537 | let def = gqlidl::parse_schema( 538 | " 539 | input RequestReviewsInput { 540 | # Add users to the set rather than replace. 541 | union: Boolean 542 | } 543 | ", 544 | ).unwrap() 545 | .pop() 546 | .unwrap(); 547 | 548 | assert_eq!(None, def.description()); 549 | assert_eq!("input_object", def.typename()); 550 | assert_eq!("RequestReviewsInput", def.name()); 551 | 552 | let field = def.fields().unwrap().remove(0); 553 | 554 | assert_eq!( 555 | "Add users to the set rather than replace.", 556 | field.description().unwrap() 557 | ); 558 | assert_eq!("union", field.name()); 559 | assert_eq!("Boolean", field.typeinfo().name()); 560 | assert_eq!("", field.typeinfo().info()); 561 | } 562 | -------------------------------------------------------------------------------- /src/type_definition.rs: -------------------------------------------------------------------------------- 1 | static SCALAR: &'static str = "scalar"; 2 | static OBJECT: &'static str = "object"; 3 | static ENUM: &'static str = "enum"; 4 | static INTERFACE: &'static str = "interface"; 5 | static UNION: &'static str = "union"; 6 | static INPUT_OBJECT: &'static str = "input_object"; 7 | 8 | pub struct GraphQLScalar { 9 | description: Option, 10 | name: String, 11 | } 12 | 13 | pub struct GraphQLObject { 14 | description: Option, 15 | name: String, 16 | implements: Option>, 17 | directives: Option>, 18 | fields: Option>, 19 | } 20 | 21 | pub struct GraphQLEnum { 22 | description: Option, 23 | name: String, 24 | directives: Option>, 25 | values: Vec, 26 | } 27 | 28 | pub struct GraphQLInterface { 29 | description: Option, 30 | name: String, 31 | directives: Option>, 32 | fields: Option>, 33 | } 34 | 35 | pub struct GraphQLUnion { 36 | description: Option, 37 | name: String, 38 | directives: Option>, 39 | types: Vec, 40 | } 41 | 42 | pub struct GraphQLInputObject { 43 | description: Option, 44 | name: String, 45 | directives: Option>, 46 | fields: Option>, 47 | } 48 | 49 | pub enum TypeDefinition { 50 | ScalarType(GraphQLScalar), 51 | ObjectType(GraphQLObject), 52 | EnumType(GraphQLEnum), 53 | InterfaceType(GraphQLInterface), 54 | UnionType(GraphQLUnion), 55 | InputObjectType(GraphQLInputObject), 56 | } 57 | 58 | #[derive(Clone)] 59 | pub struct GraphQLField { 60 | description: Option, 61 | name: String, 62 | typeinfo: FieldType, 63 | arguments: Option>, 64 | directives: Option>, 65 | } 66 | 67 | #[derive(Clone)] 68 | pub struct GraphQLValue { 69 | description: Option, 70 | name: String, 71 | directives: Option>, 72 | } 73 | 74 | #[derive(Clone, Debug, PartialEq)] 75 | pub struct GraphQLArgument { 76 | description: Option, 77 | name: String, 78 | typeinfo: FieldType, 79 | default: Option, 80 | directives: Option>, 81 | } 82 | 83 | #[derive(Clone, Debug, PartialEq)] 84 | pub struct GraphQLDirective { 85 | name: String, 86 | arguments: Option>, 87 | } 88 | 89 | #[derive(Clone, Debug, PartialEq)] 90 | pub struct GraphQLDirectiveArgument { 91 | name: String, 92 | value: Option, 93 | } 94 | 95 | #[derive(Clone, Debug, PartialEq)] 96 | pub struct FieldType { 97 | pub name: String, 98 | pub info: TypeInfo, 99 | } 100 | 101 | impl GraphQLScalar { 102 | pub fn new(description: Option, name: String) -> GraphQLScalar { 103 | GraphQLScalar { 104 | description: description, 105 | name: name, 106 | } 107 | } 108 | } 109 | 110 | impl GraphQLObject { 111 | pub fn new( 112 | description: Option, 113 | name: String, 114 | implements: Option>, 115 | directives: Option>, 116 | fields: Option>, 117 | ) -> GraphQLObject { 118 | GraphQLObject { 119 | description: description, 120 | name: name, 121 | implements: implements, 122 | directives: directives, 123 | fields: fields, 124 | } 125 | } 126 | } 127 | 128 | impl GraphQLEnum { 129 | pub fn new( 130 | description: Option, 131 | name: String, 132 | directives: Option>, 133 | values: Vec, 134 | ) -> GraphQLEnum { 135 | GraphQLEnum { 136 | description: description, 137 | name: name, 138 | directives: directives, 139 | values: values, 140 | } 141 | } 142 | } 143 | 144 | impl GraphQLInterface { 145 | pub fn new( 146 | description: Option, 147 | name: String, 148 | directives: Option>, 149 | fields: Option>, 150 | ) -> GraphQLInterface { 151 | GraphQLInterface { 152 | description: description, 153 | name: name, 154 | directives: directives, 155 | fields: fields, 156 | } 157 | } 158 | } 159 | 160 | impl GraphQLUnion { 161 | pub fn new( 162 | description: Option, 163 | name: String, 164 | directives: Option>, 165 | types: Vec, 166 | ) -> GraphQLUnion { 167 | GraphQLUnion { 168 | description: description, 169 | name: name, 170 | directives: directives, 171 | types: types, 172 | } 173 | } 174 | } 175 | 176 | impl GraphQLInputObject { 177 | pub fn new( 178 | description: Option, 179 | name: String, 180 | directives: Option>, 181 | fields: Option>, 182 | ) -> GraphQLInputObject { 183 | GraphQLInputObject { 184 | description: description, 185 | name: name, 186 | directives: directives, 187 | fields: fields, 188 | } 189 | } 190 | } 191 | 192 | #[derive(Clone, Debug, PartialEq)] 193 | pub enum TypeInfo { 194 | Nullable, 195 | NonNullable, 196 | NullableListNullableElements, 197 | NullableListNullableNonNullableElements, 198 | NonNullableListNullableElements, 199 | NonNullableListNonNullableElements, 200 | } 201 | 202 | impl TypeInfo { 203 | pub fn as_str(&self) -> &str { 204 | match self { 205 | &TypeInfo::Nullable => "", 206 | &TypeInfo::NonNullable => "!", 207 | &TypeInfo::NullableListNullableElements => "[]", 208 | &TypeInfo::NullableListNullableNonNullableElements => "[!]", 209 | &TypeInfo::NonNullableListNullableElements => "[]!", 210 | &TypeInfo::NonNullableListNonNullableElements => "[!]!", 211 | } 212 | } 213 | } 214 | 215 | impl TypeDefinition { 216 | pub fn typename(&self) -> &str { 217 | match *self { 218 | TypeDefinition::ScalarType { .. } => SCALAR, 219 | TypeDefinition::ObjectType { .. } => OBJECT, 220 | TypeDefinition::EnumType { .. } => ENUM, 221 | TypeDefinition::InterfaceType { .. } => INTERFACE, 222 | TypeDefinition::UnionType { .. } => UNION, 223 | TypeDefinition::InputObjectType { .. } => INPUT_OBJECT, 224 | } 225 | } 226 | 227 | impl_graphql_objects_common_methods! { 228 | ScalarType:GraphQLScalar, 229 | ObjectType:GraphQLObject, 230 | EnumType:GraphQLEnum, 231 | InterfaceType:GraphQLInterface, 232 | UnionType:GraphQLUnion, 233 | InputObjectType:GraphQLInputObject 234 | } 235 | 236 | pub fn implements(&self) -> Option> { 237 | match *self { 238 | TypeDefinition::ObjectType(GraphQLObject { ref implements, .. }) => implements.clone(), 239 | _ => panic!("That method does not exist for this type."), 240 | } 241 | } 242 | 243 | pub fn directives(&self) -> Option> { 244 | match *self { 245 | TypeDefinition::ObjectType(GraphQLObject { ref directives, .. }) => directives.clone(), 246 | TypeDefinition::EnumType(GraphQLEnum { ref directives, .. }) => directives.clone(), 247 | TypeDefinition::InterfaceType(GraphQLInterface { ref directives, .. }) => { 248 | directives.clone() 249 | } 250 | TypeDefinition::UnionType(GraphQLUnion { ref directives, .. }) => directives.clone(), 251 | TypeDefinition::InputObjectType(GraphQLInputObject { ref directives, .. }) => { 252 | directives.clone() 253 | } 254 | _ => panic!("That method does not exist for this type."), 255 | } 256 | } 257 | 258 | pub fn fields(&self) -> Option> { 259 | match *self { 260 | TypeDefinition::ObjectType(GraphQLObject { ref fields, .. }) | 261 | TypeDefinition::InterfaceType(GraphQLInterface { ref fields, .. }) | 262 | TypeDefinition::InputObjectType(GraphQLInputObject { ref fields, .. }) => { 263 | fields.to_owned() 264 | } 265 | _ => panic!("That method does not exist for this type."), 266 | } 267 | } 268 | 269 | pub fn values(&self) -> Option> { 270 | match *self { 271 | TypeDefinition::EnumType(GraphQLEnum { ref values, .. }) => { 272 | if values.len() > 0 { 273 | return Some(values.to_vec()); 274 | } 275 | None 276 | } 277 | _ => panic!("That method does not exist for this type."), 278 | } 279 | } 280 | 281 | pub fn types(&self) -> Option> { 282 | match *self { 283 | TypeDefinition::UnionType(GraphQLUnion { ref types, .. }) => { 284 | if types.len() > 0 { 285 | return Some(types.to_vec()); 286 | } 287 | None 288 | } 289 | _ => panic!("That method does not exist for this type."), 290 | } 291 | } 292 | } 293 | 294 | impl_graphql_meta_methods! { GraphQLField, GraphQLArgument, GraphQLValue } 295 | impl_graphql_directive_methods! { GraphQLField, GraphQLArgument, GraphQLValue } 296 | impl_graphql_type_methods! { GraphQLField, GraphQLArgument } 297 | 298 | impl GraphQLField { 299 | pub fn new( 300 | description: Option, 301 | name: String, 302 | typeinfo: FieldType, 303 | arguments: Option>, 304 | directives: Option>, 305 | ) -> GraphQLField { 306 | GraphQLField { 307 | description: description, 308 | name: name, 309 | typeinfo: typeinfo, 310 | arguments: arguments, 311 | directives: directives, 312 | } 313 | } 314 | 315 | pub fn arguments(&self) -> Option> { 316 | match self.arguments { 317 | None => None, 318 | Some(ref arguments) => Some(arguments.clone()), 319 | } 320 | } 321 | } 322 | 323 | impl GraphQLArgument { 324 | pub fn new( 325 | description: Option, 326 | name: String, 327 | typeinfo: FieldType, 328 | default: Option, 329 | directives: Option>, 330 | ) -> GraphQLArgument { 331 | GraphQLArgument { 332 | description: description, 333 | name: name, 334 | typeinfo: typeinfo, 335 | default: default, 336 | directives: directives, 337 | } 338 | } 339 | 340 | pub fn default(&self) -> Option<&str> { 341 | self.default.as_ref().map(|s| s.as_ref()) 342 | } 343 | } 344 | 345 | impl GraphQLValue { 346 | pub fn new( 347 | description: Option, 348 | name: String, 349 | directives: Option>, 350 | ) -> GraphQLValue { 351 | GraphQLValue { 352 | description: description, 353 | name: name, 354 | directives: directives, 355 | } 356 | } 357 | } 358 | 359 | impl GraphQLDirective { 360 | pub fn new(name: String, arguments: Option>) -> GraphQLDirective { 361 | GraphQLDirective { 362 | name: name, 363 | arguments: arguments, 364 | } 365 | } 366 | 367 | pub fn name(&self) -> &str { 368 | &self.name 369 | } 370 | 371 | pub fn arguments(&self) -> Option> { 372 | match self.arguments { 373 | None => None, 374 | Some(ref arguments) => Some(arguments.clone()), 375 | } 376 | } 377 | } 378 | 379 | impl GraphQLDirectiveArgument { 380 | pub fn new(name: String, value: Option) -> GraphQLDirectiveArgument { 381 | GraphQLDirectiveArgument { 382 | name: name, 383 | value: value, 384 | } 385 | } 386 | 387 | pub fn name(&self) -> &str { 388 | &self.name 389 | } 390 | 391 | pub fn value(&self) -> Option<&str> { 392 | self.value.as_ref().map(|s| s.as_ref()) 393 | } 394 | } 395 | 396 | impl FieldType { 397 | pub fn name(&self) -> &str { 398 | &self.name 399 | } 400 | 401 | pub fn info(&self) -> &str { 402 | &self.info.as_str() 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /test/enums.graphql: -------------------------------------------------------------------------------- 1 | # State of the project; either 'open' or 'closed' 2 | enum ProjectState { 3 | # The project is closed. 4 | CLOSED 5 | 6 | # The project is open. 7 | OPEN 8 | } 9 | -------------------------------------------------------------------------------- /test/input_objects.graphql: -------------------------------------------------------------------------------- 1 | # Autogenerated input type of UpdateTopics 2 | input UpdateTopicsInput { 3 | # A unique identifier for the client performing the mutation. 4 | clientMutationId: String 5 | 6 | # The Node ID of the repository. 7 | repositoryId: ID! 8 | 9 | # An array of topic names. 10 | topicNames: [String!]! 11 | } 12 | -------------------------------------------------------------------------------- /test/interfaces.graphql: -------------------------------------------------------------------------------- 1 | # An object that can be closed 2 | interface Closable { 3 | # `true` if the object is closed (definition of closed may depend on type) 4 | closed: Boolean! 5 | } 6 | -------------------------------------------------------------------------------- /test/objects.graphql: -------------------------------------------------------------------------------- 1 | type CodeOfConduct {} 2 | 3 | # The Code of Conduct for a repository 4 | type CodeOfConduct {} 5 | 6 | type PushAllowance implements Node {} 7 | 8 | type Release implements Node, UniformResourceLocatable {} 9 | 10 | # The Code of Conduct for a repository 11 | type CodeOfConduct { body: String } 12 | 13 | # The Code of Conduct for a repository 14 | type CodeOfConduct { 15 | # The body of the CoC 16 | body: String 17 | } 18 | 19 | # The Code of Conduct for a repository 20 | type CodeOfConduct { key: String! } 21 | 22 | type CommitCommentConnection { edges: [CommitCommentEdge] } 23 | 24 | type PullRequest { suggestedReviewers: [SuggestedReviewer]! } 25 | 26 | type CommitComment { viewerCannotUpdateReasons: [CommentCannotUpdateReason!]! } 27 | 28 | type User { 29 | # A list of users the given user is followed by. 30 | followers( 31 | # Returns the elements in the list that come after the specified global ID. 32 | after: String 33 | 34 | # Returns the first _n_ elements from the list. 35 | first: Int! 36 | ): FollowerConnection! 37 | } 38 | 39 | type User { 40 | databaseId: Int @deprecated 41 | } 42 | 43 | type Issue { 44 | databaseId: Int @deprecated(reason: "Exposed database IDs will eventually be removed in favor of global Relay IDs.") 45 | } 46 | 47 | # A team of users in an organization. 48 | type Team implements Node { 49 | # List of child teams belonging to this team 50 | childTeams( 51 | # Whether to list immediate child teams or all descendant child teams. 52 | immediateOnly: Boolean = true 53 | ): TeamConnection! 54 | } 55 | -------------------------------------------------------------------------------- /test/scalars.graphql: -------------------------------------------------------------------------------- 1 | scalar DateTime 2 | 3 | # An ISO-8601 encoded UTC date string. 4 | scalar DateTime 5 | -------------------------------------------------------------------------------- /test/unions.graphql: -------------------------------------------------------------------------------- 1 | # Any referencable object 2 | union ReferencedSubject = Issue | PullRequest 3 | --------------------------------------------------------------------------------