Skip to main content

schemaorg_rs/profiles/google/
article.rs

1//! Google Rich Results profile for Article (and subtypes).
2//!
3//! Source: <https://developers.google.com/search/docs/appearance/structured-data/article>
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 Article structured data.
13///
14/// Applies to: `Article`, `NewsArticle`, `BlogPosting`, `TechArticle`,
15/// `ScholarlyArticle` (all subtypes via inheritance).
16pub struct GoogleArticleProfile;
17
18impl Profile for GoogleArticleProfile {
19    fn name(&self) -> &'static str {
20        "google"
21    }
22
23    fn version(&self) -> &'static str {
24        "2026-04-01"
25    }
26
27    fn source_url(&self) -> &'static str {
28        "https://developers.google.com/search/docs/appearance/structured-data/article"
29    }
30
31    fn supported_types(&self) -> &[&str] {
32        &[
33            "Article",
34            "NewsArticle",
35            "BlogPosting",
36            "TechArticle",
37            "ScholarlyArticle",
38        ]
39    }
40
41    fn evaluate_node(&self, node: &SchemaNode, _vocab_diagnostics: &[VD]) -> NodeProfileResult {
42        let type_name = node.types.first().map_or("Article", |t| t.as_str());
43        let path = type_name;
44        let mut diagnostics = Vec::new();
45        let mut required_missing = Vec::new();
46        let mut recommended_missing = Vec::new();
47
48        // Required fields
49        for prop in &["headline", "image", "datePublished", "author"] {
50            if let Some(d) = require_property(node, prop, path) {
51                required_missing.push((*prop).to_string());
52                diagnostics.push(d);
53            }
54        }
55
56        // Recommended fields
57        for prop in &["dateModified", "publisher", "mainEntityOfPage"] {
58            if let Some(d) = recommend_property(node, prop, path) {
59                recommended_missing.push((*prop).to_string());
60                diagnostics.push(d);
61            }
62        }
63
64        // Nested Author validation
65        let author_diags = validate_nested(node, "author", "Person", &["name"], &["url"], path);
66        diagnostics.extend(author_diags);
67
68        // Nested Publisher validation
69        let publisher_diags = validate_nested(
70            node,
71            "publisher",
72            "Organization",
73            &["name"],
74            &["logo"],
75            path,
76        );
77        diagnostics.extend(publisher_diags);
78
79        let eligible = required_missing.is_empty();
80
81        NodeProfileResult {
82            type_eligibility: TypeEligibility {
83                schema_type: type_name.to_string(),
84                eligible,
85                required_missing,
86                recommended_missing,
87                field_diagnostics: diagnostics,
88            },
89        }
90    }
91}