1use crate::cache::models::{CachedCommand, CachedSecurityScheme, CachedSpec};
2use crate::cli::OutputFormat;
3use crate::config::models::GlobalConfig;
4use crate::config::url_resolver::BaseUrlResolver;
5use crate::error::Error;
6use crate::response_cache::{CacheConfig, CacheKey, CachedRequestInfo, ResponseCache};
7use clap::ArgMatches;
8use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
9use reqwest::Method;
10use serde_json::Value;
11use std::collections::HashMap;
12use std::str::FromStr;
13
14const MAX_TABLE_ROWS: usize = 1000;
16
17#[allow(clippy::too_many_lines)]
43#[allow(clippy::too_many_arguments)]
44pub async fn execute_request(
45 spec: &CachedSpec,
46 matches: &ArgMatches,
47 base_url: Option<&str>,
48 dry_run: bool,
49 idempotency_key: Option<&str>,
50 global_config: Option<&GlobalConfig>,
51 output_format: &OutputFormat,
52 jq_filter: Option<&str>,
53 cache_config: Option<&CacheConfig>,
54) -> Result<(), Error> {
55 let operation = find_operation(spec, matches)?;
57
58 let resolver = BaseUrlResolver::new(spec);
60 let resolver = if let Some(config) = global_config {
61 resolver.with_global_config(config)
62 } else {
63 resolver
64 };
65 let base_url = resolver.resolve(base_url);
66
67 let url = build_url(&base_url, &operation.path, operation, matches)?;
69
70 let client = reqwest::Client::builder()
72 .timeout(std::time::Duration::from_secs(30))
73 .build()
74 .map_err(|e| Error::RequestFailed {
75 reason: format!("Failed to create HTTP client: {e}"),
76 })?;
77
78 let mut headers = build_headers(spec, operation, matches)?;
80
81 if let Some(key) = idempotency_key {
83 headers.insert(
84 HeaderName::from_static("idempotency-key"),
85 HeaderValue::from_str(key).map_err(|_| Error::InvalidIdempotencyKey)?,
86 );
87 }
88
89 let method = Method::from_str(&operation.method).map_err(|_| Error::InvalidHttpMethod {
91 method: operation.method.clone(),
92 })?;
93
94 let headers_clone = headers.clone(); let mut request = client.request(method.clone(), &url).headers(headers);
96
97 let mut current_matches = matches;
100 while let Some((_name, sub_matches)) = current_matches.subcommand() {
101 current_matches = sub_matches;
102 }
103
104 let request_body = if operation.request_body.is_some() {
105 if let Some(body_value) = current_matches.get_one::<String>("body") {
106 let json_body: Value =
107 serde_json::from_str(body_value).map_err(|e| Error::InvalidJsonBody {
108 reason: e.to_string(),
109 })?;
110 request = request.json(&json_body);
111 Some(body_value.clone())
112 } else {
113 None
114 }
115 } else {
116 None
117 };
118
119 let cache_key = if let Some(cache_cfg) = cache_config {
121 if cache_cfg.enabled {
122 let header_map: HashMap<String, String> = headers_clone
124 .iter()
125 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
126 .collect();
127
128 let cache_key = CacheKey::from_request(
129 &spec.name,
130 &operation.operation_id,
131 method.as_ref(),
132 &url,
133 &header_map,
134 request_body.as_deref(),
135 )?;
136
137 let response_cache = ResponseCache::new(cache_cfg.clone())?;
138
139 if let Some(cached_response) = response_cache.get(&cache_key).await? {
141 print_formatted_response(&cached_response.body, output_format, jq_filter)?;
143 return Ok(());
144 }
145
146 Some((cache_key, response_cache))
147 } else {
148 None
149 }
150 } else {
151 None
152 };
153
154 if dry_run {
156 let dry_run_info = serde_json::json!({
157 "dry_run": true,
158 "method": operation.method,
159 "url": url,
160 "headers": headers_clone.iter().map(|(k, v)| (k.as_str(), v.to_str().unwrap_or("<binary>"))).collect::<std::collections::HashMap<_, _>>(),
161 "operation_id": operation.operation_id
162 });
163 let dry_run_output =
164 serde_json::to_string_pretty(&dry_run_info).map_err(|e| Error::SerializationError {
165 reason: format!("Failed to serialize dry-run info: {e}"),
166 })?;
167 println!("{dry_run_output}");
168 return Ok(());
169 }
170
171 let response = request.send().await.map_err(|e| Error::RequestFailed {
173 reason: e.to_string(),
174 })?;
175
176 let status = response.status();
177 let response_headers: HashMap<String, String> = response
178 .headers()
179 .iter()
180 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
181 .collect();
182
183 let response_text = response
184 .text()
185 .await
186 .map_err(|e| Error::ResponseReadError {
187 reason: e.to_string(),
188 })?;
189
190 if !status.is_success() {
192 let api_name = spec.name.clone();
194 let operation_id = Some(operation.operation_id.clone());
195 let security_schemes: Vec<String> = operation
196 .security_requirements
197 .iter()
198 .filter_map(|scheme_name| {
199 spec.security_schemes
200 .get(scheme_name)
201 .and_then(|scheme| scheme.aperture_secret.as_ref())
202 .map(|aperture_secret| aperture_secret.name.clone())
203 })
204 .collect();
205
206 return Err(Error::HttpErrorWithContext {
207 status: status.as_u16(),
208 body: if response_text.is_empty() {
209 "(empty response)".to_string()
210 } else {
211 response_text
212 },
213 api_name,
214 operation_id,
215 security_schemes,
216 });
217 }
218
219 if let Some((cache_key, response_cache)) = cache_key {
221 let cached_request_info = CachedRequestInfo {
223 method: method.to_string(),
224 url: url.clone(),
225 headers: headers_clone
226 .iter()
227 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
228 .collect(),
229 body_hash: request_body.as_ref().map(|body| {
230 use sha2::{Digest, Sha256};
231 let mut hasher = Sha256::new();
232 hasher.update(body.as_bytes());
233 format!("{:x}", hasher.finalize())
234 }),
235 };
236
237 let cache_ttl = cache_config.and_then(|cfg| {
239 if cfg.default_ttl.as_secs() > 0 {
240 Some(cfg.default_ttl)
241 } else {
242 None
243 }
244 });
245
246 let _ = response_cache
247 .store(
248 &cache_key,
249 &response_text,
250 status.as_u16(),
251 &response_headers,
252 cached_request_info,
253 cache_ttl,
254 )
255 .await;
256 }
257
258 if !response_text.is_empty() {
260 print_formatted_response(&response_text, output_format, jq_filter)?;
261 }
262
263 Ok(())
264}
265
266fn find_operation<'a>(
268 spec: &'a CachedSpec,
269 matches: &ArgMatches,
270) -> Result<&'a CachedCommand, Error> {
271 let mut current_matches = matches;
273 let mut subcommand_path = Vec::new();
274
275 while let Some((name, sub_matches)) = current_matches.subcommand() {
276 subcommand_path.push(name);
277 current_matches = sub_matches;
278 }
279
280 if let Some(operation_name) = subcommand_path.last() {
283 for command in &spec.commands {
284 let kebab_id = to_kebab_case(&command.operation_id);
286 if &kebab_id == operation_name || command.method.to_lowercase() == *operation_name {
287 return Ok(command);
288 }
289 }
290 }
291
292 Err(Error::OperationNotFound)
293}
294
295fn build_url(
297 base_url: &str,
298 path_template: &str,
299 operation: &CachedCommand,
300 matches: &ArgMatches,
301) -> Result<String, Error> {
302 let mut url = format!("{}{}", base_url.trim_end_matches('/'), path_template);
303
304 let mut current_matches = matches;
306 while let Some((_name, sub_matches)) = current_matches.subcommand() {
307 current_matches = sub_matches;
308 }
309
310 let mut start = 0;
313 while let Some(open) = url[start..].find('{') {
314 let open_pos = start + open;
315 if let Some(close) = url[open_pos..].find('}') {
316 let close_pos = open_pos + close;
317 let param_name = &url[open_pos + 1..close_pos];
318
319 if let Some(value) = current_matches.get_one::<String>(param_name) {
320 url.replace_range(open_pos..=close_pos, value);
321 start = open_pos + value.len();
322 } else {
323 return Err(Error::MissingPathParameter {
324 name: param_name.to_string(),
325 });
326 }
327 } else {
328 break;
329 }
330 }
331
332 let mut query_params = Vec::new();
334 for arg in current_matches.ids() {
335 let arg_str = arg.as_str();
336 let is_query_param = operation
338 .parameters
339 .iter()
340 .any(|p| p.name == arg_str && p.location == "query");
341 if is_query_param {
342 if let Some(value) = current_matches.get_one::<String>(arg_str) {
343 query_params.push(format!("{}={}", arg_str, urlencoding::encode(value)));
344 }
345 }
346 }
347
348 if !query_params.is_empty() {
349 url.push('?');
350 url.push_str(&query_params.join("&"));
351 }
352
353 Ok(url)
354}
355
356fn build_headers(
358 spec: &CachedSpec,
359 operation: &CachedCommand,
360 matches: &ArgMatches,
361) -> Result<HeaderMap, Error> {
362 let mut headers = HeaderMap::new();
363
364 headers.insert("User-Agent", HeaderValue::from_static("aperture/0.1.0"));
366 headers.insert("Accept", HeaderValue::from_static("application/json"));
367
368 let mut current_matches = matches;
370 while let Some((_name, sub_matches)) = current_matches.subcommand() {
371 current_matches = sub_matches;
372 }
373
374 for param in &operation.parameters {
376 if param.location == "header" {
377 if let Some(value) = current_matches.get_one::<String>(¶m.name) {
378 let header_name =
379 HeaderName::from_str(¶m.name).map_err(|e| Error::InvalidHeaderName {
380 name: param.name.clone(),
381 reason: e.to_string(),
382 })?;
383 let header_value =
384 HeaderValue::from_str(value).map_err(|e| Error::InvalidHeaderValue {
385 name: param.name.clone(),
386 reason: e.to_string(),
387 })?;
388 headers.insert(header_name, header_value);
389 }
390 }
391 }
392
393 for security_scheme_name in &operation.security_requirements {
395 if let Some(security_scheme) = spec.security_schemes.get(security_scheme_name) {
396 add_authentication_header(&mut headers, security_scheme)?;
397 }
398 }
399
400 if let Ok(Some(custom_headers)) = current_matches.try_get_many::<String>("header") {
403 for header_str in custom_headers {
404 let (name, value) = parse_custom_header(header_str)?;
405 let header_name =
406 HeaderName::from_str(&name).map_err(|e| Error::InvalidHeaderName {
407 name: name.clone(),
408 reason: e.to_string(),
409 })?;
410 let header_value =
411 HeaderValue::from_str(&value).map_err(|e| Error::InvalidHeaderValue {
412 name: name.clone(),
413 reason: e.to_string(),
414 })?;
415 headers.insert(header_name, header_value);
416 }
417 }
418
419 Ok(headers)
420}
421
422fn parse_custom_header(header_str: &str) -> Result<(String, String), Error> {
424 let colon_pos = header_str
426 .find(':')
427 .ok_or_else(|| Error::InvalidHeaderFormat {
428 header: header_str.to_string(),
429 })?;
430
431 let name = header_str[..colon_pos].trim();
432 let value = header_str[colon_pos + 1..].trim();
433
434 if name.is_empty() {
435 return Err(Error::EmptyHeaderName);
436 }
437
438 let expanded_value = if value.starts_with("${") && value.ends_with('}') {
440 let var_name = &value[2..value.len() - 1];
442 std::env::var(var_name).unwrap_or_else(|_| value.to_string())
443 } else {
444 value.to_string()
445 };
446
447 Ok((name.to_string(), expanded_value))
448}
449
450fn add_authentication_header(
452 headers: &mut HeaderMap,
453 security_scheme: &CachedSecurityScheme,
454) -> Result<(), Error> {
455 if let Some(aperture_secret) = &security_scheme.aperture_secret {
457 let secret_value =
459 std::env::var(&aperture_secret.name).map_err(|_| Error::SecretNotSet {
460 scheme_name: security_scheme.name.clone(),
461 env_var: aperture_secret.name.clone(),
462 })?;
463
464 match security_scheme.scheme_type.as_str() {
466 "apiKey" => {
467 if let (Some(location), Some(param_name)) =
468 (&security_scheme.location, &security_scheme.parameter_name)
469 {
470 if location == "header" {
471 let header_name = HeaderName::from_str(param_name).map_err(|e| {
472 Error::InvalidHeaderName {
473 name: param_name.clone(),
474 reason: e.to_string(),
475 }
476 })?;
477 let header_value = HeaderValue::from_str(&secret_value).map_err(|e| {
478 Error::InvalidHeaderValue {
479 name: param_name.clone(),
480 reason: e.to_string(),
481 }
482 })?;
483 headers.insert(header_name, header_value);
484 }
485 }
487 }
488 "http" => {
489 if let Some(scheme) = &security_scheme.scheme {
490 match scheme.as_str() {
491 "bearer" => {
492 let auth_value = format!("Bearer {secret_value}");
493 let header_value = HeaderValue::from_str(&auth_value).map_err(|e| {
494 Error::InvalidHeaderValue {
495 name: "Authorization".to_string(),
496 reason: e.to_string(),
497 }
498 })?;
499 headers.insert("Authorization", header_value);
500 }
501 "basic" => {
502 let auth_value = format!("Basic {secret_value}");
504 let header_value = HeaderValue::from_str(&auth_value).map_err(|e| {
505 Error::InvalidHeaderValue {
506 name: "Authorization".to_string(),
507 reason: e.to_string(),
508 }
509 })?;
510 headers.insert("Authorization", header_value);
511 }
512 _ => {
513 return Err(Error::UnsupportedAuthScheme {
514 scheme: scheme.clone(),
515 });
516 }
517 }
518 }
519 }
520 _ => {
521 return Err(Error::UnsupportedSecurityScheme {
522 scheme_type: security_scheme.scheme_type.clone(),
523 });
524 }
525 }
526 }
527
528 Ok(())
529}
530
531fn to_kebab_case(s: &str) -> String {
533 let mut result = String::new();
534 let mut prev_lowercase = false;
535
536 for (i, ch) in s.chars().enumerate() {
537 if ch.is_uppercase() && i > 0 && prev_lowercase {
538 result.push('-');
539 }
540 result.push(ch.to_ascii_lowercase());
541 prev_lowercase = ch.is_lowercase();
542 }
543
544 result
545}
546
547fn print_formatted_response(
549 response_text: &str,
550 output_format: &OutputFormat,
551 jq_filter: Option<&str>,
552) -> Result<(), Error> {
553 let processed_text = if let Some(filter) = jq_filter {
555 apply_jq_filter(response_text, filter)?
556 } else {
557 response_text.to_string()
558 };
559
560 match output_format {
561 OutputFormat::Json => {
562 if let Ok(json_value) = serde_json::from_str::<Value>(&processed_text) {
564 if let Ok(pretty) = serde_json::to_string_pretty(&json_value) {
565 println!("{pretty}");
566 } else {
567 println!("{processed_text}");
568 }
569 } else {
570 println!("{processed_text}");
571 }
572 }
573 OutputFormat::Yaml => {
574 if let Ok(json_value) = serde_json::from_str::<Value>(&processed_text) {
576 match serde_yaml::to_string(&json_value) {
577 Ok(yaml_output) => println!("{yaml_output}"),
578 Err(_) => println!("{processed_text}"), }
580 } else {
581 println!("{processed_text}");
583 }
584 }
585 OutputFormat::Table => {
586 if let Ok(json_value) = serde_json::from_str::<Value>(&processed_text) {
588 print_as_table(&json_value);
589 } else {
590 println!("{processed_text}");
592 }
593 }
594 }
595
596 Ok(())
597}
598
599#[derive(tabled::Tabled)]
601struct TableRow {
602 #[tabled(rename = "Key")]
603 key: String,
604 #[tabled(rename = "Value")]
605 value: String,
606}
607
608#[derive(tabled::Tabled)]
609struct KeyValue {
610 #[tabled(rename = "Key")]
611 key: String,
612 #[tabled(rename = "Value")]
613 value: String,
614}
615
616#[allow(clippy::unnecessary_wraps)]
618fn print_as_table(json_value: &Value) {
619 use std::collections::BTreeMap;
620 use tabled::Table;
621
622 match json_value {
623 Value::Array(items) => {
624 if items.is_empty() {
625 println!("(empty array)");
626 return;
627 }
628
629 if items.len() > MAX_TABLE_ROWS {
631 println!(
632 "Array too large: {} items (max {} for table display)",
633 items.len(),
634 MAX_TABLE_ROWS
635 );
636 println!("Use --format json or --jq to process the full data");
637 return;
638 }
639
640 if let Some(Value::Object(_)) = items.first() {
642 let mut table_data: Vec<BTreeMap<String, String>> = Vec::new();
644
645 for item in items {
646 if let Value::Object(obj) = item {
647 let mut row = BTreeMap::new();
648 for (key, value) in obj {
649 row.insert(key.clone(), format_value_for_table(value));
650 }
651 table_data.push(row);
652 }
653 }
654
655 if !table_data.is_empty() {
656 let mut rows = Vec::new();
659 for (i, row) in table_data.iter().enumerate() {
660 if i > 0 {
661 rows.push(TableRow {
662 key: "---".to_string(),
663 value: "---".to_string(),
664 });
665 }
666 for (key, value) in row {
667 rows.push(TableRow {
668 key: key.clone(),
669 value: value.clone(),
670 });
671 }
672 }
673
674 let table = Table::new(&rows);
675 println!("{table}");
676 return;
677 }
678 }
679
680 for (i, item) in items.iter().enumerate() {
682 println!("{}: {}", i, format_value_for_table(item));
683 }
684 }
685 Value::Object(obj) => {
686 if obj.len() > MAX_TABLE_ROWS {
688 println!(
689 "Object too large: {} fields (max {} for table display)",
690 obj.len(),
691 MAX_TABLE_ROWS
692 );
693 println!("Use --format json or --jq to process the full data");
694 return;
695 }
696
697 let rows: Vec<KeyValue> = obj
699 .iter()
700 .map(|(key, value)| KeyValue {
701 key: key.clone(),
702 value: format_value_for_table(value),
703 })
704 .collect();
705
706 let table = Table::new(&rows);
707 println!("{table}");
708 }
709 _ => {
710 println!("{}", format_value_for_table(json_value));
712 }
713 }
714}
715
716fn format_value_for_table(value: &Value) -> String {
718 match value {
719 Value::Null => "null".to_string(),
720 Value::Bool(b) => b.to_string(),
721 Value::Number(n) => n.to_string(),
722 Value::String(s) => s.clone(),
723 Value::Array(arr) => {
724 if arr.len() <= 3 {
725 format!(
726 "[{}]",
727 arr.iter()
728 .map(format_value_for_table)
729 .collect::<Vec<_>>()
730 .join(", ")
731 )
732 } else {
733 format!("[{} items]", arr.len())
734 }
735 }
736 Value::Object(obj) => {
737 if obj.len() <= 2 {
738 format!(
739 "{{{}}}",
740 obj.iter()
741 .map(|(k, v)| format!("{}: {}", k, format_value_for_table(v)))
742 .collect::<Vec<_>>()
743 .join(", ")
744 )
745 } else {
746 format!("{{object with {} fields}}", obj.len())
747 }
748 }
749 }
750}
751
752fn apply_jq_filter(response_text: &str, filter: &str) -> Result<String, Error> {
754 let json_value: Value =
756 serde_json::from_str(response_text).map_err(|e| Error::JqFilterError {
757 reason: format!("Response is not valid JSON: {e}"),
758 })?;
759
760 #[cfg(feature = "jq")]
761 {
762 use jaq_interpret::{Ctx, FilterT, ParseCtx, RcIter, Val};
764 use jaq_parse::parse;
765 use jaq_std::std;
766
767 let (expr, errs) = parse(filter, jaq_parse::main());
769 if !errs.is_empty() {
770 return Err(Error::JqFilterError {
771 reason: format!("Parse error in jq expression: {}", errs[0]),
772 });
773 }
774
775 let mut ctx = ParseCtx::new(Vec::new());
777 ctx.insert_defs(std());
778 let filter = ctx.compile(expr.unwrap());
779
780 let jaq_value = serde_json_to_jaq_val(&json_value);
782
783 let inputs = RcIter::new(core::iter::empty());
785 let ctx = Ctx::new([], &inputs);
786 let results: Result<Vec<Val>, _> = filter.run((ctx, jaq_value.into())).collect();
787
788 match results {
789 Ok(vals) => {
790 if vals.is_empty() {
791 Ok("null".to_string())
792 } else if vals.len() == 1 {
793 let json_val = jaq_val_to_serde_json(&vals[0]);
795 serde_json::to_string_pretty(&json_val).map_err(|e| Error::JqFilterError {
796 reason: format!("Failed to serialize result: {e}"),
797 })
798 } else {
799 let json_vals: Vec<Value> = vals.iter().map(jaq_val_to_serde_json).collect();
801 let array = Value::Array(json_vals);
802 serde_json::to_string_pretty(&array).map_err(|e| Error::JqFilterError {
803 reason: format!("Failed to serialize results: {e}"),
804 })
805 }
806 }
807 Err(e) => Err(Error::JqFilterError {
808 reason: format!("Filter execution error: {e}"),
809 }),
810 }
811 }
812
813 #[cfg(not(feature = "jq"))]
814 {
815 apply_basic_jq_filter(&json_value, filter)
817 }
818}
819
820#[cfg(not(feature = "jq"))]
821fn apply_basic_jq_filter(json_value: &Value, filter: &str) -> Result<String, Error> {
823 let uses_advanced_features = filter.contains('[')
825 || filter.contains(']')
826 || filter.contains('|')
827 || filter.contains('(')
828 || filter.contains(')')
829 || filter.contains("select")
830 || filter.contains("map")
831 || filter.contains("length");
832
833 if uses_advanced_features {
834 eprintln!("Warning: Advanced JQ features require building with --features jq");
835 eprintln!(" Currently only basic field access is supported (e.g., '.field', '.nested.field')");
836 eprintln!(" To enable full JQ support: cargo install aperture-cli --features jq");
837 }
838
839 let result = match filter {
840 "." => json_value.clone(),
841 ".[]" => {
842 match json_value {
844 Value::Array(arr) => {
845 Value::Array(arr.clone())
847 }
848 Value::Object(obj) => {
849 Value::Array(obj.values().cloned().collect())
851 }
852 _ => Value::Null,
853 }
854 }
855 ".length" => {
856 match json_value {
858 Value::Array(arr) => Value::Number(arr.len().into()),
859 Value::Object(obj) => Value::Number(obj.len().into()),
860 Value::String(s) => Value::Number(s.len().into()),
861 _ => Value::Null,
862 }
863 }
864 filter if filter.starts_with(".[].") => {
865 let field_path = &filter[4..]; match json_value {
868 Value::Array(arr) => {
869 let mapped: Vec<Value> = arr
870 .iter()
871 .map(|item| get_nested_field(item, field_path))
872 .collect();
873 Value::Array(mapped)
874 }
875 _ => Value::Null,
876 }
877 }
878 filter if filter.starts_with('.') => {
879 let field_path = &filter[1..]; get_nested_field(json_value, field_path)
882 }
883 _ => {
884 return Err(Error::JqFilterError {
885 reason: format!("Unsupported JQ filter: '{filter}'. Only basic field access like '.name' or '.metadata.role' is supported without the full jq library."),
886 });
887 }
888 };
889
890 serde_json::to_string_pretty(&result).map_err(|e| Error::JqFilterError {
891 reason: format!("Failed to serialize filtered result: {e}"),
892 })
893}
894
895#[cfg(not(feature = "jq"))]
896fn get_nested_field(json_value: &Value, field_path: &str) -> Value {
898 let parts: Vec<&str> = field_path.split('.').collect();
899 let mut current = json_value;
900
901 for part in parts {
902 if part.is_empty() {
903 continue;
904 }
905
906 if part.starts_with('[') && part.ends_with(']') {
908 let index_str = &part[1..part.len() - 1];
909 if let Ok(index) = index_str.parse::<usize>() {
910 match current {
911 Value::Array(arr) => {
912 if let Some(item) = arr.get(index) {
913 current = item;
914 } else {
915 return Value::Null;
916 }
917 }
918 _ => return Value::Null,
919 }
920 } else {
921 return Value::Null;
922 }
923 continue;
924 }
925
926 match current {
927 Value::Object(obj) => {
928 if let Some(field) = obj.get(part) {
929 current = field;
930 } else {
931 return Value::Null;
932 }
933 }
934 Value::Array(arr) => {
935 if let Ok(index) = part.parse::<usize>() {
937 if let Some(item) = arr.get(index) {
938 current = item;
939 } else {
940 return Value::Null;
941 }
942 } else {
943 return Value::Null;
944 }
945 }
946 _ => return Value::Null,
947 }
948 }
949
950 current.clone()
951}
952
953#[cfg(feature = "jq")]
954fn serde_json_to_jaq_val(value: &Value) -> jaq_interpret::Val {
956 use jaq_interpret::Val;
957 use std::rc::Rc;
958
959 match value {
960 Value::Null => Val::Null,
961 Value::Bool(b) => Val::Bool(*b),
962 Value::Number(n) => {
963 if let Some(i) = n.as_i64() {
964 if let Ok(isize_val) = isize::try_from(i) {
966 Val::Int(isize_val)
967 } else {
968 Val::Float(i as f64)
970 }
971 } else if let Some(f) = n.as_f64() {
972 Val::Float(f)
973 } else {
974 Val::Null
975 }
976 }
977 Value::String(s) => Val::Str(s.clone().into()),
978 Value::Array(arr) => {
979 let jaq_arr: Vec<Val> = arr.iter().map(serde_json_to_jaq_val).collect();
980 Val::Arr(Rc::new(jaq_arr))
981 }
982 Value::Object(obj) => {
983 let mut jaq_obj = indexmap::IndexMap::with_hasher(ahash::RandomState::new());
984 for (k, v) in obj {
985 jaq_obj.insert(Rc::new(k.clone()), serde_json_to_jaq_val(v));
986 }
987 Val::Obj(Rc::new(jaq_obj))
988 }
989 }
990}
991
992#[cfg(feature = "jq")]
993fn jaq_val_to_serde_json(val: &jaq_interpret::Val) -> Value {
995 use jaq_interpret::Val;
996
997 match val {
998 Val::Null => Value::Null,
999 Val::Bool(b) => Value::Bool(*b),
1000 Val::Int(i) => {
1001 Value::Number((*i as i64).into())
1003 }
1004 Val::Float(f) => {
1005 if let Some(num) = serde_json::Number::from_f64(*f) {
1006 Value::Number(num)
1007 } else {
1008 Value::Null
1009 }
1010 }
1011 Val::Str(s) => Value::String(s.to_string()),
1012 Val::Arr(arr) => {
1013 let json_arr: Vec<Value> = arr.iter().map(jaq_val_to_serde_json).collect();
1014 Value::Array(json_arr)
1015 }
1016 Val::Obj(obj) => {
1017 let mut json_obj = serde_json::Map::new();
1018 for (k, v) in obj.iter() {
1019 json_obj.insert(k.to_string(), jaq_val_to_serde_json(v));
1020 }
1021 Value::Object(json_obj)
1022 }
1023 _ => Value::Null, }
1025}