@@ -109,8 +109,8 @@ impl HeaderContext {
109109 }
110110 pub async fn handle_row ( self , data : JsonValue ) -> anyhow:: Result < PageContext > {
111111 log:: debug!( "Handling header row: {data}" ) ;
112- let comp_opt =
113- get_object_str ( & data , "component" ) . and_then ( |s| HeaderComponent :: try_from ( s) . ok ( ) ) ;
112+ let comp_opt = get_object_str_lower_or_upper ( & data , "component" , "COMPONENT" )
113+ . and_then ( |s| HeaderComponent :: try_from ( s) . ok ( ) ) ;
114114 match comp_opt {
115115 Some ( HeaderComponent :: StatusCode ) => self . status_code ( & data) . map ( PageContext :: Header ) ,
116116 Some ( HeaderComponent :: HttpHeader ) => {
@@ -141,9 +141,7 @@ impl HeaderContext {
141141 }
142142
143143 fn status_code ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
144- let status_code = data
145- . as_object ( )
146- . and_then ( |m| m. get ( "status" ) )
144+ let status_code = get_object_value_lower_or_upper ( data, "status" , "STATUS" )
147145 . with_context ( || "status_code component requires a status" ) ?
148146 . as_u64 ( )
149147 . with_context ( || "status must be a number" ) ?;
@@ -157,7 +155,7 @@ impl HeaderContext {
157155 fn add_http_header ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
158156 let obj = data. as_object ( ) . with_context ( || "expected object" ) ?;
159157 for ( name, value) in obj {
160- if name == "component" {
158+ if name. eq_ignore_ascii_case ( "component" ) {
161159 continue ;
162160 }
163161 let value_str = value
@@ -173,55 +171,51 @@ impl HeaderContext {
173171 }
174172
175173 fn add_cookie ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
176- let obj = data. as_object ( ) . with_context ( || "expected object" ) ?;
177- let name = obj
178- . get ( "name" )
179- . and_then ( JsonValue :: as_str)
174+ data. as_object ( ) . with_context ( || "expected object" ) ?;
175+ let name = get_object_str_lower_or_upper ( data, "name" , "NAME" )
180176 . with_context ( || "cookie name must be a string" ) ?;
181177 let mut cookie = actix_web:: cookie:: Cookie :: named ( name) ;
182178
183- let path = obj . get ( "path" ) . and_then ( JsonValue :: as_str ) ;
179+ let path = get_object_str_lower_or_upper ( data , "path" , "PATH" ) ;
184180 if let Some ( path) = path {
185181 cookie. set_path ( path) ;
186182 } else {
187183 cookie. set_path ( "/" ) ;
188184 }
189- let domain = obj . get ( "domain" ) . and_then ( JsonValue :: as_str ) ;
185+ let domain = get_object_str_lower_or_upper ( data , "domain" , "DOMAIN" ) ;
190186 if let Some ( domain) = domain {
191187 cookie. set_domain ( domain) ;
192188 }
193189
194- let remove = obj . get ( "remove" ) ;
190+ let remove = get_object_value_lower_or_upper ( data , "remove" , "REMOVE ") ;
195191 if remove == Some ( & json ! ( true ) ) || remove == Some ( & json ! ( 1 ) ) {
196192 cookie. make_removal ( ) ;
197193 self . response . cookie ( cookie) ;
198194 log:: trace!( "Removing cookie {name}" ) ;
199195 return Ok ( self ) ;
200196 }
201197
202- let value = obj
203- . get ( "value" )
204- . and_then ( JsonValue :: as_str)
198+ let value = get_object_str_lower_or_upper ( data, "value" , "VALUE" )
205199 . with_context ( || "The 'value' property of the cookie component is required (unless 'remove' is set) and must be a string." ) ?;
206200 cookie. set_value ( value) ;
207- let http_only = obj . get ( "http_only" ) ;
201+ let http_only = get_object_value_lower_or_upper ( data , "http_only" , "HTTP_ONLY ") ;
208202 cookie. set_http_only ( http_only != Some ( & json ! ( false ) ) && http_only != Some ( & json ! ( 0 ) ) ) ;
209- let same_site = obj . get ( "same_site" ) . and_then ( Value :: as_str ) ;
203+ let same_site = get_object_str_lower_or_upper ( data , "same_site" , "SAME_SITE" ) ;
210204 cookie. set_same_site ( match same_site {
211205 Some ( "none" ) => actix_web:: cookie:: SameSite :: None ,
212206 Some ( "lax" ) => actix_web:: cookie:: SameSite :: Lax ,
213207 None | Some ( "strict" ) => actix_web:: cookie:: SameSite :: Strict , // strict by default
214208 Some ( other) => bail ! ( "Cookie: invalid value for same_site: {other}" ) ,
215209 } ) ;
216- let secure = obj . get ( "secure" ) ;
210+ let secure = get_object_value_lower_or_upper ( data , "secure" , "SECURE ") ;
217211 cookie. set_secure ( secure != Some ( & json ! ( false ) ) && secure != Some ( & json ! ( 0 ) ) ) ;
218- if let Some ( max_age_json) = obj . get ( "max_age" ) {
212+ if let Some ( max_age_json) = get_object_value_lower_or_upper ( data , "max_age" , "MAX_AGE ") {
219213 let seconds = max_age_json
220214 . as_i64 ( )
221215 . ok_or_else ( || anyhow:: anyhow!( "max_age must be a number, not {max_age_json}" ) ) ?;
222216 cookie. set_max_age ( Duration :: seconds ( seconds) ) ;
223217 }
224- let expires = obj . get ( "expires" ) ;
218+ let expires = get_object_value_lower_or_upper ( data , "expires" , "EXPIRES ") ;
225219 if let Some ( expires) = expires {
226220 cookie. set_expires ( actix_web:: cookie:: Expiration :: DateTime ( match expires {
227221 JsonValue :: String ( s) => OffsetDateTime :: parse ( s, & Rfc3339 ) ?,
@@ -240,7 +234,7 @@ impl HeaderContext {
240234 fn redirect ( mut self , data : & JsonValue ) -> anyhow:: Result < HttpResponse > {
241235 self . response . status ( StatusCode :: FOUND ) ;
242236 self . has_status = true ;
243- let link = get_object_str ( data, "link" )
237+ let link = get_object_str_lower_or_upper ( data, "link" , "LINK ")
244238 . with_context ( || "The redirect component requires a 'link' property" ) ?;
245239 self . response . insert_header ( ( header:: LOCATION , link) ) ;
246240 let response = self . response . body ( ( ) ) ;
@@ -251,15 +245,15 @@ impl HeaderContext {
251245 fn json ( mut self , data : & JsonValue ) -> anyhow:: Result < PageContext > {
252246 self . response
253247 . insert_header ( ( header:: CONTENT_TYPE , "application/json" ) ) ;
254- if let Some ( contents) = data . get ( "contents" ) {
248+ if let Some ( contents) = get_object_value_lower_or_upper ( data , "contents" , "CONTENTS ") {
255249 let json_response = if let Some ( s) = contents. as_str ( ) {
256250 s. as_bytes ( ) . to_owned ( )
257251 } else {
258252 serde_json:: to_vec ( contents) ?
259253 } ;
260254 Ok ( PageContext :: Close ( self . response . body ( json_response) ) )
261255 } else {
262- let body_type = get_object_str ( data, "type" ) ;
256+ let body_type = get_object_str_lower_or_upper ( data, "type" , "TYPE ") ;
263257 let json_renderer = match body_type {
264258 None | Some ( "array" ) => JsonBodyRenderer :: new_array ( self . writer ) ,
265259 Some ( "jsonlines" ) => JsonBodyRenderer :: new_jsonlines ( self . writer ) ,
@@ -284,8 +278,8 @@ impl HeaderContext {
284278 async fn csv ( mut self , options : & JsonValue ) -> anyhow:: Result < PageContext > {
285279 self . response
286280 . insert_header ( ( header:: CONTENT_TYPE , "text/csv; charset=utf-8" ) ) ;
287- if let Some ( filename) =
288- get_object_str ( options , "filename" ) . or_else ( || get_object_str ( options, "title" ) )
281+ if let Some ( filename) = get_object_str_lower_or_upper ( options , "filename" , "FILENAME" )
282+ . or_else ( || get_object_str_lower_or_upper ( options, "title" , "TITLE ") )
289283 {
290284 let extension = if filename. contains ( '.' ) { "" } else { ".csv" } ;
291285 self . response . insert_header ( (
@@ -303,8 +297,8 @@ impl HeaderContext {
303297 }
304298
305299 async fn authentication ( mut self , mut data : JsonValue ) -> anyhow:: Result < PageContext > {
306- let password_hash = take_object_str ( & mut data, "password_hash" ) ;
307- let password = take_object_str ( & mut data, "password" ) ;
300+ let password_hash = take_object_str_lower_or_upper ( & mut data, "password_hash" , "PASSWORD_HASH ") ;
301+ let password = take_object_str_lower_or_upper ( & mut data, "password" , "PASSWORD ") ;
308302 if let ( Some ( password) , Some ( password_hash) ) = ( password, password_hash) {
309303 log:: debug!( "Authentication with password_hash = {password_hash:?}" ) ;
310304 match verify_password_async ( password_hash, password) . await ? {
@@ -314,7 +308,7 @@ impl HeaderContext {
314308 }
315309 log:: debug!( "Authentication failed" ) ;
316310 // The authentication failed
317- let http_response: HttpResponse = if let Some ( link) = get_object_str ( & data, "link" ) {
311+ let http_response: HttpResponse = if let Some ( link) = get_object_str_lower_or_upper ( & data, "link" , "LINK ") {
318312 self . response
319313 . status ( StatusCode :: FOUND )
320314 . insert_header ( ( header:: LOCATION , link) )
@@ -332,13 +326,13 @@ impl HeaderContext {
332326 }
333327
334328 fn download ( mut self , options : & JsonValue ) -> anyhow:: Result < PageContext > {
335- if let Some ( filename) = get_object_str ( options, "filename" ) {
329+ if let Some ( filename) = get_object_str_lower_or_upper ( options, "filename" , "FILENAME ") {
336330 self . response . insert_header ( (
337331 header:: CONTENT_DISPOSITION ,
338332 format ! ( "attachment; filename=\" {filename}\" " ) ,
339333 ) ) ;
340334 }
341- let data_url = get_object_str ( options, "data_url" )
335+ let data_url = get_object_str_lower_or_upper ( options, "data_url" , "DATA_URL ")
342336 . with_context ( || "The download component requires a 'data_url' property" ) ?;
343337 let rest = data_url
344338 . strip_prefix ( "data:" )
@@ -412,6 +406,39 @@ fn take_object_str(json: &mut JsonValue, key: &str) -> Option<String> {
412406 }
413407}
414408
409+ #[ inline]
410+ fn get_object_value_lower_or_upper < ' a > ( json : & ' a JsonValue , lower : & str , upper : & str ) -> Option < & ' a JsonValue > {
411+ json. as_object ( )
412+ . and_then ( |obj| obj. get ( lower) . or_else ( || obj. get ( upper) ) )
413+ }
414+
415+ #[ inline]
416+ fn get_object_str_lower_or_upper < ' a > ( json : & ' a JsonValue , lower : & str , upper : & str ) -> Option < & ' a str > {
417+ get_object_value_lower_or_upper ( json, lower, upper) . and_then ( JsonValue :: as_str)
418+ }
419+
420+ #[ inline]
421+ fn take_object_str_lower_or_upper ( json : & mut JsonValue , lower : & str , upper : & str ) -> Option < String > {
422+ if let Some ( v) = json. get_mut ( lower) {
423+ match v. take ( ) {
424+ JsonValue :: String ( s) => return Some ( s) ,
425+ other => {
426+ // put it back if not a string
427+ * v = other;
428+ }
429+ }
430+ }
431+ if let Some ( v) = json. get_mut ( upper) {
432+ match v. take ( ) {
433+ JsonValue :: String ( s) => return Some ( s) ,
434+ other => {
435+ * v = other;
436+ }
437+ }
438+ }
439+ None
440+ }
441+
415442/**
416443 * Can receive rows, and write them in a given format to an `io::Write`
417444 */
@@ -553,26 +580,25 @@ impl CsvBodyRenderer {
553580 options : & JsonValue ,
554581 ) -> anyhow:: Result < CsvBodyRenderer > {
555582 let mut builder = csv_async:: AsyncWriterBuilder :: new ( ) ;
556- if let Some ( separator) = get_object_str ( options, "separator" ) {
583+ if let Some ( separator) = get_object_str_lower_or_upper ( options, "separator" , "SEPARATOR ") {
557584 let & [ separator_byte] = separator. as_bytes ( ) else {
558585 bail ! ( "Invalid csv separator: {separator:?}. It must be a single byte." ) ;
559586 } ;
560587 builder. delimiter ( separator_byte) ;
561588 }
562- if let Some ( quote) = get_object_str ( options, "quote" ) {
589+ if let Some ( quote) = get_object_str_lower_or_upper ( options, "quote" , "QUOTE ") {
563590 let & [ quote_byte] = quote. as_bytes ( ) else {
564591 bail ! ( "Invalid csv quote: {quote:?}. It must be a single byte." ) ;
565592 } ;
566593 builder. quote ( quote_byte) ;
567594 }
568- if let Some ( escape) = get_object_str ( options, "escape" ) {
595+ if let Some ( escape) = get_object_str_lower_or_upper ( options, "escape" , "ESCAPE ") {
569596 let & [ escape_byte] = escape. as_bytes ( ) else {
570597 bail ! ( "Invalid csv escape: {escape:?}. It must be a single byte." ) ;
571598 } ;
572599 builder. escape ( escape_byte) ;
573600 }
574- if options
575- . get ( "bom" )
601+ if get_object_value_lower_or_upper ( options, "bom" , "BOM" )
576602 . and_then ( JsonValue :: as_bool)
577603 . unwrap_or ( false )
578604 {
@@ -671,7 +697,7 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
671697
672698 if !initial_rows
673699 . first ( )
674- . and_then ( |c| get_object_str ( c, "component" ) )
700+ . and_then ( |c| get_object_str_lower_or_upper ( c, "component" , "COMPONENT ") )
675701 . is_some_and ( Self :: is_shell_component)
676702 {
677703 let default_shell = if request_context. is_embedded {
@@ -690,8 +716,8 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
690716 let shell_row = rows_iter
691717 . next ( )
692718 . expect ( "shell row should exist at this point" ) ;
693- let mut shell_component =
694- get_object_str ( & shell_row , "component" ) . expect ( "shell should exist" ) ;
719+ let mut shell_component = get_object_str_lower_or_upper ( & shell_row , "component" , "COMPONENT" )
720+ . expect ( "shell should exist" ) ;
695721 if request_context. is_embedded && shell_component != FRAGMENT_SHELL_COMPONENT {
696722 log:: warn!(
697723 "Embedded pages cannot use a shell component! Ignoring the '{shell_component}' component and its properties: {shell_row}"
@@ -759,7 +785,7 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
759785 }
760786
761787 pub async fn handle_row ( & mut self , data : & JsonValue ) -> anyhow:: Result < ( ) > {
762- let new_component = get_object_str ( data, "component" ) ;
788+ let new_component = get_object_str_lower_or_upper ( data, "component" , "COMPONENT ") ;
763789 let current_component = self
764790 . current_component
765791 . as_ref ( )
@@ -914,15 +940,15 @@ fn handle_log_component(
914940 current_statement : Option < usize > ,
915941 data : & JsonValue ,
916942) -> anyhow:: Result < ( ) > {
917- let level_name = get_object_str ( data, "level" ) . unwrap_or ( "info" ) ;
943+ let level_name = get_object_str_lower_or_upper ( data, "level" , "LEVEL ") . unwrap_or ( "info" ) ;
918944 let log_level = log:: Level :: from_str ( level_name) . with_context ( || "Invalid log level value" ) ?;
919945
920946 let mut target = format ! ( "sqlpage::log from \" {}\" " , source_path. display( ) ) ;
921947 if let Some ( current_statement) = current_statement {
922948 write ! ( & mut target, " statement {current_statement}" ) ?;
923949 }
924950
925- let message = get_object_str ( data, "message" ) . context ( "log: missing property 'message'" ) ?;
951+ let message = get_object_str_lower_or_upper ( data, "message" , "MESSAGE ") . context ( "log: missing property 'message'" ) ?;
926952 log:: log!( target: & target, log_level, "{message}" ) ;
927953 Ok ( ( ) )
928954}
0 commit comments