Skip to main content

schemaorg_rs/profiles/google/
recipe.rs

1//! Google Rich Results profile for Recipe.
2//!
3//! Source: <https://developers.google.com/search/docs/appearance/structured-data/recipe>
4//! Verified: 2026-04-01
5
6use crate::types::SchemaNode;
7use crate::validation::ValidationDiagnostic as VD;
8
9use super::common::{recommend_property, require_property, validate_nested};
10use crate::profiles::{NodeProfileResult, Profile, TypeEligibility};
11
12/// Google Rich Results profile for Recipe structured data.
13pub struct GoogleRecipeProfile;
14
15impl Profile for GoogleRecipeProfile {
16    fn name(&self) -> &'static str {
17        "google"
18    }
19
20    fn version(&self) -> &'static str {
21        "2026-04-01"
22    }
23
24    fn source_url(&self) -> &'static str {
25        "https://developers.google.com/search/docs/appearance/structured-data/recipe"
26    }
27
28    fn supported_types(&self) -> &[&str] {
29        &["Recipe"]
30    }
31
32    fn evaluate_node(&self, node: &SchemaNode, _vocab_diagnostics: &[VD]) -> NodeProfileResult {
33        let path = "Recipe";
34        let mut diagnostics = Vec::new();
35        let mut required_missing = Vec::new();
36        let mut recommended_missing = Vec::new();
37
38        // Required fields
39        for prop in &["name", "image"] {
40            if let Some(d) = require_property(node, prop, path) {
41                required_missing.push((*prop).to_string());
42                diagnostics.push(d);
43            }
44        }
45
46        // Recommended fields
47        for prop in &[
48            "author",
49            "datePublished",
50            "description",
51            "recipeCuisine",
52            "prepTime",
53            "cookTime",
54            "totalTime",
55            "recipeYield",
56            "recipeCategory",
57            "recipeIngredient",
58            "recipeInstructions",
59            "nutrition",
60            "video",
61        ] {
62            if let Some(d) = recommend_property(node, prop, path) {
63                recommended_missing.push((*prop).to_string());
64                diagnostics.push(d);
65            }
66        }
67
68        // Nested HowToStep validation (in recipeInstructions)
69        let step_diags = validate_nested(
70            node,
71            "recipeInstructions",
72            "HowToStep",
73            &["text"],
74            &[],
75            path,
76        );
77        diagnostics.extend(step_diags);
78
79        let eligible = required_missing.is_empty();
80
81        NodeProfileResult {
82            type_eligibility: TypeEligibility {
83                schema_type: "Recipe".to_string(),
84                eligible,
85                required_missing,
86                recommended_missing,
87                field_diagnostics: diagnostics,
88            },
89        }
90    }
91}