1use serde::{Deserialize, Serialize};
8use std::fmt;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct HfCategory {
17 pub name: String,
18 pub components: Vec<HfComponent>,
19}
20
21impl HfCategory {
22 pub fn new(name: impl Into<String>) -> Self {
23 Self { name: name.into(), components: Vec::new() }
24 }
25
26 #[allow(clippy::should_implement_trait)]
27 pub fn add(mut self, component: HfComponent) -> Self {
28 self.components.push(component);
29 self
30 }
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct HfComponent {
36 pub name: String,
37 pub description: String,
38 pub url: Option<String>,
39}
40
41impl HfComponent {
42 pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
43 Self { name: name.into(), description: description.into(), url: None }
44 }
45
46 pub fn with_url(mut self, url: impl Into<String>) -> Self {
47 self.url = Some(url.into());
48 self
49 }
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct HfTree {
55 pub name: String,
56 pub categories: Vec<HfCategory>,
57}
58
59impl HfTree {
60 pub fn new(name: impl Into<String>) -> Self {
61 Self { name: name.into(), categories: Vec::new() }
62 }
63
64 pub fn add_category(mut self, category: HfCategory) -> Self {
65 self.categories.push(category);
66 self
67 }
68
69 pub fn total_components(&self) -> usize {
70 self.categories.iter().map(|c| c.components.len()).sum()
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80pub enum IntegrationType {
81 Compatible,
83 Alternative,
85 Orchestrates,
87 Uses,
89 Incompatible,
91}
92
93impl fmt::Display for IntegrationType {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 Self::Compatible => write!(f, "✓ COMPATIBLE"),
97 Self::Alternative => write!(f, "⚡ ALTERNATIVE"),
98 Self::Orchestrates => write!(f, "🔄 ORCHESTRATES"),
99 Self::Uses => write!(f, "📦 USES"),
100 Self::Incompatible => write!(f, "✗ INCOMPATIBLE"),
101 }
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct IntegrationMapping {
108 pub paiml_component: String,
109 pub hf_equivalent: String,
110 pub integration_type: IntegrationType,
111 pub notes: Option<String>,
112}
113
114impl IntegrationMapping {
115 pub fn new(
116 paiml: impl Into<String>,
117 hf: impl Into<String>,
118 integration: IntegrationType,
119 ) -> Self {
120 Self {
121 paiml_component: paiml.into(),
122 hf_equivalent: hf.into(),
123 integration_type: integration,
124 notes: None,
125 }
126 }
127
128 pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
129 self.notes = Some(notes.into());
130 self
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct IntegrationCategory {
137 pub name: String,
138 pub mappings: Vec<IntegrationMapping>,
139}
140
141impl IntegrationCategory {
142 pub fn new(name: impl Into<String>) -> Self {
143 Self { name: name.into(), mappings: Vec::new() }
144 }
145
146 #[allow(clippy::should_implement_trait)]
147 pub fn add(mut self, mapping: IntegrationMapping) -> Self {
148 self.mappings.push(mapping);
149 self
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct IntegrationTree {
156 pub categories: Vec<IntegrationCategory>,
157}
158
159impl IntegrationTree {
160 pub fn new() -> Self {
161 Self { categories: Vec::new() }
162 }
163
164 pub fn add_category(mut self, category: IntegrationCategory) -> Self {
165 self.categories.push(category);
166 self
167 }
168
169 pub fn total_mappings(&self) -> usize {
170 self.categories.iter().map(|c| c.mappings.len()).sum()
171 }
172
173 pub fn compatible_count(&self) -> usize {
174 self.categories
175 .iter()
176 .flat_map(|c| &c.mappings)
177 .filter(|m| m.integration_type == IntegrationType::Compatible)
178 .count()
179 }
180
181 pub fn alternative_count(&self) -> usize {
182 self.categories
183 .iter()
184 .flat_map(|c| &c.mappings)
185 .filter(|m| m.integration_type == IntegrationType::Alternative)
186 .count()
187 }
188}
189
190impl Default for IntegrationTree {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196pub fn format_hf_tree_ascii(tree: &HfTree) -> String {
202 let mut output = format!("{} ({} categories)\n", tree.name, tree.categories.len());
203
204 for (cat_idx, category) in tree.categories.iter().enumerate() {
205 let is_last_cat = cat_idx == tree.categories.len() - 1;
206 let cat_prefix = if is_last_cat { "└── " } else { "├── " };
207 output.push_str(&format!("{}{}\n", cat_prefix, category.name));
208
209 for (comp_idx, comp) in category.components.iter().enumerate() {
210 let is_last_comp = comp_idx == category.components.len() - 1;
211 let comp_prefix = if is_last_cat { " " } else { "│ " };
212 let comp_branch = if is_last_comp { "└── " } else { "├── " };
213 output.push_str(&format!(
214 "{}{}{:<20} ({})\n",
215 comp_prefix, comp_branch, comp.name, comp.description
216 ));
217 }
218 }
219
220 output
221}
222
223pub fn format_integration_tree_ascii(tree: &IntegrationTree) -> String {
225 let mut output = String::from("PAIML ↔ HuggingFace Integration Map\n");
226 output.push_str(&"═".repeat(65));
227 output.push('\n');
228 output.push('\n');
229
230 output.push_str(&format!("┌{:─<20}┬{:─<20}┬{:─<22}┐\n", "", "", ""));
231 output.push_str(&format!(
232 "│ {:^18} │ {:^18} │ {:^20} │\n",
233 "PAIML Component", "HF Equivalent", "Integration Type"
234 ));
235 output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
236
237 for category in &tree.categories {
238 output.push_str(&format!(
239 "│ {:^18} │ {:^18} │ {:^20} │\n",
240 category.name.to_uppercase(),
241 "",
242 ""
243 ));
244 output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
245
246 for mapping in &category.mappings {
247 output.push_str(&format!(
248 "│ {:<18} │ {:<18} │ {:<20} │\n",
249 mapping.paiml_component,
250 mapping.hf_equivalent,
251 format!("{}", mapping.integration_type)
252 ));
253 }
254
255 output.push_str(&format!("├{:─<20}┼{:─<20}┼{:─<22}┤\n", "", "", ""));
256 }
257
258 output.push_str(&format!("└{:─<20}┴{:─<20}┴{:─<22}┘\n", "", "", ""));
259
260 output.push_str("\nLegend:\n");
261 output.push_str(" ✓ COMPATIBLE - Interoperates with HF format/API\n");
262 output.push_str(" ⚡ ALTERNATIVE - PAIML native replacement (pure Rust)\n");
263 output.push_str(" 🔄 ORCHESTRATES - PAIML wraps/orchestrates HF\n");
264 output.push_str(" 📦 USES - PAIML uses HF library directly\n");
265
266 output.push_str(&format!(
267 "\nSummary: {} compatible, {} alternatives, {} total mappings\n",
268 tree.compatible_count(),
269 tree.alternative_count(),
270 tree.total_mappings()
271 ));
272
273 output
274}
275
276pub fn format_hf_tree_json(tree: &HfTree) -> Result<String, serde_json::Error> {
278 serde_json::to_string_pretty(tree)
279}
280
281pub fn format_integration_tree_json(tree: &IntegrationTree) -> Result<String, serde_json::Error> {
283 serde_json::to_string_pretty(tree)
284}
285
286pub fn build_hf_tree() -> HfTree {
292 HfTree::new("HuggingFace Ecosystem")
293 .add_category(
294 HfCategory::new("hub")
295 .add(HfComponent::new("models", "700K+ models"))
296 .add(HfComponent::new("datasets", "100K+ datasets"))
297 .add(HfComponent::new("spaces", "300K+ spaces"))
298 .add(HfComponent::new("papers", "Research papers")),
299 )
300 .add_category(
301 HfCategory::new("libraries")
302 .add(HfComponent::new("transformers", "Model architectures"))
303 .add(HfComponent::new("diffusers", "Diffusion models"))
304 .add(HfComponent::new("accelerate", "Distributed training"))
305 .add(HfComponent::new("peft", "Parameter-efficient fine-tuning"))
306 .add(HfComponent::new("trl", "Reinforcement learning"))
307 .add(HfComponent::new("optimum", "Hardware optimization"))
308 .add(HfComponent::new("datasets", "Dataset loading"))
309 .add(HfComponent::new("tokenizers", "Fast tokenization"))
310 .add(HfComponent::new("safetensors", "Safe serialization"))
311 .add(HfComponent::new("huggingface_hub", "Hub API client")),
312 )
313 .add_category(
314 HfCategory::new("inference")
315 .add(HfComponent::new("inference-api", "Serverless inference"))
316 .add(HfComponent::new("inference-endpoints", "Dedicated endpoints"))
317 .add(HfComponent::new("text-generation-inference", "TGI server")),
318 )
319 .add_category(
320 HfCategory::new("training")
321 .add(HfComponent::new("autotrain", "AutoML training"))
322 .add(HfComponent::new("trainer", "Training loops"))
323 .add(HfComponent::new("evaluate", "Metrics & evaluation")),
324 )
325 .add_category(
326 HfCategory::new("formats")
327 .add(HfComponent::new("safetensors", "Safe tensor format"))
328 .add(HfComponent::new("gguf", "Quantized format"))
329 .add(HfComponent::new("onnx", "Cross-platform"))
330 .add(HfComponent::new("pytorch", "Native PyTorch")),
331 )
332 .add_category(
333 HfCategory::new("tasks")
334 .add(HfComponent::new("text-generation", "LLM generation"))
335 .add(HfComponent::new("text-classification", "Classification"))
336 .add(HfComponent::new("question-answering", "QA"))
337 .add(HfComponent::new("translation", "Translation"))
338 .add(HfComponent::new("summarization", "Summarization"))
339 .add(HfComponent::new("image-classification", "Vision"))
340 .add(HfComponent::new("text-to-image", "Diffusion"))
341 .add(HfComponent::new("speech-recognition", "ASR")),
342 )
343}
344
345pub fn build_integration_tree() -> IntegrationTree {
347 IntegrationTree::new()
348 .add_category(
349 IntegrationCategory::new("formats")
350 .add(
351 IntegrationMapping::new(".apr", "pickle/.joblib", IntegrationType::Alternative)
352 .with_notes("Safe sklearn model format"),
353 )
354 .add(
355 IntegrationMapping::new(".apr", "safetensors", IntegrationType::Alternative)
356 .with_notes("Safe tensor serialization"),
357 )
358 .add(
359 IntegrationMapping::new(".apr", "gguf", IntegrationType::Alternative)
360 .with_notes("Quantized model format"),
361 )
362 .add(
363 IntegrationMapping::new("realizar/gguf", "gguf", IntegrationType::Compatible)
364 .with_notes("Import GGUF models"),
365 )
366 .add(
367 IntegrationMapping::new(
368 "realizar/safetensors",
369 "safetensors",
370 IntegrationType::Compatible,
371 )
372 .with_notes("Import SafeTensors"),
373 ),
374 )
375 .add_category(
376 IntegrationCategory::new("hub_access")
377 .add(
378 IntegrationMapping::new(
379 "aprender/hf_hub",
380 "huggingface_hub",
381 IntegrationType::Uses,
382 )
383 .with_notes("hf-hub crate"),
384 )
385 .add(
386 IntegrationMapping::new(
387 "batuta/hf",
388 "huggingface_hub",
389 IntegrationType::Orchestrates,
390 )
391 .with_notes("CLI orchestration"),
392 ),
393 )
394 .add_category(
395 IntegrationCategory::new("registry")
396 .add(
397 IntegrationMapping::new(
398 "pacha",
399 "HF Hub registry",
400 IntegrationType::Alternative,
401 )
402 .with_notes("Model/data/recipe registry"),
403 )
404 .add(
405 IntegrationMapping::new("pacha", "MLflow/W&B", IntegrationType::Alternative)
406 .with_notes("Full lineage tracking"),
407 ),
408 )
409 .add_category(
410 IntegrationCategory::new("inference")
411 .add(
412 IntegrationMapping::new(
413 "realizar",
414 "transformers",
415 IntegrationType::Alternative,
416 )
417 .with_notes("Pure Rust LLM inference"),
418 )
419 .add(
420 IntegrationMapping::new("realizar", "TGI", IntegrationType::Alternative)
421 .with_notes("Native server"),
422 )
423 .add(
424 IntegrationMapping::new(
425 "realizar/moe",
426 "optimum",
427 IntegrationType::Alternative,
428 )
429 .with_notes("MoE backend selection"),
430 ),
431 )
432 .add_category(
433 IntegrationCategory::new("classical_ml")
434 .add(
435 IntegrationMapping::new("aprender", "sklearn", IntegrationType::Alternative)
436 .with_notes("Pure Rust ML algorithms"),
437 )
438 .add(
439 IntegrationMapping::new(
440 "aprender",
441 "xgboost/lightgbm",
442 IntegrationType::Alternative,
443 )
444 .with_notes("Gradient boosting"),
445 ),
446 )
447 .add_category(
448 IntegrationCategory::new("deep_learning")
449 .add(
450 IntegrationMapping::new(
451 "entrenar",
452 "PyTorch training",
453 IntegrationType::Alternative,
454 )
455 .with_notes("DL training loops"),
456 )
457 .add(
458 IntegrationMapping::new("alimentar", "datasets", IntegrationType::Alternative)
459 .with_notes("Data loading/streaming"),
460 ),
461 )
462 .add_category(
463 IntegrationCategory::new("data_formats")
464 .add(
465 IntegrationMapping::new(".ald", "parquet/arrow", IntegrationType::Alternative)
466 .with_notes("Secure dataset format"),
467 )
468 .add(
469 IntegrationMapping::new(".ald", "json/csv", IntegrationType::Alternative)
470 .with_notes("+encryption/signing/licensing"),
471 ),
472 )
473 .add_category(
474 IntegrationCategory::new("compute")
475 .add(
476 IntegrationMapping::new(
477 "trueno",
478 "NumPy/PyTorch tensors",
479 IntegrationType::Alternative,
480 )
481 .with_notes("SIMD tensor ops"),
482 )
483 .add(
484 IntegrationMapping::new("repartir", "accelerate", IntegrationType::Alternative)
485 .with_notes("Distributed compute"),
486 ),
487 )
488 .add_category(
489 IntegrationCategory::new("tokenization")
490 .add(
491 IntegrationMapping::new(
492 "realizar/tokenizer",
493 "tokenizers",
494 IntegrationType::Compatible,
495 )
496 .with_notes("Load HF tokenizers"),
497 )
498 .add(
499 IntegrationMapping::new(
500 "trueno-rag",
501 "tokenizers",
502 IntegrationType::Compatible,
503 )
504 .with_notes("RAG tokenization"),
505 ),
506 )
507 .add_category(
508 IntegrationCategory::new("apps")
509 .add(
510 IntegrationMapping::new("presentar", "gradio", IntegrationType::Alternative)
511 .with_notes("Rust app framework"),
512 )
513 .add(
514 IntegrationMapping::new(
515 "trueno-viz",
516 "visualization",
517 IntegrationType::Alternative,
518 )
519 .with_notes("GPU rendering"),
520 ),
521 )
522 .add_category(
523 IntegrationCategory::new("quality").add(
524 IntegrationMapping::new("certeza", "evaluate", IntegrationType::Alternative)
525 .with_notes("Rust metrics"),
526 ),
527 )
528 .add_category(
529 IntegrationCategory::new("mcp_tooling")
530 .add(
531 IntegrationMapping::new(
532 "pforge",
533 "LangChain Tools",
534 IntegrationType::Alternative,
535 )
536 .with_notes("Declarative MCP servers"),
537 )
538 .add(
539 IntegrationMapping::new(
540 "pmat",
541 "code analysis tools",
542 IntegrationType::Alternative,
543 )
544 .with_notes("AI context generation"),
545 )
546 .add(
547 IntegrationMapping::new("pmcp", "mcp-sdk", IntegrationType::Alternative)
548 .with_notes("Rust MCP runtime"),
549 ),
550 )
551}
552
553#[cfg(test)]
558#[allow(non_snake_case)]
559mod tests {
560 use super::*;
561
562 #[test]
567 fn test_HF_TREE_001_hf_component_new() {
568 let comp = HfComponent::new("transformers", "Model architectures");
569 assert_eq!(comp.name, "transformers");
570 assert_eq!(comp.description, "Model architectures");
571 assert!(comp.url.is_none());
572 }
573
574 #[test]
575 fn test_HF_TREE_001_hf_component_with_url() {
576 let comp = HfComponent::new("transformers", "Models")
577 .with_url("https://huggingface.co/docs/transformers");
578 assert!(comp.url.is_some());
579 }
580
581 #[test]
582 fn test_HF_TREE_001_hf_category_new() {
583 let cat = HfCategory::new("libraries");
584 assert_eq!(cat.name, "libraries");
585 assert!(cat.components.is_empty());
586 }
587
588 #[test]
589 fn test_HF_TREE_001_hf_category_add() {
590 let cat = HfCategory::new("libraries").add(HfComponent::new("transformers", "Models"));
591 assert_eq!(cat.components.len(), 1);
592 }
593
594 #[test]
599 fn test_HF_TREE_002_hf_tree_new() {
600 let tree = HfTree::new("Test");
601 assert_eq!(tree.name, "Test");
602 assert!(tree.categories.is_empty());
603 }
604
605 #[test]
606 fn test_HF_TREE_002_hf_tree_add_category() {
607 let tree = HfTree::new("Test").add_category(HfCategory::new("hub"));
608 assert_eq!(tree.categories.len(), 1);
609 }
610
611 #[test]
612 fn test_HF_TREE_002_hf_tree_total_components() {
613 let tree = HfTree::new("Test").add_category(
614 HfCategory::new("hub")
615 .add(HfComponent::new("models", "Models"))
616 .add(HfComponent::new("datasets", "Datasets")),
617 );
618 assert_eq!(tree.total_components(), 2);
619 }
620
621 #[test]
626 fn test_HF_TREE_003_integration_type_display() {
627 assert_eq!(format!("{}", IntegrationType::Compatible), "✓ COMPATIBLE");
628 assert_eq!(format!("{}", IntegrationType::Alternative), "⚡ ALTERNATIVE");
629 assert_eq!(format!("{}", IntegrationType::Orchestrates), "🔄 ORCHESTRATES");
630 assert_eq!(format!("{}", IntegrationType::Uses), "📦 USES");
631 }
632
633 #[test]
634 fn test_HF_TREE_003_integration_mapping_new() {
635 let mapping =
636 IntegrationMapping::new("realizar", "transformers", IntegrationType::Alternative);
637 assert_eq!(mapping.paiml_component, "realizar");
638 assert_eq!(mapping.hf_equivalent, "transformers");
639 }
640
641 #[test]
642 fn test_HF_TREE_003_integration_mapping_with_notes() {
643 let mapping =
644 IntegrationMapping::new("a", "b", IntegrationType::Compatible).with_notes("Test notes");
645 assert_eq!(mapping.notes, Some("Test notes".to_string()));
646 }
647
648 #[test]
653 fn test_HF_TREE_004_integration_tree_new() {
654 let tree = IntegrationTree::new();
655 assert!(tree.categories.is_empty());
656 }
657
658 #[test]
659 fn test_HF_TREE_004_integration_tree_counts() {
660 let tree = IntegrationTree::new().add_category(
661 IntegrationCategory::new("test")
662 .add(IntegrationMapping::new("a", "b", IntegrationType::Compatible))
663 .add(IntegrationMapping::new("c", "d", IntegrationType::Alternative)),
664 );
665 assert_eq!(tree.total_mappings(), 2);
666 assert_eq!(tree.compatible_count(), 1);
667 assert_eq!(tree.alternative_count(), 1);
668 }
669
670 #[test]
675 fn test_HF_TREE_005_format_hf_tree_ascii() {
676 let tree = HfTree::new("Test")
677 .add_category(HfCategory::new("hub").add(HfComponent::new("models", "700K+ models")));
678 let output = format_hf_tree_ascii(&tree);
679 assert!(output.contains("Test"));
680 assert!(output.contains("hub"));
681 assert!(output.contains("models"));
682 }
683
684 #[test]
685 fn test_HF_TREE_005_format_integration_tree_ascii() {
686 let tree = IntegrationTree::new().add_category(
687 IntegrationCategory::new("formats").add(IntegrationMapping::new(
688 "a",
689 "b",
690 IntegrationType::Compatible,
691 )),
692 );
693 let output = format_integration_tree_ascii(&tree);
694 assert!(output.contains("PAIML"));
695 assert!(output.contains("HuggingFace"));
696 assert!(output.contains("Legend"));
697 }
698
699 #[test]
700 fn test_HF_TREE_005_format_hf_tree_json() {
701 let tree = HfTree::new("Test");
702 let json = format_hf_tree_json(&tree).expect("unexpected failure");
703 assert!(json.contains("\"name\": \"Test\""));
704 }
705
706 #[test]
707 fn test_HF_TREE_005_format_integration_tree_json() {
708 let tree = IntegrationTree::new();
709 let json = format_integration_tree_json(&tree).expect("unexpected failure");
710 assert!(json.contains("categories"));
711 }
712
713 #[test]
718 fn test_HF_TREE_006_build_hf_tree() {
719 let tree = build_hf_tree();
720 assert_eq!(tree.name, "HuggingFace Ecosystem");
721 assert!(!tree.categories.is_empty());
722 assert!(tree.total_components() > 20);
723 }
724
725 #[test]
726 fn test_HF_TREE_006_build_hf_tree_has_hub() {
727 let tree = build_hf_tree();
728 let hub = tree.categories.iter().find(|c| c.name == "hub");
729 assert!(hub.is_some());
730 assert!(hub.expect("unexpected failure").components.iter().any(|c| c.name == "models"));
731 }
732
733 #[test]
734 fn test_HF_TREE_006_build_hf_tree_has_libraries() {
735 let tree = build_hf_tree();
736 let libs = tree.categories.iter().find(|c| c.name == "libraries");
737 assert!(libs.is_some());
738 assert!(libs
739 .expect("unexpected failure")
740 .components
741 .iter()
742 .any(|c| c.name == "transformers"));
743 }
744
745 #[test]
746 fn test_HF_TREE_006_build_integration_tree() {
747 let tree = build_integration_tree();
748 assert!(!tree.categories.is_empty());
749 assert!(tree.total_mappings() > 10);
750 }
751
752 #[test]
753 fn test_HF_TREE_006_build_integration_tree_has_formats() {
754 let tree = build_integration_tree();
755 let formats = tree.categories.iter().find(|c| c.name == "formats");
756 assert!(formats.is_some());
757 }
758
759 #[test]
760 fn test_HF_TREE_006_build_integration_tree_has_compatible() {
761 let tree = build_integration_tree();
762 assert!(tree.compatible_count() > 0);
763 }
764
765 #[test]
766 fn test_HF_TREE_006_build_integration_tree_has_alternatives() {
767 let tree = build_integration_tree();
768 assert!(tree.alternative_count() > 0);
769 }
770
771 #[test]
776 fn test_HF_TREE_007_full_hf_tree_output() {
777 let tree = build_hf_tree();
778 let output = format_hf_tree_ascii(&tree);
779 assert!(output.contains("HuggingFace Ecosystem"));
780 assert!(output.contains("hub"));
781 assert!(output.contains("libraries"));
782 assert!(output.contains("transformers"));
783 }
784
785 #[test]
786 fn test_HF_TREE_007_full_integration_tree_output() {
787 let tree = build_integration_tree();
788 let output = format_integration_tree_ascii(&tree);
789 assert!(output.contains("PAIML"));
790 assert!(output.contains("realizar"));
791 assert!(output.contains("COMPATIBLE"));
792 assert!(output.contains("ALTERNATIVE"));
793 }
794
795 #[test]
796 fn test_HF_TREE_007_json_roundtrip() {
797 let tree = build_hf_tree();
798 let json = format_hf_tree_json(&tree).expect("unexpected failure");
799 let parsed: HfTree = serde_json::from_str(&json).expect("json deserialize failed");
800 assert_eq!(parsed.name, tree.name);
801 }
802}