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 if language == "elixir" {
283 let current = std::mem::take(&mut out);
284 out = format!("Enum.at({current}.{f}, 0)");
285 } else {
286 out.push('.');
287 out.push_str(f);
288 out.push_str("[0]");
289 }
290 }
291 PathSegment::MapAccess { field, key } => {
292 out.push('.');
293 out.push_str(field);
294 if language == "elixir" {
296 out.push_str(&format!("[\"{key}\"]"));
297 } else {
298 out.push_str(&format!(".get(\"{key}\")"));
299 }
300 }
301 PathSegment::Length => match language {
302 "ruby" => out.push_str(".length"),
303 "elixir" => {
304 let current = std::mem::take(&mut out);
305 out = format!("length({current})");
306 }
307 _ => {
309 let current = std::mem::take(&mut out);
310 out = format!("len({current})");
311 }
312 },
313 }
314 }
315 out
316}
317
318fn render_typescript(segments: &[PathSegment], result_var: &str) -> String {
321 let mut out = result_var.to_string();
322 for seg in segments {
323 match seg {
324 PathSegment::Field(f) => {
325 out.push('.');
326 out.push_str(&f.to_lower_camel_case());
327 }
328 PathSegment::ArrayField(f) => {
329 out.push('.');
330 out.push_str(&f.to_lower_camel_case());
331 out.push_str("[0]");
332 }
333 PathSegment::MapAccess { field, key } => {
334 out.push('.');
335 out.push_str(&field.to_lower_camel_case());
336 out.push_str(&format!("[\"{key}\"]"));
337 }
338 PathSegment::Length => {
339 out.push_str(".length");
340 }
341 }
342 }
343 out
344}
345
346fn render_wasm(segments: &[PathSegment], result_var: &str) -> String {
351 let mut out = result_var.to_string();
352 for seg in segments {
353 match seg {
354 PathSegment::Field(f) => {
355 out.push('.');
356 out.push_str(&f.to_lower_camel_case());
357 }
358 PathSegment::ArrayField(f) => {
359 out.push('.');
360 out.push_str(&f.to_lower_camel_case());
361 out.push_str("[0]");
362 }
363 PathSegment::MapAccess { field, key } => {
364 out.push('.');
365 out.push_str(&field.to_lower_camel_case());
366 out.push_str(&format!(".get(\"{key}\")"));
367 }
368 PathSegment::Length => {
369 out.push_str(".length");
370 }
371 }
372 }
373 out
374}
375
376fn render_go(segments: &[PathSegment], result_var: &str) -> String {
378 let mut out = result_var.to_string();
379 for seg in segments {
380 match seg {
381 PathSegment::Field(f) => {
382 out.push('.');
383 out.push_str(&f.to_pascal_case());
384 }
385 PathSegment::ArrayField(f) => {
386 out.push('.');
387 out.push_str(&f.to_pascal_case());
388 out.push_str("[0]");
389 }
390 PathSegment::MapAccess { field, key } => {
391 out.push('.');
392 out.push_str(&field.to_pascal_case());
393 out.push_str(&format!("[\"{key}\"]"));
394 }
395 PathSegment::Length => {
396 let current = std::mem::take(&mut out);
397 out = format!("len({current})");
398 }
399 }
400 }
401 out
402}
403
404fn render_java(segments: &[PathSegment], result_var: &str) -> String {
407 let mut out = result_var.to_string();
408 for seg in segments {
409 match seg {
410 PathSegment::Field(f) => {
411 out.push('.');
412 out.push_str(&f.to_lower_camel_case());
413 out.push_str("()");
414 }
415 PathSegment::ArrayField(f) => {
416 out.push('.');
417 out.push_str(&f.to_lower_camel_case());
418 out.push_str("().getFirst()");
419 }
420 PathSegment::MapAccess { field, key } => {
421 out.push('.');
422 out.push_str(&field.to_lower_camel_case());
423 out.push_str(&format!("().get(\"{key}\")"));
424 }
425 PathSegment::Length => {
426 out.push_str(".size()");
427 }
428 }
429 }
430 out
431}
432
433fn render_java_with_optionals(segments: &[PathSegment], result_var: &str, optional_fields: &HashSet<String>) -> String {
438 let mut out = result_var.to_string();
439 let mut path_so_far = String::new();
440 for (i, seg) in segments.iter().enumerate() {
441 let is_leaf = i == segments.len() - 1;
442 match seg {
443 PathSegment::Field(f) => {
444 if !path_so_far.is_empty() {
445 path_so_far.push('.');
446 }
447 path_so_far.push_str(f);
448 out.push('.');
449 out.push_str(&f.to_lower_camel_case());
450 out.push_str("()");
451 if !is_leaf && optional_fields.contains(&path_so_far) {
453 out.push_str(".orElseThrow()");
454 }
455 }
456 PathSegment::ArrayField(f) => {
457 if !path_so_far.is_empty() {
458 path_so_far.push('.');
459 }
460 path_so_far.push_str(f);
461 out.push('.');
462 out.push_str(&f.to_lower_camel_case());
463 out.push_str("().getFirst()");
464 }
465 PathSegment::MapAccess { field, key } => {
466 if !path_so_far.is_empty() {
467 path_so_far.push('.');
468 }
469 path_so_far.push_str(field);
470 out.push('.');
471 out.push_str(&field.to_lower_camel_case());
472 out.push_str(&format!("().get(\"{key}\")"));
473 }
474 PathSegment::Length => {
475 out.push_str(".size()");
476 }
477 }
478 }
479 out
480}
481
482fn render_rust_with_optionals(segments: &[PathSegment], result_var: &str, optional_fields: &HashSet<String>) -> String {
487 let mut out = result_var.to_string();
488 let mut path_so_far = String::new();
489 for (i, seg) in segments.iter().enumerate() {
490 let is_leaf = i == segments.len() - 1;
491 match seg {
492 PathSegment::Field(f) => {
493 if !path_so_far.is_empty() {
494 path_so_far.push('.');
495 }
496 path_so_far.push_str(f);
497 out.push('.');
498 out.push_str(&f.to_snake_case());
499 if !is_leaf && optional_fields.contains(&path_so_far) {
501 out.push_str(".as_ref().unwrap()");
502 }
503 }
504 PathSegment::ArrayField(f) => {
505 if !path_so_far.is_empty() {
506 path_so_far.push('.');
507 }
508 path_so_far.push_str(f);
509 out.push('.');
510 out.push_str(&f.to_snake_case());
511 out.push_str("[0]");
512 }
513 PathSegment::MapAccess { field, key } => {
514 if !path_so_far.is_empty() {
515 path_so_far.push('.');
516 }
517 path_so_far.push_str(field);
518 out.push('.');
519 out.push_str(&field.to_snake_case());
520 out.push_str(&format!(".get(\"{key}\").map(|s| s.as_str())"));
521 }
522 PathSegment::Length => {
523 out.push_str(".len()");
524 }
525 }
526 }
527 out
528}
529
530fn render_pascal_dot(segments: &[PathSegment], result_var: &str) -> String {
532 let mut out = result_var.to_string();
533 for seg in segments {
534 match seg {
535 PathSegment::Field(f) => {
536 out.push('.');
537 out.push_str(&f.to_pascal_case());
538 }
539 PathSegment::ArrayField(f) => {
540 out.push('.');
541 out.push_str(&f.to_pascal_case());
542 out.push_str("[0]");
543 }
544 PathSegment::MapAccess { field, key } => {
545 out.push('.');
546 out.push_str(&field.to_pascal_case());
547 out.push_str(&format!("[\"{key}\"]"));
548 }
549 PathSegment::Length => {
550 out.push_str(".Count");
551 }
552 }
553 }
554 out
555}
556
557fn render_csharp_with_optionals(
562 segments: &[PathSegment],
563 result_var: &str,
564 optional_fields: &HashSet<String>,
565) -> String {
566 let mut out = result_var.to_string();
567 let mut path_so_far = String::new();
568 for (i, seg) in segments.iter().enumerate() {
569 let is_leaf = i == segments.len() - 1;
570 match seg {
571 PathSegment::Field(f) => {
572 if !path_so_far.is_empty() {
573 path_so_far.push('.');
574 }
575 path_so_far.push_str(f);
576 out.push('.');
577 out.push_str(&f.to_pascal_case());
578 if !is_leaf && optional_fields.contains(&path_so_far) {
580 out.push('!');
581 }
582 }
583 PathSegment::ArrayField(f) => {
584 if !path_so_far.is_empty() {
585 path_so_far.push('.');
586 }
587 path_so_far.push_str(f);
588 out.push('.');
589 out.push_str(&f.to_pascal_case());
590 out.push_str("[0]");
591 }
592 PathSegment::MapAccess { field, key } => {
593 if !path_so_far.is_empty() {
594 path_so_far.push('.');
595 }
596 path_so_far.push_str(field);
597 out.push('.');
598 out.push_str(&field.to_pascal_case());
599 out.push_str(&format!("[\"{key}\"]"));
600 }
601 PathSegment::Length => {
602 out.push_str(".Count");
603 }
604 }
605 }
606 out
607}
608
609fn render_php(segments: &[PathSegment], result_var: &str) -> String {
611 let mut out = result_var.to_string();
612 for seg in segments {
613 match seg {
614 PathSegment::Field(f) => {
615 out.push_str("->");
616 out.push_str(f);
617 }
618 PathSegment::ArrayField(f) => {
619 out.push_str("->");
620 out.push_str(f);
621 out.push_str("[0]");
622 }
623 PathSegment::MapAccess { field, key } => {
624 out.push_str("->");
625 out.push_str(field);
626 out.push_str(&format!("[\"{key}\"]"));
627 }
628 PathSegment::Length => {
629 let current = std::mem::take(&mut out);
630 out = format!("count({current})");
631 }
632 }
633 }
634 out
635}
636
637fn render_r(segments: &[PathSegment], result_var: &str) -> String {
639 let mut out = result_var.to_string();
640 for seg in segments {
641 match seg {
642 PathSegment::Field(f) => {
643 out.push('$');
644 out.push_str(f);
645 }
646 PathSegment::ArrayField(f) => {
647 out.push('$');
648 out.push_str(f);
649 out.push_str("[[1]]");
650 }
651 PathSegment::MapAccess { field, key } => {
652 out.push('$');
653 out.push_str(field);
654 out.push_str(&format!("[[\"{key}\"]]"));
655 }
656 PathSegment::Length => {
657 let current = std::mem::take(&mut out);
658 out = format!("length({current})");
659 }
660 }
661 }
662 out
663}
664
665fn render_c(segments: &[PathSegment], result_var: &str) -> String {
667 let mut parts = Vec::new();
668 let mut trailing_length = false;
669 for seg in segments {
670 match seg {
671 PathSegment::Field(f) | PathSegment::ArrayField(f) => parts.push(f.to_snake_case()),
672 PathSegment::MapAccess { field, key } => {
673 parts.push(field.to_snake_case());
674 parts.push(key.clone());
675 }
676 PathSegment::Length => {
677 trailing_length = true;
678 }
679 }
680 }
681 let suffix = parts.join("_");
682 if trailing_length {
683 format!("result_{suffix}_count({result_var})")
684 } else {
685 format!("result_{suffix}({result_var})")
686 }
687}
688
689#[cfg(test)]
690mod tests {
691 use super::*;
692
693 fn make_resolver() -> FieldResolver {
694 let mut fields = HashMap::new();
695 fields.insert("title".to_string(), "metadata.document.title".to_string());
696 fields.insert("tags".to_string(), "metadata.tags[name]".to_string());
697 fields.insert("og".to_string(), "metadata.document.open_graph".to_string());
698 fields.insert("twitter".to_string(), "metadata.document.twitter_card".to_string());
699 fields.insert("canonical".to_string(), "metadata.document.canonical_url".to_string());
700 fields.insert("og_tag".to_string(), "metadata.open_graph_tags[og_title]".to_string());
701
702 let mut optional = HashSet::new();
703 optional.insert("metadata.document.title".to_string());
704
705 FieldResolver::new(&fields, &optional, &HashSet::new(), &HashSet::new())
706 }
707
708 fn make_resolver_with_doc_optional() -> FieldResolver {
709 let mut fields = HashMap::new();
710 fields.insert("title".to_string(), "metadata.document.title".to_string());
711 fields.insert("tags".to_string(), "metadata.tags[name]".to_string());
712
713 let mut optional = HashSet::new();
714 optional.insert("document".to_string());
715 optional.insert("metadata.document.title".to_string());
716 optional.insert("metadata.document".to_string());
717
718 FieldResolver::new(&fields, &optional, &HashSet::new(), &HashSet::new())
719 }
720
721 #[test]
722 fn test_resolve_alias() {
723 let r = make_resolver();
724 assert_eq!(r.resolve("title"), "metadata.document.title");
725 }
726
727 #[test]
728 fn test_resolve_passthrough() {
729 let r = make_resolver();
730 assert_eq!(r.resolve("content"), "content");
731 }
732
733 #[test]
734 fn test_is_optional() {
735 let r = make_resolver();
736 assert!(r.is_optional("metadata.document.title"));
737 assert!(!r.is_optional("content"));
738 }
739
740 #[test]
741 fn test_accessor_rust_struct() {
742 let r = make_resolver();
743 assert_eq!(r.accessor("title", "rust", "result"), "result.metadata.document.title");
744 }
745
746 #[test]
747 fn test_accessor_rust_map() {
748 let r = make_resolver();
749 assert_eq!(
750 r.accessor("tags", "rust", "result"),
751 "result.metadata.tags.get(\"name\").map(|s| s.as_str())"
752 );
753 }
754
755 #[test]
756 fn test_accessor_python() {
757 let r = make_resolver();
758 assert_eq!(
759 r.accessor("title", "python", "result"),
760 "result.metadata.document.title"
761 );
762 }
763
764 #[test]
765 fn test_accessor_go() {
766 let r = make_resolver();
767 assert_eq!(r.accessor("title", "go", "result"), "result.Metadata.Document.Title");
768 }
769
770 #[test]
771 fn test_accessor_typescript() {
772 let r = make_resolver();
773 assert_eq!(
774 r.accessor("title", "typescript", "result"),
775 "result.metadata.document.title"
776 );
777 }
778
779 #[test]
780 fn test_accessor_typescript_snake_to_camel() {
781 let r = make_resolver();
782 assert_eq!(
783 r.accessor("og", "typescript", "result"),
784 "result.metadata.document.openGraph"
785 );
786 assert_eq!(
787 r.accessor("twitter", "typescript", "result"),
788 "result.metadata.document.twitterCard"
789 );
790 assert_eq!(
791 r.accessor("canonical", "typescript", "result"),
792 "result.metadata.document.canonicalUrl"
793 );
794 }
795
796 #[test]
797 fn test_accessor_typescript_map_snake_to_camel() {
798 let r = make_resolver();
799 assert_eq!(
800 r.accessor("og_tag", "typescript", "result"),
801 "result.metadata.openGraphTags[\"og_title\"]"
802 );
803 }
804
805 #[test]
806 fn test_accessor_node_alias() {
807 let r = make_resolver();
808 assert_eq!(r.accessor("og", "node", "result"), "result.metadata.document.openGraph");
809 }
810
811 #[test]
812 fn test_accessor_wasm_camel_case() {
813 let r = make_resolver();
814 assert_eq!(r.accessor("og", "wasm", "result"), "result.metadata.document.openGraph");
815 assert_eq!(
816 r.accessor("twitter", "wasm", "result"),
817 "result.metadata.document.twitterCard"
818 );
819 assert_eq!(
820 r.accessor("canonical", "wasm", "result"),
821 "result.metadata.document.canonicalUrl"
822 );
823 }
824
825 #[test]
826 fn test_accessor_wasm_map_access() {
827 let r = make_resolver();
828 assert_eq!(
830 r.accessor("og_tag", "wasm", "result"),
831 "result.metadata.openGraphTags.get(\"og_title\")"
832 );
833 }
834
835 #[test]
836 fn test_accessor_java() {
837 let r = make_resolver();
838 assert_eq!(
839 r.accessor("title", "java", "result"),
840 "result.metadata().document().title()"
841 );
842 }
843
844 #[test]
845 fn test_accessor_csharp() {
846 let r = make_resolver();
847 assert_eq!(
848 r.accessor("title", "csharp", "result"),
849 "result.Metadata.Document.Title"
850 );
851 }
852
853 #[test]
854 fn test_accessor_php() {
855 let r = make_resolver();
856 assert_eq!(
857 r.accessor("title", "php", "$result"),
858 "$result->metadata->document->title"
859 );
860 }
861
862 #[test]
863 fn test_accessor_r() {
864 let r = make_resolver();
865 assert_eq!(r.accessor("title", "r", "result"), "result$metadata$document$title");
866 }
867
868 #[test]
869 fn test_accessor_c() {
870 let r = make_resolver();
871 assert_eq!(
872 r.accessor("title", "c", "result"),
873 "result_metadata_document_title(result)"
874 );
875 }
876
877 #[test]
878 fn test_rust_unwrap_binding() {
879 let r = make_resolver();
880 let (binding, var) = r.rust_unwrap_binding("title", "result").unwrap();
881 assert_eq!(var, "metadata_document_title");
882 assert!(binding.contains("as_deref().unwrap_or(\"\")"));
883 }
884
885 #[test]
886 fn test_rust_unwrap_binding_non_optional() {
887 let r = make_resolver();
888 assert!(r.rust_unwrap_binding("content", "result").is_none());
889 }
890
891 #[test]
892 fn test_direct_field_no_alias() {
893 let r = make_resolver();
894 assert_eq!(r.accessor("content", "rust", "result"), "result.content");
895 assert_eq!(r.accessor("content", "go", "result"), "result.Content");
896 }
897
898 #[test]
899 fn test_accessor_rust_with_optionals() {
900 let r = make_resolver_with_doc_optional();
901 assert_eq!(
903 r.accessor("title", "rust", "result"),
904 "result.metadata.document.as_ref().unwrap().title"
905 );
906 }
907
908 #[test]
909 fn test_accessor_csharp_with_optionals() {
910 let r = make_resolver_with_doc_optional();
911 assert_eq!(
913 r.accessor("title", "csharp", "result"),
914 "result.Metadata.Document!.Title"
915 );
916 }
917
918 #[test]
919 fn test_accessor_rust_non_optional_field() {
920 let r = make_resolver();
921 assert_eq!(r.accessor("content", "rust", "result"), "result.content");
923 }
924
925 #[test]
926 fn test_accessor_csharp_non_optional_field() {
927 let r = make_resolver();
928 assert_eq!(r.accessor("content", "csharp", "result"), "result.Content");
930 }
931}