Skip to content

Commit 99fd9f0

Browse files
Schema validation should check the object's oneOf if it has instead of the object type (#663)
* Schema validation should check the object's oneOf if it has instead of the object type * reused existing method
1 parent b889fbe commit 99fd9f0

File tree

2 files changed

+81
-13
lines changed

2 files changed

+81
-13
lines changed

src/languageservice/parser/jsonParser07.ts

+33-5
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,8 @@ function validate(
602602
originalSchema: JSONSchema,
603603
validationResult: ValidationResult,
604604
matchingSchemas: ISchemaCollector,
605-
options: Options
605+
options: Options,
606+
isAdditionalPropertiesCheck?: boolean
606607
// eslint-disable-next-line @typescript-eslint/no-explicit-any
607608
): any {
608609
const { isKubernetes } = options;
@@ -646,6 +647,18 @@ function validate(
646647
return node.type === type || (type === 'integer' && node.type === 'number' && node.isInteger);
647648
}
648649

650+
function matchesSchemaType(schema: JSONSchema): boolean {
651+
/* when schema type is object and it contains oneOf then needs to validate that particular types
652+
reference issue 692
653+
*/
654+
const type = Array.isArray(schema.type) ? undefined : schema.type;
655+
if (type === 'object' && typeof schema.additionalProperties === 'object' && schema.additionalProperties.oneOf) {
656+
return true;
657+
} else if (type) {
658+
return matchesType(type);
659+
}
660+
}
661+
649662
if (Array.isArray(schema.type)) {
650663
if (!schema.type.some(matchesType)) {
651664
validationResult.problems.push({
@@ -659,7 +672,7 @@ function validate(
659672
});
660673
}
661674
} else if (schema.type) {
662-
if (!matchesType(schema.type)) {
675+
if (!matchesSchemaType(schema)) {
663676
//get more specific name than just object
664677
const schemaType = schema.type === 'object' ? getSchemaTypeName(schema) : schema.type;
665678
validationResult.problems.push({
@@ -678,6 +691,13 @@ function validate(
678691
validate(node, asSchema(subSchemaRef), schema, validationResult, matchingSchemas, options);
679692
}
680693
}
694+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object' && schema.additionalProperties.oneOf) {
695+
const propertyValidationResult = new ValidationResult(isKubernetes);
696+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
697+
validate(node, <any>schema.additionalProperties, schema, propertyValidationResult, matchingSchemas, options, true);
698+
validationResult.mergePropertyMatch(propertyValidationResult);
699+
validationResult.mergeEnumValues(propertyValidationResult);
700+
}
681701
const notSchema = asSchema(schema.not);
682702
if (notSchema) {
683703
const subValidationResult = new ValidationResult(isKubernetes);
@@ -727,7 +747,14 @@ function validate(
727747
} else if (isKubernetes) {
728748
bestMatch = alternativeComparison(subValidationResult, bestMatch, subSchema, subMatchingSchemas);
729749
} else {
730-
bestMatch = genericComparison(maxOneMatch, subValidationResult, bestMatch, subSchema, subMatchingSchemas);
750+
bestMatch = genericComparison(
751+
maxOneMatch,
752+
subValidationResult,
753+
bestMatch,
754+
subSchema,
755+
subMatchingSchemas,
756+
isAdditionalPropertiesCheck
757+
);
731758
}
732759
}
733760

@@ -1429,7 +1456,8 @@ function validate(
14291456
matchingSchemas: ISchemaCollector;
14301457
},
14311458
subSchema,
1432-
subMatchingSchemas
1459+
subMatchingSchemas,
1460+
isAdditionalPropertiesCheck?: boolean
14331461
): {
14341462
schema: JSONSchema;
14351463
validationResult: ValidationResult;
@@ -1442,7 +1470,7 @@ function validate(
14421470
bestMatch.validationResult.propertiesValueMatches += subValidationResult.propertiesValueMatches;
14431471
} else {
14441472
const compareResult = subValidationResult.compareGeneric(bestMatch.validationResult);
1445-
if (compareResult > 0) {
1473+
if (compareResult > 0 || isAdditionalPropertiesCheck) {
14461474
// our node is the best matching so far
14471475
bestMatch = {
14481476
schema: subSchema,

test/schemaValidation.test.ts

+48-8
Original file line numberDiff line numberDiff line change
@@ -1557,18 +1557,58 @@ obj:
15571557
expect(result.length).to.eq(0);
15581558
});
15591559
});
1560-
});
15611560

1562-
describe('Bug fixes', () => {
1563-
it('should handle not valid schema object', async () => {
1564-
const schema = 'Foo';
1565-
languageService.addSchema(SCHEMA_ID, schema as JSONSchema);
1566-
const content = `foo: bar`;
1561+
it('schema should validate additionalProp oneOf', async () => {
1562+
const schema = {
1563+
type: 'object',
1564+
definitions: {
1565+
expressionSyntax: {
1566+
type: 'string',
1567+
pattern: '^\\$\\{\\{\\s*fromJSON\\(.*\\)\\s*\\}\\}$',
1568+
},
1569+
},
1570+
properties: {
1571+
env: {
1572+
type: 'object',
1573+
additionalProperties: {
1574+
oneOf: [
1575+
{
1576+
type: 'object',
1577+
additionalProperties: {
1578+
oneOf: [
1579+
{
1580+
type: 'string',
1581+
},
1582+
{
1583+
type: 'number',
1584+
},
1585+
{
1586+
type: 'boolean',
1587+
},
1588+
],
1589+
},
1590+
minProperties: 1,
1591+
},
1592+
{
1593+
$ref: '#/definitions/expressionSyntax',
1594+
},
1595+
],
1596+
},
1597+
},
1598+
},
1599+
};
1600+
languageService.addSchema(SCHEMA_ID, schema);
1601+
const content = `env: matrix.env`;
15671602
const result = await parseSetup(content);
1568-
expect(result).to.be.empty;
1569-
expect(telemetry.messages).to.be.empty;
1603+
expect(result.length).to.eq(1);
1604+
assert.deepStrictEqual(
1605+
result[0].message,
1606+
'String does not match the pattern of "^\\$\\{\\{\\s*fromJSON\\(.*\\)\\s*\\}\\}$".'
1607+
);
15701608
});
1609+
});
15711610

1611+
describe('Bug fixes', () => {
15721612
it('should handle bad schema refs', async () => {
15731613
const schema = {
15741614
type: 'object',

0 commit comments

Comments
 (0)