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