@@ -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,11 @@ 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 ( custom_header. name . as_str ( ) , custom_header. value . as_str ( ) ) ;
43+ }
44+
3845 if let Some ( token) = authorization {
3946 req_builder = req_builder. bearer_auth ( token. as_str ( ) ) ;
4047 } ;
@@ -60,3 +67,100 @@ fn construct_headers() -> HeaderMap {
6067 headers. insert ( ACCEPT , HeaderValue :: from_static ( "application/json" ) ) ;
6168 headers
6269}
70+
71+ #[ derive( Debug , PartialEq ) ]
72+ pub struct Header {
73+ name : String ,
74+ value : String ,
75+ }
76+
77+ impl FromStr for Header {
78+ type Err = failure:: Error ;
79+
80+ fn from_str ( input : & str ) -> Result < Self , Self :: Err > {
81+ // error: colon required for name/value pair
82+ if !input. contains ( ":" ) {
83+ return Err ( format_err ! (
84+ "Invalid header input. A colon is required to separate the name and value. [{}]" ,
85+ input
86+ ) ) ;
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 ! (
97+ "Invalid header input. Field name is required before colon. [{}]" ,
98+ input
99+ ) ) ;
100+ }
101+
102+ // error: no whitespace in field name
103+ if name. split_whitespace ( ) . count ( ) > 1 {
104+ return Err ( format_err ! (
105+ "Invalid header input. Whitespace not allowed in field name. [{}]" ,
106+ input
107+ ) ) ;
108+ }
109+
110+ Ok ( Self {
111+ name : name. to_string ( ) ,
112+ value : value. to_string ( ) ,
113+ } )
114+ }
115+ }
116+
117+ #[ cfg( test) ]
118+ mod tests {
119+ use super :: * ;
120+
121+ #[ test]
122+ fn it_errors_invalid_headers ( ) {
123+ // https://tools.ietf.org/html/rfc7230#section-3.2
124+
125+ for input in vec ! [
126+ "X-Name Value" , // error: colon required for name/value pair
127+ ": Value" , // error: field name must be
128+ "X Name: Value" , // error: no whitespace in field name
129+ "X\t Name: Value" , // error: no whitespace in field name (tab)
130+ ] {
131+ let header = Header :: from_str ( input) ;
132+
133+ assert ! ( header. is_err( ) , "Expected error: [{}]" , input) ;
134+ }
135+ }
136+
137+ #[ test]
138+ fn it_parses_valid_headers ( ) {
139+ // https://tools.ietf.org/html/rfc7230#section-3.2
140+
141+ let expected1 = Header {
142+ name : "X-Name" . to_string ( ) ,
143+ value : "Value" . to_string ( ) ,
144+ } ;
145+ let expected2 = Header {
146+ name : "X-Name" . to_string ( ) ,
147+ value : "Value:" . to_string ( ) ,
148+ } ;
149+
150+ for ( input, expected) in vec ! [
151+ ( "X-Name: Value" , & expected1) , // ideal
152+ ( "X-Name:Value" , & expected1) , // no optional whitespace
153+ ( "X-Name: Value " , & expected1) , // with optional whitespace
154+ ( "X-Name:\t Value" , & expected1) , // with optional whitespace (tab)
155+ ( "X-Name: Value:" , & expected2) , // with colon in value
156+ // not allowed per RFC, but we'll forgive
157+ ( "X-Name : Value" , & expected1) ,
158+ ( " X-Name: Value" , & expected1) ,
159+ ] {
160+ let header = Header :: from_str ( input) ;
161+
162+ assert ! ( header. is_ok( ) , "Expected ok: [{}]" , input) ;
163+ assert_eq ! ( header. unwrap( ) , * expected, "Expected equality: [{}]" , input) ;
164+ }
165+ }
166+ }
0 commit comments