Skip to content

Commit 6959da9

Browse files
openapi3: gate OAS 3.1 schema fields from OAS 3.0 validation
Per Pierre's PR #15 review: Schema.validate accepted OAS 3.1 / JSON Schema 2020-12 fields (prefixItems, contains, if/then/else, $defs, $schema, patternProperties, unevaluatedItems, etc.) on OAS 3.0 documents. Add a gate at the top of Schema.validate that rejects each 3.1-only field in 3.0 mode via errFieldFor31Plus. The gate honours AllowExtraSiblingFields so 3.0 documents that resolve external JSON Schema refs (draft-04, draft-07) can opt in to letting $id/$schema/etc. through. This matches the existing opt-in mechanism already used by TestExtraSiblingsInRemoteRef. This whole block becomes a no-op once OAS 3.0 gets its own standalone schema validator. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
1 parent f7f98e7 commit 6959da9

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

openapi3/schema.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,6 +1425,146 @@ func (schema *Schema) validate(ctx context.Context, stack []*Schema) ([]*Schema,
14251425
return stack, errors.New("a property MUST NOT be marked as both readOnly and writeOnly being true")
14261426
}
14271427

1428+
// Reject fields that only exist in OAS 3.1 / JSON Schema 2020-12 when the
1429+
// document is OAS 3.0. Fields explicitly allowed via AllowExtraSiblingFields
1430+
// are skipped (opt-in escape hatch for 3.0 docs that reference external
1431+
// JSON Schema documents). Once 3.0 moves to its own schema validator this
1432+
// block becomes a no-op.
1433+
if !validationOpts.isOpenAPI31OrLater {
1434+
allowed := validationOpts.extraSiblingFieldsAllowed
1435+
reject := func(field string) error {
1436+
if _, ok := allowed[field]; ok {
1437+
return nil
1438+
}
1439+
return errFieldFor31Plus(field)
1440+
}
1441+
if schema.Const != nil {
1442+
if err := reject("const"); err != nil {
1443+
return stack, err
1444+
}
1445+
}
1446+
if len(schema.Examples) != 0 {
1447+
if err := reject("examples"); err != nil {
1448+
return stack, err
1449+
}
1450+
}
1451+
if len(schema.PrefixItems) != 0 {
1452+
if err := reject("prefixItems"); err != nil {
1453+
return stack, err
1454+
}
1455+
}
1456+
if schema.Contains != nil {
1457+
if err := reject("contains"); err != nil {
1458+
return stack, err
1459+
}
1460+
}
1461+
if schema.MinContains != nil {
1462+
if err := reject("minContains"); err != nil {
1463+
return stack, err
1464+
}
1465+
}
1466+
if schema.MaxContains != nil {
1467+
if err := reject("maxContains"); err != nil {
1468+
return stack, err
1469+
}
1470+
}
1471+
if len(schema.PatternProperties) != 0 {
1472+
if err := reject("patternProperties"); err != nil {
1473+
return stack, err
1474+
}
1475+
}
1476+
if len(schema.DependentSchemas) != 0 {
1477+
if err := reject("dependentSchemas"); err != nil {
1478+
return stack, err
1479+
}
1480+
}
1481+
if schema.PropertyNames != nil {
1482+
if err := reject("propertyNames"); err != nil {
1483+
return stack, err
1484+
}
1485+
}
1486+
if schema.UnevaluatedItems.Has != nil || schema.UnevaluatedItems.Schema != nil {
1487+
if err := reject("unevaluatedItems"); err != nil {
1488+
return stack, err
1489+
}
1490+
}
1491+
if schema.UnevaluatedProperties.Has != nil || schema.UnevaluatedProperties.Schema != nil {
1492+
if err := reject("unevaluatedProperties"); err != nil {
1493+
return stack, err
1494+
}
1495+
}
1496+
if schema.If != nil {
1497+
if err := reject("if"); err != nil {
1498+
return stack, err
1499+
}
1500+
}
1501+
if schema.Then != nil {
1502+
if err := reject("then"); err != nil {
1503+
return stack, err
1504+
}
1505+
}
1506+
if schema.Else != nil {
1507+
if err := reject("else"); err != nil {
1508+
return stack, err
1509+
}
1510+
}
1511+
if len(schema.DependentRequired) != 0 {
1512+
if err := reject("dependentRequired"); err != nil {
1513+
return stack, err
1514+
}
1515+
}
1516+
if len(schema.Defs) != 0 {
1517+
if err := reject("$defs"); err != nil {
1518+
return stack, err
1519+
}
1520+
}
1521+
if schema.SchemaDialect != "" {
1522+
if err := reject("$schema"); err != nil {
1523+
return stack, err
1524+
}
1525+
}
1526+
if schema.Comment != "" {
1527+
if err := reject("$comment"); err != nil {
1528+
return stack, err
1529+
}
1530+
}
1531+
if schema.SchemaID != "" {
1532+
if err := reject("$id"); err != nil {
1533+
return stack, err
1534+
}
1535+
}
1536+
if schema.Anchor != "" {
1537+
if err := reject("$anchor"); err != nil {
1538+
return stack, err
1539+
}
1540+
}
1541+
if schema.DynamicRef != "" {
1542+
if err := reject("$dynamicRef"); err != nil {
1543+
return stack, err
1544+
}
1545+
}
1546+
if schema.DynamicAnchor != "" {
1547+
if err := reject("$dynamicAnchor"); err != nil {
1548+
return stack, err
1549+
}
1550+
}
1551+
if schema.ContentMediaType != "" {
1552+
if err := reject("contentMediaType"); err != nil {
1553+
return stack, err
1554+
}
1555+
}
1556+
if schema.ContentEncoding != "" {
1557+
if err := reject("contentEncoding"); err != nil {
1558+
return stack, err
1559+
}
1560+
}
1561+
if schema.ContentSchema != nil {
1562+
if err := reject("contentSchema"); err != nil {
1563+
return stack, err
1564+
}
1565+
}
1566+
}
1567+
14281568
for _, item := range schema.OneOf {
14291569
v := item.Value
14301570
if v == nil {

0 commit comments

Comments
 (0)