@@ -4,6 +4,7 @@ use reqwest;
44use reqwest:: header:: { HeaderMap , HeaderValue , ACCEPT , CONTENT_TYPE } ;
55use serde_json;
66use std:: path:: PathBuf ;
7+ use std:: str:: FromStr ;
78
89#[ derive( GraphQLQuery ) ]
910#[ graphql(
@@ -18,6 +19,7 @@ pub fn introspect_schema(
1819 location : & str ,
1920 output : Option < PathBuf > ,
2021 authorization : Option < String > ,
22+ headers : Vec < Header > ,
2123) -> Result < ( ) , failure:: Error > {
2224 use std:: io:: Write ;
2325
@@ -35,6 +37,14 @@ pub fn introspect_schema(
3537 let client = reqwest:: Client :: new ( ) ;
3638
3739 let mut req_builder = client. post ( location) . headers ( construct_headers ( ) ) ;
40+
41+ for custom_header in headers {
42+ req_builder = req_builder. header (
43+ custom_header. name . as_str ( ) ,
44+ custom_header. value . as_str ( ) ,
45+ ) ;
46+ }
47+
3848 if let Some ( token) = authorization {
3949 req_builder = req_builder. bearer_auth ( token. as_str ( ) ) ;
4050 } ;
@@ -60,3 +70,82 @@ fn construct_headers() -> HeaderMap {
6070 headers. insert ( ACCEPT , HeaderValue :: from_static ( "application/json" ) ) ;
6171 headers
6272}
73+
74+ #[ derive( Debug , PartialEq ) ]
75+ pub struct Header {
76+ name : String ,
77+ value : String ,
78+ }
79+
80+ impl FromStr for Header {
81+ type Err = failure:: Error ;
82+
83+ fn from_str ( input : & str ) -> Result < Self , Self :: Err > {
84+ // error: colon required for name/value pair
85+ if ! input. contains ( ":" ) {
86+ return Err ( format_err ! ( "Invalid header input. A colon is required to separate the name and value. [{}]" , input) ) ;
87+ }
88+
89+ // split on first colon and trim whitespace from name and value
90+ let name_value: Vec < & str > = input. splitn ( 2 , ':' ) . collect ( ) ;
91+ let name = name_value[ 0 ] . trim ( ) ;
92+ let value = name_value[ 1 ] . trim ( ) ;
93+
94+ // error: field name must be
95+ if name. len ( ) == 0 {
96+ return Err ( format_err ! ( "Invalid header input. Field name is required before colon. [{}]" , input) ) ;
97+ }
98+
99+ // error: no whitespace in field name
100+ if name. split_whitespace ( ) . count ( ) > 1 {
101+ return Err ( format_err ! ( "Invalid header input. Whitespace not allowed in field name. [{}]" , input) ) ;
102+ }
103+
104+ Ok ( Self { name : name. to_string ( ) , value : value. to_string ( ) } )
105+ }
106+ }
107+
108+ #[ cfg( test) ]
109+ mod tests {
110+ use super :: * ;
111+
112+ #[ test]
113+ fn it_errors_invalid_headers ( ) {
114+ // https://tools.ietf.org/html/rfc7230#section-3.2
115+
116+ for input in vec ! [
117+ "X-Name Value" , // error: colon required for name/value pair
118+ ": Value" , // error: field name must be
119+ "X Name: Value" , // error: no whitespace in field name
120+ "X\t Name: Value" , // error: no whitespace in field name (tab)
121+ ] {
122+ let header = Header :: from_str ( input) ;
123+
124+ assert ! ( header. is_err( ) , "Expected error: [{}]" , input) ;
125+ }
126+ }
127+
128+ #[ test]
129+ fn it_parses_valid_headers ( ) {
130+ // https://tools.ietf.org/html/rfc7230#section-3.2
131+
132+ let expected1 = Header { name : "X-Name" . to_string ( ) , value : "Value" . to_string ( ) } ;
133+ let expected2 = Header { name : "X-Name" . to_string ( ) , value : "Value:" . to_string ( ) } ;
134+
135+ for ( input, expected) in vec ! [
136+ ( "X-Name: Value" , & expected1) , // ideal
137+ ( "X-Name:Value" , & expected1) , // no optional whitespace
138+ ( "X-Name: Value " , & expected1) , // with optional whitespace
139+ ( "X-Name:\t Value" , & expected1) , // with optional whitespace (tab)
140+ ( "X-Name: Value:" , & expected2) , // with colon in value
141+ // not allowed per RFC, but we'll forgive
142+ ( "X-Name : Value" , & expected1) ,
143+ ( " X-Name: Value" , & expected1) ,
144+ ] {
145+ let header = Header :: from_str ( input) ;
146+
147+ assert ! ( header. is_ok( ) , "Expected ok: [{}]" , input) ;
148+ assert_eq ! ( header. unwrap( ) , * expected, "Expected equality: [{}]" , input) ;
149+ }
150+ }
151+ }
0 commit comments