diff --git a/document_constraint.go b/document_constraint.go index 4d5afaa..b7dd0b4 100644 --- a/document_constraint.go +++ b/document_constraint.go @@ -63,7 +63,7 @@ func (dc DocumentConstraint) Matches( } for _, k := range dc.Match.Keys { - value, ok := documentMatchAttribute(d, k, vCtx.variants) + value, ok := documentMatchAttribute(d, k) if !ok { return NoMatch } @@ -71,6 +71,19 @@ func (dc DocumentConstraint) Matches( check := dc.Match.Constraints[k] _, err := check.Validate(value, ok, vCtx) + if err != nil && documentAttributeKey(k) == docAttrType { + // Fall back to the resolved variant type so that + // match expressions targeting the base type still + // apply to variant documents. + resolved := resolveVariant(d.Type, vCtx.variants) + if resolved != value { + _, err = check.Validate(resolved, ok, vCtx) + if err == nil { + value = resolved + } + } + } + if err != nil { return NoMatch } @@ -98,9 +111,9 @@ const ( docAttrURL documentAttributeKey = "url" ) -func documentMatchAttribute(d *newsdoc.Document, name string, variants []Variant) (string, bool) { +func documentMatchAttribute(d *newsdoc.Document, name string) (string, bool) { if documentAttributeKey(name) == docAttrType { - return resolveVariant(d.Type, variants), true + return d.Type, true } return "", false diff --git a/validation_test.go b/validation_test.go index 305d80a..3198d32 100644 --- a/validation_test.go +++ b/validation_test.go @@ -496,6 +496,92 @@ func TestTemplateDocumentType(t *testing.T) { } }) + t.Run("VariantMatchExtension", func(t *testing.T) { + variantType := "core/article#prefab" + + extensionSet := revisor.ConstraintSet{ + Name: "test-prefab-extension", + Version: 1, + Documents: []revisor.DocumentConstraint{ + { + Match: revisor.MakeConstraintMap(map[string]revisor.StringConstraint{ + "type": {Const: &variantType}, + }), + Meta: []*revisor.BlockConstraint{ + { + Declares: &revisor.BlockSignature{ + Type: "core/prefab-setting", + }, + Name: "Prefab setting", + }, + }, + }, + }, + } + + extValidator, err := validator.WithConstraints(extensionSet) + if err != nil { + t.Fatalf("create extended validator: %v", err) + } + + extValidator = extValidator.WithVariants(revisor.Variant{Name: "prefab"}) + + // The prefab-setting meta block should be valid on the variant. + t.Run("VariantAcceptsExtendedBlock", func(t *testing.T) { + doc := newsdoc.Document{ + Type: "core/article#prefab", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + Meta: []newsdoc.Block{ + {Type: "core/prefab-setting"}, + }, + } + + results, err := extValidator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for _, r := range results { + if strings.Contains(r.Error, "core/prefab-setting") { + t.Errorf("prefab-setting should be allowed on variant, got: %s", r.Error) + } + } + }) + + // The prefab-setting meta block should not be valid on the + // base document type. + t.Run("BaseRejectsExtendedBlock", func(t *testing.T) { + doc := newsdoc.Document{ + Type: "core/article", + UUID: "00000000-0000-0000-0000-000000000001", + URI: "article://test/1", + Meta: []newsdoc.Block{ + {Type: "core/prefab-setting"}, + }, + } + + results, err := extValidator.ValidateDocument(ctx, &doc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + found := false + + for _, r := range results { + if strings.Contains(r.Error, "undeclared block") { + found = true + + break + } + } + + if !found { + t.Error("expected undeclared block error for prefab-setting on base document type") + } + }) + }) + t.Run("TypeRestrictedVariant", func(t *testing.T) { restrictedValidator := baseValidator.WithVariants( revisor.Variant{