1use alef_codegen::naming::to_go_name;
8use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
9use std::collections::{HashMap, HashSet};
10
11pub struct FieldResolver {
13 aliases: HashMap<String, String>,
14 optional_fields: HashSet<String>,
15 result_fields: HashSet<String>,
16 array_fields: HashSet<String>,
17}
18
19#[derive(Debug, Clone)]
21enum PathSegment {
22 Field(String),
24 ArrayField(String),
26 MapAccess { field: String, key: String },
28 Length,
30}
31
32impl FieldResolver {
33 pub fn new(
36 fields: &HashMap<String, String>,
37 optional: &HashSet<String>,
38 result_fields: &HashSet<String>,
39 array_fields: &HashSet<String>,
40 ) -> Self {
41 Self {
42 aliases: fields.clone(),
43 optional_fields: optional.clone(),
44 result_fields: result_fields.clone(),
45 array_fields: array_fields.clone(),
46 }
47 }
48
49 pub fn resolve<'a>(&'a self, fixture_field: &'a str) -> &'a str {
52 self.aliases
53 .get(fixture_field)
54 .map(String::as_str)
55 .unwrap_or(fixture_field)
56 }
57
58 pub fn is_optional(&self, field: &str) -> bool {
60 if self.optional_fields.contains(field) {
61 return true;
62 }
63 let normalized = field.replace("[].", ".");
66 if normalized != field && self.optional_fields.contains(normalized.as_str()) {
67 return true;
68 }
69 for af in &self.array_fields {
71 if let Some(rest) = field.strip_prefix(af.as_str()) {
72 if let Some(rest) = rest.strip_prefix('.') {
73 let with_bracket = format!("{af}[].{rest}");
74 if self.optional_fields.contains(with_bracket.as_str()) {
75 return true;
76 }
77 }
78 }
79 }
80 false
81 }
82
83 pub fn has_alias(&self, fixture_field: &str) -> bool {
85 self.aliases.contains_key(fixture_field)
86 }
87
88 pub fn is_valid_for_result(&self, fixture_field: &str) -> bool {
95 if self.result_fields.is_empty() {
96 return true;
97 }
98 let resolved = self.resolve(fixture_field);
99 let first_segment = resolved.split('.').next().unwrap_or(resolved);
100 let first_segment = first_segment.split('[').next().unwrap_or(first_segment);
102 self.result_fields.contains(first_segment)
103 }
104
105 pub fn is_array(&self, field: &str) -> bool {
107 self.array_fields.contains(field)
108 }
109
110 pub fn has_map_access(&self, fixture_field: &str) -> bool {
114 let resolved = self.resolve(fixture_field);
115 let segments = parse_path(resolved);
116 segments.iter().any(|s| matches!(s, PathSegment::MapAccess { .. }))
117 }
118
119 pub fn accessor(&self, fixture_field: &str, language: &str, result_var: &str) -> String {
122 let resolved = self.resolve(fixture_field);
123 let segments = parse_path(resolved);
124
125 let segments = self.inject_array_indexing(segments);
128
129 match language {
130 "java" => render_java_with_optionals(&segments, result_var, &self.optional_fields),
131 "rust" => render_rust_with_optionals(&segments, result_var, &self.optional_fields),
132 "csharp" => render_csharp_with_optionals(&segments, result_var, &self.optional_fields),
133 _ => render_accessor(&segments, language, result_var),
134 }
135 }
136
137 fn inject_array_indexing(&self, segments: Vec<PathSegment>) -> Vec<PathSegment> {
144 if self.array_fields.is_empty() {
145 return segments;
146 }
147 let len = segments.len();
148 let mut result = Vec::with_capacity(len);
149 let mut path_so_far = String::new();
150 for i in 0..len {
151 let seg = &segments[i];
152 match seg {
153 PathSegment::Field(f) => {
154 if !path_so_far.is_empty() {
155 path_so_far.push('.');
156 }
157 path_so_far.push_str(f);
158 let next_is_length = i + 1 < len && matches!(segments[i + 1], PathSegment::Length);
163 if i + 1 < len && self.array_fields.contains(&path_so_far) && !next_is_length {
164 result.push(PathSegment::ArrayField(f.clone()));
165 } else {
166 result.push(seg.clone());
167 }
168 }
169 _ => {
170 result.push(seg.clone());
171 }
172 }
173 }
174 result
175 }
176
177 pub fn rust_unwrap_binding(&self, fixture_field: &str, result_var: &str) -> Option<(String, String)> {
180 let resolved = self.resolve(fixture_field);
181 if !self.is_optional(resolved) {
182 return None;
183 }
184 let segments = parse_path(resolved);
185 let segments = self.inject_array_indexing(segments);
186 let local_var = resolved.replace(['.', '['], "_").replace(']', "");
187 let accessor = render_accessor(&segments, "rust", result_var);
188 let has_map_access = segments.iter().any(|s| matches!(s, PathSegment::MapAccess { .. }));
191 let is_array = self.is_array(resolved);
193 let binding = if has_map_access {
194 format!("let {local_var} = {accessor}.unwrap_or(\"\");")
195 } else if is_array {
196 format!("let {local_var} = {accessor}.as_deref().unwrap_or(&[]);")
197 } else {
198 format!("let {local_var} = {accessor}.as_deref().unwrap_or(\"\");")
199 };
200 Some((binding, local_var))
201 }
202}
203
204fn parse_path(path: &str) -> Vec<PathSegment> {
207 let mut segments = Vec::new();
208 for part in path.split('.') {
209 if part == "length" || part == "count" || part == "size" {
210 segments.push(PathSegment::Length);
211 } else if let Some(bracket_pos) = part.find('[') {
212 let field = part[..bracket_pos].to_string();
213 let key = part[bracket_pos + 1..].trim_end_matches(']').to_string();
214 if key.is_empty() {
215 segments.push(PathSegment::ArrayField(field));
217 } else {
218 segments.push(PathSegment::MapAccess { field, key });
219 }
220 } else {
221 segments.push(PathSegment::Field(part.to_string()));
222 }
223 }
224 segments
225}
226
227fn render_accessor(segments: &[PathSegment], language: &str, result_var: &str) -> String {
229 match language {
230 "rust" => render_rust(segments, result_var),
231 "python" => render_dot_access(segments, result_var, "python"),
232 "typescript" | "node" => render_typescript(segments, result_var),
233 "wasm" => render_wasm(segments, result_var),
234 "go" => render_go(segments, result_var),
235 "java" => render_java(segments, result_var),
236 "csharp" => render_pascal_dot(segments, result_var),
237 "ruby" => render_dot_access(segments, result_var, "ruby"),
238 "php" => render_php(segments, result_var),
239 "elixir" => render_dot_access(segments, result_var, "elixir"),
240 "r" => render_r(segments, result_var),
241 "c" => render_c(segments, result_var),
242 _ => render_dot_access(segments, result_var, language),
243 }
244}
245
246fn render_rust(segments: &[PathSegment], result_var: &str) -> String {
252 let mut out = result_var.to_string();
253 for seg in segments {
254 match seg {
255 PathSegment::Field(f) => {
256 out.push('.');
257 out.push_str(&f.to_snake_case());
258 }
259 PathSegment::ArrayField(f) => {
260 out.push('.');
261 out.push_str(&f.to_snake_case());
262 out.push_str("[0]");
263 }
264 PathSegment::MapAccess { field, key } => {
265 out.push('.');
266 out.push_str(&field.to_snake_case());
267 out.push_str(&format!(".get(\"{key}\").map(|s| s.as_str())"));
268 }
269 PathSegment::Length => {
270 out.push_str(".len()");
271 }
272 }
273 }
274 out
275}
276
277fn render_dot_access(segments: &[PathSegment], result_var: &str, language: &str) -> String {
279 let mut out = result_var.to_string();
280 for seg in segments {
281 match seg {
282 PathSegment::Field(f) => {
283 out.push('.');
284 out.push_str(f);
285 }
286 PathSegment::ArrayField(f) => {
287 if language == "elixir" {
288 let current = std::mem::take(&mut out);
289 out = format!("Enum.at({current}.{f}, 0)");
290 } else {
291 out.push('.');
292 out.push_str(f);
293 out.push_str("[0]");
294 }
295 }
296 PathSegment::MapAccess { field, key } => {
297 out.push('.');
298 out.push_str(field);
299 if language == "elixir" {
301 out.push_str(&format!("[\"{key}\"]"));
302 } else {
303 out.push_str(&format!(".get(\"{key}\")"));
304 }
305 }
306 PathSegment::Length => match language {
307 "ruby" => out.push_str(".length"),
308 "elixir" => {
309 let current = std::mem::take(&mut out);
310 out = format!("length({current})");
311 }
312 _ => {
314 let current = std::mem::take(&mut out);
315 out = format!("len({current})");
316 }
317 },
318 }
319 }
320 out
321}
322
323fn render_typescript(segments: &[PathSegment], result_var: &str) -> String {
326 let mut out = result_var.to_string();
327 for seg in segments {
328 match seg {
329 PathSegment::Field(f) => {
330 out.push('.');
331 out.push_str(&f.to_lower_camel_case());
332 }
333 PathSegment::ArrayField(f) => {
334 out.push('.');
335 out.push_str(&f.to_lower_camel_case());
336 out.push_str("[0]");
337 }
338 PathSegment::MapAccess { field, key } => {
339 out.push('.');
340 out.push_str(&field.to_lower_camel_case());
341 out.push_str(&format!("[\"{key}\"]"));
342 }
343 PathSegment::Length => {
344 out.push_str(".length");
345 }
346 }
347 }
348 out
349}
350
351fn render_wasm(segments: &[PathSegment], result_var: &str) -> String {
356 let mut out = result_var.to_string();
357 for seg in segments {
358 match seg {
359 PathSegment::Field(f) => {
360 out.push('.');
361 out.push_str(&f.to_lower_camel_case());
362 }
363 PathSegment::ArrayField(f) => {
364 out.push('.');
365 out.push_str(&f.to_lower_camel_case());
366 out.push_str("[0]");
367 }
368 PathSegment::MapAccess { field, key } => {
369 out.push('.');
370 out.push_str(&field.to_lower_camel_case());
371 out.push_str(&format!(".get(\"{key}\")"));
372 }
373 PathSegment::Length => {
374 out.push_str(".length");
375 }
376 }
377 }
378 out
379}
380
381fn render_go(segments: &[PathSegment], result_var: &str) -> String {
386 let mut out = result_var.to_string();
387 for seg in segments {
388 match seg {
389 PathSegment::Field(f) => {
390 out.push('.');
391 out.push_str(&to_go_name(f));
392 }
393 PathSegment::ArrayField(f) => {
394 out.push('.');
395 out.push_str(&to_go_name(f));
396 out.push_str("[0]");
397 }
398 PathSegment::MapAccess { field, key } => {
399 out.push('.');
400 out.push_str(&to_go_name(field));
401 out.push_str(&format!("[\"{key}\"]"));
402 }
403 PathSegment::Length => {
404 let current = std::mem::take(&mut out);
405 out = format!("len({current})");
406 }
407 }
408 }
409 out
410}
411
412fn render_java(segments: &[PathSegment], result_var: &str) -> String {
415 let mut out = result_var.to_string();
416 for seg in segments {
417 match seg {
418 PathSegment::Field(f) => {
419 out.push('.');
420 out.push_str(&f.to_lower_camel_case());
421 out.push_str("()");
422 }
423 PathSegment::ArrayField(f) => {
424 out.push('.');
425 out.push_str(&f.to_lower_camel_case());
426 out.push_str("().getFirst()");
427 }
428 PathSegment::MapAccess { field, key } => {
429 out.push('.');
430 out.push_str(&field.to_lower_camel_case());
431 out.push_str(&format!("().get(\"{key}\")"));
432 }
433 PathSegment::Length => {
434 out.push_str(".size()");
435 }
436 }
437 }
438 out
439}
440
441fn render_java_with_optionals(segments: &[PathSegment], result_var: &str, optional_fields: &HashSet<String>) -> String {
446 let mut out = result_var.to_string();
447 let mut path_so_far = String::new();
448 for (i, seg) in segments.iter().enumerate() {
449 let is_leaf = i == segments.len() - 1;
450 match seg {
451 PathSegment::Field(f) => {
452 if !path_so_far.is_empty() {
453 path_so_far.push('.');
454 }
455 path_so_far.push_str(f);
456 out.push('.');
457 out.push_str(&f.to_lower_camel_case());
458 out.push_str("()");
459 if !is_leaf && optional_fields.contains(&path_so_far) {
461 out.push_str(".orElseThrow()");
462 }
463 }
464 PathSegment::ArrayField(f) => {
465 if !path_so_far.is_empty() {
466 path_so_far.push('.');
467 }
468 path_so_far.push_str(f);
469 out.push('.');
470 out.push_str(&f.to_lower_camel_case());
471 out.push_str("().getFirst()");
472 }
473 PathSegment::MapAccess { field, key } => {
474 if !path_so_far.is_empty() {
475 path_so_far.push('.');
476 }
477 path_so_far.push_str(field);
478 out.push('.');
479 out.push_str(&field.to_lower_camel_case());
480 out.push_str(&format!("().get(\"{key}\")"));
481 }
482 PathSegment::Length => {
483 out.push_str(".size()");
484 }
485 }
486 }
487 out
488}
489
490fn render_rust_with_optionals(segments: &[PathSegment], result_var: &str, optional_fields: &HashSet<String>) -> String {
495 let mut out = result_var.to_string();
496 let mut path_so_far = String::new();
497 for (i, seg) in segments.iter().enumerate() {
498 let is_leaf = i == segments.len() - 1;
499 match seg {
500 PathSegment::Field(f) => {
501 if !path_so_far.is_empty() {
502 path_so_far.push('.');
503 }
504 path_so_far.push_str(f);
505 out.push('.');
506 out.push_str(&f.to_snake_case());
507 if !is_leaf && optional_fields.contains(&path_so_far) {
509 out.push_str(".as_ref().unwrap()");
510 }
511 }
512 PathSegment::ArrayField(f) => {
513 if !path_so_far.is_empty() {
514 path_so_far.push('.');
515 }
516 path_so_far.push_str(f);
517 out.push('.');
518 out.push_str(&f.to_snake_case());
519 out.push_str("[0]");
520 }
521 PathSegment::MapAccess { field, key } => {
522 if !path_so_far.is_empty() {
523 path_so_far.push('.');
524 }
525 path_so_far.push_str(field);
526 out.push('.');
527 out.push_str(&field.to_snake_case());
528 out.push_str(&format!(".get(\"{key}\").map(|s| s.as_str())"));
529 }
530 PathSegment::Length => {
531 out.push_str(".len()");
532 }
533 }
534 }
535 out
536}
537
538fn render_pascal_dot(segments: &[PathSegment], result_var: &str) -> String {
540 let mut out = result_var.to_string();
541 for seg in segments {
542 match seg {
543 PathSegment::Field(f) => {
544 out.push('.');
545 out.push_str(&f.to_pascal_case());
546 }
547 PathSegment::ArrayField(f) => {
548 out.push('.');
549 out.push_str(&f.to_pascal_case());
550 out.push_str("[0]");
551 }
552 PathSegment::MapAccess { field, key } => {
553 out.push('.');
554 out.push_str(&field.to_pascal_case());
555 out.push_str(&format!("[\"{key}\"]"));
556 }
557 PathSegment::Length => {
558 out.push_str(".Count");
559 }
560 }
561 }
562 out
563}
564
565fn render_csharp_with_optionals(
570 segments: &[PathSegment],
571 result_var: &str,
572 optional_fields: &HashSet<String>,
573) -> String {
574 let mut out = result_var.to_string();
575 let mut path_so_far = String::new();
576 for (i, seg) in segments.iter().enumerate() {
577 let is_leaf = i == segments.len() - 1;
578 match seg {
579 PathSegment::Field(f) => {
580 if !path_so_far.is_empty() {
581 path_so_far.push('.');
582 }
583 path_so_far.push_str(f);
584 out.push('.');
585 out.push_str(&f.to_pascal_case());
586 if !is_leaf && optional_fields.contains(&path_so_far) {
588 out.push('!');
589 }
590 }
591 PathSegment::ArrayField(f) => {
592 if !path_so_far.is_empty() {
593 path_so_far.push('.');
594 }
595 path_so_far.push_str(f);
596 out.push('.');
597 out.push_str(&f.to_pascal_case());
598 out.push_str("[0]");
599 }
600 PathSegment::MapAccess { field, key } => {
601 if !path_so_far.is_empty() {
602 path_so_far.push('.');
603 }
604 path_so_far.push_str(field);
605 out.push('.');
606 out.push_str(&field.to_pascal_case());
607 out.push_str(&format!("[\"{key}\"]"));
608 }
609 PathSegment::Length => {
610 out.push_str(".Count");
611 }
612 }
613 }
614 out
615}
616
617fn render_php(segments: &[PathSegment], result_var: &str) -> String {
619 let mut out = result_var.to_string();
620 for seg in segments {
621 match seg {
622 PathSegment::Field(f) => {
623 out.push_str("->");
624 out.push_str(f);
625 }
626 PathSegment::ArrayField(f) => {
627 out.push_str("->");
628 out.push_str(f);
629 out.push_str("[0]");
630 }
631 PathSegment::MapAccess { field, key } => {
632 out.push_str("->");
633 out.push_str(field);
634 out.push_str(&format!("[\"{key}\"]"));
635 }
636 PathSegment::Length => {
637 let current = std::mem::take(&mut out);
638 out = format!("count({current})");
639 }
640 }
641 }
642 out
643}
644
645fn render_r(segments: &[PathSegment], result_var: &str) -> String {
647 let mut out = result_var.to_string();
648 for seg in segments {
649 match seg {
650 PathSegment::Field(f) => {
651 out.push('$');
652 out.push_str(f);
653 }
654 PathSegment::ArrayField(f) => {
655 out.push('$');
656 out.push_str(f);
657 out.push_str("[[1]]");
658 }
659 PathSegment::MapAccess { field, key } => {
660 out.push('$');
661 out.push_str(field);
662 out.push_str(&format!("[[\"{key}\"]]"));
663 }
664 PathSegment::Length => {
665 let current = std::mem::take(&mut out);
666 out = format!("length({current})");
667 }
668 }
669 }
670 out
671}
672
673fn render_c(segments: &[PathSegment], result_var: &str) -> String {
675 let mut parts = Vec::new();
676 let mut trailing_length = false;
677 for seg in segments {
678 match seg {
679 PathSegment::Field(f) | PathSegment::ArrayField(f) => parts.push(f.to_snake_case()),
680 PathSegment::MapAccess { field, key } => {
681 parts.push(field.to_snake_case());
682 parts.push(key.clone());
683 }
684 PathSegment::Length => {
685 trailing_length = true;
686 }
687 }
688 }
689 let suffix = parts.join("_");
690 if trailing_length {
691 format!("result_{suffix}_count({result_var})")
692 } else {
693 format!("result_{suffix}({result_var})")
694 }
695}
696
697#[cfg(test)]
698mod tests {
699 use super::*;
700
701 fn make_resolver() -> FieldResolver {
702 let mut fields = HashMap::new();
703 fields.insert("title".to_string(), "metadata.document.title".to_string());
704 fields.insert("tags".to_string(), "metadata.tags[name]".to_string());
705 fields.insert("og".to_string(), "metadata.document.open_graph".to_string());
706 fields.insert("twitter".to_string(), "metadata.document.twitter_card".to_string());
707 fields.insert("canonical".to_string(), "metadata.document.canonical_url".to_string());
708 fields.insert("og_tag".to_string(), "metadata.open_graph_tags[og_title]".to_string());
709
710 let mut optional = HashSet::new();
711 optional.insert("metadata.document.title".to_string());
712
713 FieldResolver::new(&fields, &optional, &HashSet::new(), &HashSet::new())
714 }
715
716 fn make_resolver_with_doc_optional() -> FieldResolver {
717 let mut fields = HashMap::new();
718 fields.insert("title".to_string(), "metadata.document.title".to_string());
719 fields.insert("tags".to_string(), "metadata.tags[name]".to_string());
720
721 let mut optional = HashSet::new();
722 optional.insert("document".to_string());
723 optional.insert("metadata.document.title".to_string());
724 optional.insert("metadata.document".to_string());
725
726 FieldResolver::new(&fields, &optional, &HashSet::new(), &HashSet::new())
727 }
728
729 #[test]
730 fn test_resolve_alias() {
731 let r = make_resolver();
732 assert_eq!(r.resolve("title"), "metadata.document.title");
733 }
734
735 #[test]
736 fn test_resolve_passthrough() {
737 let r = make_resolver();
738 assert_eq!(r.resolve("content"), "content");
739 }
740
741 #[test]
742 fn test_is_optional() {
743 let r = make_resolver();
744 assert!(r.is_optional("metadata.document.title"));
745 assert!(!r.is_optional("content"));
746 }
747
748 #[test]
749 fn test_accessor_rust_struct() {
750 let r = make_resolver();
751 assert_eq!(r.accessor("title", "rust", "result"), "result.metadata.document.title");
752 }
753
754 #[test]
755 fn test_accessor_rust_map() {
756 let r = make_resolver();
757 assert_eq!(
758 r.accessor("tags", "rust", "result"),
759 "result.metadata.tags.get(\"name\").map(|s| s.as_str())"
760 );
761 }
762
763 #[test]
764 fn test_accessor_python() {
765 let r = make_resolver();
766 assert_eq!(
767 r.accessor("title", "python", "result"),
768 "result.metadata.document.title"
769 );
770 }
771
772 #[test]
773 fn test_accessor_go() {
774 let r = make_resolver();
775 assert_eq!(r.accessor("title", "go", "result"), "result.Metadata.Document.Title");
776 }
777
778 #[test]
779 fn test_accessor_go_initialism_fields() {
780 let mut fields = std::collections::HashMap::new();
783 fields.insert("content".to_string(), "html".to_string());
784 fields.insert("link_url".to_string(), "links.url".to_string());
785 let r = FieldResolver::new(&fields, &HashSet::new(), &HashSet::new(), &HashSet::new());
786
787 assert_eq!(r.accessor("content", "go", "result"), "result.HTML");
788 assert_eq!(r.accessor("link_url", "go", "result"), "result.Links.URL");
789 assert_eq!(r.accessor("html", "go", "result"), "result.HTML");
791 assert_eq!(r.accessor("url", "go", "result"), "result.URL");
792 assert_eq!(r.accessor("id", "go", "result"), "result.ID");
793 assert_eq!(r.accessor("user_id", "go", "result"), "result.UserID");
794 assert_eq!(r.accessor("request_url", "go", "result"), "result.RequestURL");
795 assert_eq!(r.accessor("links", "go", "result"), "result.Links");
796 }
797
798 #[test]
799 fn test_accessor_typescript() {
800 let r = make_resolver();
801 assert_eq!(
802 r.accessor("title", "typescript", "result"),
803 "result.metadata.document.title"
804 );
805 }
806
807 #[test]
808 fn test_accessor_typescript_snake_to_camel() {
809 let r = make_resolver();
810 assert_eq!(
811 r.accessor("og", "typescript", "result"),
812 "result.metadata.document.openGraph"
813 );
814 assert_eq!(
815 r.accessor("twitter", "typescript", "result"),
816 "result.metadata.document.twitterCard"
817 );
818 assert_eq!(
819 r.accessor("canonical", "typescript", "result"),
820 "result.metadata.document.canonicalUrl"
821 );
822 }
823
824 #[test]
825 fn test_accessor_typescript_map_snake_to_camel() {
826 let r = make_resolver();
827 assert_eq!(
828 r.accessor("og_tag", "typescript", "result"),
829 "result.metadata.openGraphTags[\"og_title\"]"
830 );
831 }
832
833 #[test]
834 fn test_accessor_node_alias() {
835 let r = make_resolver();
836 assert_eq!(r.accessor("og", "node", "result"), "result.metadata.document.openGraph");
837 }
838
839 #[test]
840 fn test_accessor_wasm_camel_case() {
841 let r = make_resolver();
842 assert_eq!(r.accessor("og", "wasm", "result"), "result.metadata.document.openGraph");
843 assert_eq!(
844 r.accessor("twitter", "wasm", "result"),
845 "result.metadata.document.twitterCard"
846 );
847 assert_eq!(
848 r.accessor("canonical", "wasm", "result"),
849 "result.metadata.document.canonicalUrl"
850 );
851 }
852
853 #[test]
854 fn test_accessor_wasm_map_access() {
855 let r = make_resolver();
856 assert_eq!(
858 r.accessor("og_tag", "wasm", "result"),
859 "result.metadata.openGraphTags.get(\"og_title\")"
860 );
861 }
862
863 #[test]
864 fn test_accessor_java() {
865 let r = make_resolver();
866 assert_eq!(
867 r.accessor("title", "java", "result"),
868 "result.metadata().document().title()"
869 );
870 }
871
872 #[test]
873 fn test_accessor_csharp() {
874 let r = make_resolver();
875 assert_eq!(
876 r.accessor("title", "csharp", "result"),
877 "result.Metadata.Document.Title"
878 );
879 }
880
881 #[test]
882 fn test_accessor_php() {
883 let r = make_resolver();
884 assert_eq!(
885 r.accessor("title", "php", "$result"),
886 "$result->metadata->document->title"
887 );
888 }
889
890 #[test]
891 fn test_accessor_r() {
892 let r = make_resolver();
893 assert_eq!(r.accessor("title", "r", "result"), "result$metadata$document$title");
894 }
895
896 #[test]
897 fn test_accessor_c() {
898 let r = make_resolver();
899 assert_eq!(
900 r.accessor("title", "c", "result"),
901 "result_metadata_document_title(result)"
902 );
903 }
904
905 #[test]
906 fn test_rust_unwrap_binding() {
907 let r = make_resolver();
908 let (binding, var) = r.rust_unwrap_binding("title", "result").unwrap();
909 assert_eq!(var, "metadata_document_title");
910 assert!(binding.contains("as_deref().unwrap_or(\"\")"));
911 }
912
913 #[test]
914 fn test_rust_unwrap_binding_non_optional() {
915 let r = make_resolver();
916 assert!(r.rust_unwrap_binding("content", "result").is_none());
917 }
918
919 #[test]
920 fn test_direct_field_no_alias() {
921 let r = make_resolver();
922 assert_eq!(r.accessor("content", "rust", "result"), "result.content");
923 assert_eq!(r.accessor("content", "go", "result"), "result.Content");
924 }
925
926 #[test]
927 fn test_accessor_rust_with_optionals() {
928 let r = make_resolver_with_doc_optional();
929 assert_eq!(
931 r.accessor("title", "rust", "result"),
932 "result.metadata.document.as_ref().unwrap().title"
933 );
934 }
935
936 #[test]
937 fn test_accessor_csharp_with_optionals() {
938 let r = make_resolver_with_doc_optional();
939 assert_eq!(
941 r.accessor("title", "csharp", "result"),
942 "result.Metadata.Document!.Title"
943 );
944 }
945
946 #[test]
947 fn test_accessor_rust_non_optional_field() {
948 let r = make_resolver();
949 assert_eq!(r.accessor("content", "rust", "result"), "result.content");
951 }
952
953 #[test]
954 fn test_accessor_csharp_non_optional_field() {
955 let r = make_resolver();
956 assert_eq!(r.accessor("content", "csharp", "result"), "result.Content");
958 }
959}