@gsriram24/structured-data-validator 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslint-header.txt +9 -0
- package/.releaserc.json +22 -0
- package/CHANGELOG.md +71 -0
- package/CODE_OF_CONDUCT.md +79 -0
- package/LICENSE +201 -0
- package/README.md +109 -0
- package/package.json +49 -0
- package/renovate.json +4 -0
- package/src/index.js +14 -0
- package/src/types/3DModel.js +21 -0
- package/src/types/AggregateOffer.js +23 -0
- package/src/types/AggregateRating.js +35 -0
- package/src/types/Answer.js +18 -0
- package/src/types/Article.js +26 -0
- package/src/types/Brand.js +18 -0
- package/src/types/BreadcrumbList.js +148 -0
- package/src/types/BroadcastEvent.js +23 -0
- package/src/types/Certification.js +26 -0
- package/src/types/Clip.js +25 -0
- package/src/types/DefinedRegion.js +38 -0
- package/src/types/Event.js +51 -0
- package/src/types/FAQPage.js +18 -0
- package/src/types/HowTo.js +27 -0
- package/src/types/HowToDirection.js +19 -0
- package/src/types/HowToSection.js +22 -0
- package/src/types/HowToStep.js +43 -0
- package/src/types/HowToTip.js +19 -0
- package/src/types/ImageObject.js +40 -0
- package/src/types/JobPosting.js +63 -0
- package/src/types/ListItem.js +28 -0
- package/src/types/LocalBusiness.js +30 -0
- package/src/types/MerchantReturnPolicy.js +96 -0
- package/src/types/Offer.js +39 -0
- package/src/types/OfferShippingDetails.js +27 -0
- package/src/types/Organization.js +18 -0
- package/src/types/PeopleAudience.js +37 -0
- package/src/types/Person.js +18 -0
- package/src/types/PriceSpecification.js +21 -0
- package/src/types/Product.js +90 -0
- package/src/types/ProductMerchant.js +88 -0
- package/src/types/QuantitativeValue.js +36 -0
- package/src/types/Question.js +21 -0
- package/src/types/Rating.js +56 -0
- package/src/types/Recipe.js +75 -0
- package/src/types/Review.js +35 -0
- package/src/types/SeekToAction.js +22 -0
- package/src/types/ShippingDeliveryTime.js +21 -0
- package/src/types/SizeSpecification.js +22 -0
- package/src/types/VideoObject.js +41 -0
- package/src/types/WebSite.js +23 -0
- package/src/types/base.js +201 -0
- package/src/types/schemaOrg.js +227 -0
- package/src/utils.js +15 -0
- package/src/validator.js +323 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class LocalBusinessValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('name'),
|
|
18
|
+
this.required('address', 'object'),
|
|
19
|
+
|
|
20
|
+
this.recommended('aggregateRating', 'object'),
|
|
21
|
+
this.recommended('geo', 'object'),
|
|
22
|
+
this.recommended('image', 'url'),
|
|
23
|
+
this.recommended('openingHoursSpecification', 'arrayOrObject'),
|
|
24
|
+
this.recommended('priceRange'),
|
|
25
|
+
this.recommended('review', 'arrayOrObject'),
|
|
26
|
+
this.recommended('telephone'),
|
|
27
|
+
this.recommended('url', 'url'),
|
|
28
|
+
].map((c) => c.bind(this));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class MerchantReturnPolicyValidator extends BaseValidator {
|
|
15
|
+
getConditions(data) {
|
|
16
|
+
const conditions = [this.countryOrLink];
|
|
17
|
+
|
|
18
|
+
const category = data.returnPolicyCategory;
|
|
19
|
+
|
|
20
|
+
// If returnPolicyCategory is present, we need to validate the return window
|
|
21
|
+
if (
|
|
22
|
+
category &&
|
|
23
|
+
(category.includes('MerchantReturnFiniteReturnWindow') ||
|
|
24
|
+
category.includes('MerchantReturnUnlimitedWindow'))
|
|
25
|
+
) {
|
|
26
|
+
if (category.includes('MerchantReturnFiniteReturnWindow')) {
|
|
27
|
+
conditions.push(this.recommended('merchantReturnDays', 'number'));
|
|
28
|
+
}
|
|
29
|
+
conditions.push(
|
|
30
|
+
this.recommended('returnFees'),
|
|
31
|
+
this.recommended('returnMethod'),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (data.returnFees && data.returnFees.includes('ReturnShippingFees')) {
|
|
35
|
+
conditions.push(this.recommended('returnShippingFeesAmount'));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Additional properties for Organization context
|
|
39
|
+
// TODO: Consider other subtypes of Organization
|
|
40
|
+
if (
|
|
41
|
+
this.inType('Organization') &&
|
|
42
|
+
this.inProperty('hasMerchantReturnPolicy')
|
|
43
|
+
) {
|
|
44
|
+
conditions.push(
|
|
45
|
+
this.recommended('customerRemorseReturnFees'),
|
|
46
|
+
this.recommended('customerRemorseReturnLabelSource'),
|
|
47
|
+
this.recommended('customerRemorseReturnShippingFeesAmount'),
|
|
48
|
+
this.recommended('itemCondition'),
|
|
49
|
+
this.recommended('itemDefectReturnFees'),
|
|
50
|
+
this.recommended('itemDefectReturnLabelSource'),
|
|
51
|
+
this.recommended('itemDefectReturnShippingFeesAmount'),
|
|
52
|
+
this.recommended('refundType'),
|
|
53
|
+
this.recommended('restockingFee'),
|
|
54
|
+
this.recommended('returnLabelSource'),
|
|
55
|
+
this.recommended('returnPolicyCountry'),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return conditions.map((c) => c.bind(this));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
countryOrLink(data) {
|
|
64
|
+
// Either applicableCountry and returnPolicyCategory need to be present
|
|
65
|
+
// or merchantReturnLink needs to be present, but only when nested under Organization
|
|
66
|
+
|
|
67
|
+
let valid = false;
|
|
68
|
+
if (
|
|
69
|
+
this.inProperty('hasMerchantReturnPolicy') &&
|
|
70
|
+
this.inType('Organization') &&
|
|
71
|
+
this.required('merchantReturnLink')(data) === null
|
|
72
|
+
) {
|
|
73
|
+
valid = true;
|
|
74
|
+
} else if (
|
|
75
|
+
this.required('applicableCountry')(data) === null &&
|
|
76
|
+
this.required('returnPolicyCategory')(data) === null
|
|
77
|
+
) {
|
|
78
|
+
valid = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!valid) {
|
|
82
|
+
return {
|
|
83
|
+
issueMessage:
|
|
84
|
+
'Either applicableCountry and returnPolicyCategory or merchantReturnLink must be present',
|
|
85
|
+
severity: 'ERROR',
|
|
86
|
+
path: this.path,
|
|
87
|
+
fieldName: 'applicableCountry',
|
|
88
|
+
fieldNames: [
|
|
89
|
+
'applicableCountry',
|
|
90
|
+
'returnPolicyCategory',
|
|
91
|
+
'merchantReturnLink',
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class PriceSpecificationValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
const conditions = [];
|
|
17
|
+
const offerIndex = this.path.findIndex(
|
|
18
|
+
(pathElement) => pathElement.type === 'Offer',
|
|
19
|
+
);
|
|
20
|
+
const productIsParent =
|
|
21
|
+
offerIndex > 0 ? this.path[offerIndex - 1].type === 'Product' : false;
|
|
22
|
+
|
|
23
|
+
if (productIsParent) {
|
|
24
|
+
conditions.push(
|
|
25
|
+
this.or(
|
|
26
|
+
this.required('price', 'number'),
|
|
27
|
+
this.required('priceSpecification.price', 'number'),
|
|
28
|
+
),
|
|
29
|
+
this.recommended('availability'),
|
|
30
|
+
this.or(
|
|
31
|
+
this.recommended('priceCurrency', 'currency'),
|
|
32
|
+
this.recommended('priceSpecification.priceCurrency', 'currency'),
|
|
33
|
+
),
|
|
34
|
+
this.recommended('priceValidUntil', 'date'),
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return conditions.map((c) => c.bind(this));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class OfferShippingDetailsValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('deliveryTime'),
|
|
18
|
+
this.required('shippingDestination'),
|
|
19
|
+
this.required('shippingRate'),
|
|
20
|
+
this.required('shippingRate.currency'),
|
|
21
|
+
this.or(
|
|
22
|
+
this.required('shippingRate.value'),
|
|
23
|
+
this.required('shippingRate.maxValue'),
|
|
24
|
+
),
|
|
25
|
+
].map((c) => c.bind(this));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class OrganizationValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [this.required('name')].map((c) => c.bind(this));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class PeopleAudienceValidator extends BaseValidator {
|
|
15
|
+
getConditions(data) {
|
|
16
|
+
const conditions = [
|
|
17
|
+
this.recommended('suggestedGender'),
|
|
18
|
+
|
|
19
|
+
this.or(
|
|
20
|
+
this.recommended('suggestedMinAge', 'number'),
|
|
21
|
+
this.recommended('suggestedAge.minValue', 'number'),
|
|
22
|
+
),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const minAge = data.suggestedMinAge || data.suggestedAge?.minValue;
|
|
26
|
+
if (minAge && minAge < 13) {
|
|
27
|
+
conditions.push(
|
|
28
|
+
this.or(
|
|
29
|
+
this.recommended('suggestedMaxAge', 'number'),
|
|
30
|
+
this.recommended('suggestedAge.maxValue', 'number'),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return conditions.map((c) => c.bind(this));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class PersonValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [this.required('name')].map((c) => c.bind(this));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class PriceSpecificationValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('price', 'number'),
|
|
18
|
+
this.recommended('priceCurrency', 'currency'),
|
|
19
|
+
].map((c) => c.bind(this));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class ProductValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('name'),
|
|
18
|
+
this.ratingReviewOrOffers,
|
|
19
|
+
this.notesCount,
|
|
20
|
+
].map((c) => c.bind(this));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
notesCount(data) {
|
|
24
|
+
if (!data.review) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const issues = [];
|
|
29
|
+
let notes = 0;
|
|
30
|
+
|
|
31
|
+
const reviews = Array.isArray(data.review) ? data.review : [data.review];
|
|
32
|
+
for (const review of reviews) {
|
|
33
|
+
// positiveNotes and negativeNotes are optional, but if they are present, they must be correct
|
|
34
|
+
if (
|
|
35
|
+
(review.positiveNotes && review.positiveNotes.itemListElement) ||
|
|
36
|
+
(review.negativeNotes && review.negativeNotes.itemListElement)
|
|
37
|
+
) {
|
|
38
|
+
notes += review.positiveNotes?.itemListElement?.length || 0;
|
|
39
|
+
notes += review.negativeNotes?.itemListElement?.length || 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Need to have at least 2 or zero notes
|
|
44
|
+
if (notes === 1) {
|
|
45
|
+
issues.push({
|
|
46
|
+
issueMessage:
|
|
47
|
+
'At least 2 notes, either positive or negative, are required',
|
|
48
|
+
severity: 'WARNING',
|
|
49
|
+
path: this.path,
|
|
50
|
+
fieldName: 'review',
|
|
51
|
+
fieldNames: ['review.positiveNotes', 'review.negativeNotes'],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return issues;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ratingReviewOrOffers(data) {
|
|
59
|
+
const issues = [];
|
|
60
|
+
|
|
61
|
+
// One of the three is required
|
|
62
|
+
if (!data.aggregateRating && !data.offers && !data.review) {
|
|
63
|
+
issues.push({
|
|
64
|
+
issueMessage:
|
|
65
|
+
'One of the following attributes is required: "aggregateRating", "offers" or "review"',
|
|
66
|
+
severity: 'ERROR',
|
|
67
|
+
path: this.path,
|
|
68
|
+
fieldName: 'aggregateRating',
|
|
69
|
+
fieldNames: ['aggregateRating', 'offers', 'review'],
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// If only offers is present, then aggregateRating and review are recommended
|
|
74
|
+
if (data.offers && (!data.aggregateRating || !data.review)) {
|
|
75
|
+
const aggregateRating = this.recommended(
|
|
76
|
+
'aggregateRating',
|
|
77
|
+
'object',
|
|
78
|
+
)(data);
|
|
79
|
+
if (aggregateRating) {
|
|
80
|
+
issues.push(aggregateRating);
|
|
81
|
+
}
|
|
82
|
+
const review = this.recommended('review', 'object')(data);
|
|
83
|
+
if (review) {
|
|
84
|
+
issues.push(review);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return issues;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class ProductMerchantValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('image'),
|
|
18
|
+
this.required('offers'),
|
|
19
|
+
|
|
20
|
+
this.recommended('audience'),
|
|
21
|
+
this.recommended('brand'),
|
|
22
|
+
this.recommended('color', 'string'),
|
|
23
|
+
this.recommended('description', 'string'),
|
|
24
|
+
this.recommended('hasCertification'),
|
|
25
|
+
this.recommended('inProductGroupWithID', 'string'),
|
|
26
|
+
this.recommended('isVariantOf'),
|
|
27
|
+
this.recommended('material', 'string'),
|
|
28
|
+
this.recommended('mpn', 'string'),
|
|
29
|
+
this.recommended('pattern', 'string'),
|
|
30
|
+
this.recommended('size'),
|
|
31
|
+
this.recommended('sku', 'string'),
|
|
32
|
+
this.recommended('subjectOf'),
|
|
33
|
+
this.validateGtin,
|
|
34
|
+
].map((c) => c.bind(this));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
validateGtin(data) {
|
|
38
|
+
let gtinFields = ['gtin', 'gtin8', 'gtin12', 'gtin13', 'gtin14', 'isbn'];
|
|
39
|
+
|
|
40
|
+
// Check if gtin is present on product
|
|
41
|
+
const productPass =
|
|
42
|
+
this.or(...gtinFields.map((field) => this.recommended(field, 'string')))(
|
|
43
|
+
data,
|
|
44
|
+
) === null;
|
|
45
|
+
|
|
46
|
+
// Check if gtin is present on offers object
|
|
47
|
+
let offerPass = false;
|
|
48
|
+
if (
|
|
49
|
+
data.offers &&
|
|
50
|
+
typeof data.offers === 'object' &&
|
|
51
|
+
!Array.isArray(data.offers)
|
|
52
|
+
) {
|
|
53
|
+
offerPass =
|
|
54
|
+
this.or(
|
|
55
|
+
...gtinFields.map((field) =>
|
|
56
|
+
this.recommended(`offers.${field}`, 'string'),
|
|
57
|
+
),
|
|
58
|
+
)(data.offers) === null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if gtin is present on offers array
|
|
62
|
+
let allOffersPass = false;
|
|
63
|
+
if (data.offers && Array.isArray(data.offers)) {
|
|
64
|
+
allOffersPass = true;
|
|
65
|
+
data.offers.forEach((offer, index) => {
|
|
66
|
+
const offerPass =
|
|
67
|
+
this.or(
|
|
68
|
+
...gtinFields.map((field) => this.recommended(field, 'string')),
|
|
69
|
+
)(offer, index, data) === null;
|
|
70
|
+
if (!offerPass) {
|
|
71
|
+
allOffersPass = false;
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (productPass || offerPass || allOffersPass) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
issueMessage: `Missing one of field ${gtinFields.map((a) => `"${a}"`).join(', ')} on either product or all offers`,
|
|
82
|
+
severity: 'WARNING',
|
|
83
|
+
path: this.path,
|
|
84
|
+
fieldName: 'gtin',
|
|
85
|
+
fieldNames: gtinFields,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class QuantitativeValueValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
if (this.inType('UnitPriceSpecification')) {
|
|
17
|
+
// If used in the context of unit pricing
|
|
18
|
+
return [
|
|
19
|
+
this.required('unitCode'),
|
|
20
|
+
this.required('value'),
|
|
21
|
+
|
|
22
|
+
this.recommended('valueReference'),
|
|
23
|
+
].map((c) => c.bind(this));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (this.inType('ShippingDeliveryTime')) {
|
|
27
|
+
return [
|
|
28
|
+
this.required('maxValue'),
|
|
29
|
+
this.required('minValue'),
|
|
30
|
+
this.required('unitCode'),
|
|
31
|
+
].map((c) => c.bind(this));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class QuestionValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
return [
|
|
17
|
+
this.required('name'),
|
|
18
|
+
this.required('acceptedAnswer', 'object'),
|
|
19
|
+
].map((c) => c.bind(this));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class RatingValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
const conditions = [
|
|
17
|
+
this.required('ratingValue'),
|
|
18
|
+
this.validateRange,
|
|
19
|
+
|
|
20
|
+
// Those fields are listed as recommended in documentation
|
|
21
|
+
// BUT: Google validator does not show warnings and assumes default values 0 and 5.
|
|
22
|
+
this.recommended('bestRating'),
|
|
23
|
+
this.recommended('worstRating'),
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return conditions.map((c) => c.bind(this));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
validateRange(data) {
|
|
30
|
+
// If number or if it can be parsed as number
|
|
31
|
+
// For % and / values, Google ignores the range
|
|
32
|
+
const from = data.worstRating || 0;
|
|
33
|
+
const to = data.bestRating || 5;
|
|
34
|
+
let value = data.ratingValue;
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
// Try to parse as number
|
|
37
|
+
value = parseFloat(value);
|
|
38
|
+
if (!isNaN(value)) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof value === 'number') {
|
|
44
|
+
if (value < from || value > to) {
|
|
45
|
+
return {
|
|
46
|
+
issueMessage: `Rating is outside the specified or default range`,
|
|
47
|
+
severity: 'ERROR',
|
|
48
|
+
path: this.path,
|
|
49
|
+
fieldName: 'ratingValue',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2025 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
import BaseValidator from './base.js';
|
|
13
|
+
|
|
14
|
+
export default class RecipeValidator extends BaseValidator {
|
|
15
|
+
getConditions() {
|
|
16
|
+
const conditions = [
|
|
17
|
+
this.validateImage,
|
|
18
|
+
this.required('name', 'string'),
|
|
19
|
+
|
|
20
|
+
this.recommended('aggregateRating'),
|
|
21
|
+
this.recommended('author'),
|
|
22
|
+
this.recommended('datePublished', 'date'),
|
|
23
|
+
this.recommended('description', 'string'),
|
|
24
|
+
this.recommended('keywords', 'string'),
|
|
25
|
+
this.recommended('recipeCategory', 'string'),
|
|
26
|
+
this.recommended('recipeCuisine', 'string'),
|
|
27
|
+
this.recommended('recipeIngredient'),
|
|
28
|
+
this.recommended('recipeInstructions'),
|
|
29
|
+
this.validateNutritionAndYield,
|
|
30
|
+
this.validateCookTime,
|
|
31
|
+
this.recommended('video'),
|
|
32
|
+
];
|
|
33
|
+
return conditions.map((c) => c.bind(this));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
validateNutritionAndYield(data) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
if (data.nutrition?.calories && !data.recipeYield) {
|
|
39
|
+
issues.push(this.required('recipeYield')(data));
|
|
40
|
+
} else {
|
|
41
|
+
issues.push(this.recommended('recipeYield')(data));
|
|
42
|
+
issues.push(this.recommended('nutrition.calories')(data));
|
|
43
|
+
}
|
|
44
|
+
return issues.filter((issue) => issue !== null);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
validateCookTime(data) {
|
|
48
|
+
const issues = [];
|
|
49
|
+
const hasCookTime = data.cookTime !== undefined && data.cookTime !== null;
|
|
50
|
+
const hasPrepTime = data.prepTime !== undefined && data.prepTime !== null;
|
|
51
|
+
const hasTotalTime =
|
|
52
|
+
data.totalTime !== undefined && data.totalTime !== null;
|
|
53
|
+
|
|
54
|
+
if (hasCookTime || hasPrepTime) {
|
|
55
|
+
issues.push(this.recommended('prepTime', 'duration')(data));
|
|
56
|
+
issues.push(this.recommended('cookTime', 'duration')(data));
|
|
57
|
+
if (hasTotalTime) {
|
|
58
|
+
issues.push(this.recommended('totalTime', 'duration')(data));
|
|
59
|
+
}
|
|
60
|
+
} else {
|
|
61
|
+
issues.push(this.recommended('totalTime', 'duration')(data));
|
|
62
|
+
}
|
|
63
|
+
return issues.filter((issue) => issue !== null);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
validateImage(data) {
|
|
67
|
+
const issues = [];
|
|
68
|
+
if (Array.isArray(data.image)) {
|
|
69
|
+
issues.push(this.required('image', 'url')(data));
|
|
70
|
+
} else {
|
|
71
|
+
issues.push(this.required('image')(data));
|
|
72
|
+
}
|
|
73
|
+
return issues.filter((issue) => issue !== null);
|
|
74
|
+
}
|
|
75
|
+
}
|