Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for extensible enums #1

Merged
merged 4 commits into from
Jun 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
version: 2
updates:
- package-ecosystem: "gomod"
- package-ecosystem: gomod
directory: "/"
schedule:
interval: "weekly"
interval: weekly
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
4 changes: 2 additions & 2 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22.0'
go-version: '1.22.3'
cache: false # Let golangcilint handle caching
- name: golangci-lint
uses: golangci/golangci-lint-action@v4
with:
version: v1.56
version: v1.59
args: --timeout=4m
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22.0'
go-version: '1.22.3'
- name: Run go tests
run: |
go test ./...
2 changes: 1 addition & 1 deletion block_constraint.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (bc BlockConstraint) Matches(b *newsdoc.Block) (Match, []string) {
// Optional attributes are empty strings.
check.AllowEmpty = check.AllowEmpty || check.Optional

err := check.Validate(value, ok, nil)
_, err := check.Validate(value, ok, nil)
if err != nil {
return NoMatch, nil
}
Expand Down
41 changes: 1 addition & 40 deletions document_constraint.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package revisor

import (
"fmt"

"github.com/ttab/newsdoc"
)

Expand Down Expand Up @@ -55,7 +53,7 @@ func (dc DocumentConstraint) Matches(
return NoMatch
}

err := check.Validate(value, ok, vCtx)
_, err := check.Validate(value, ok, vCtx)
if err != nil {
return NoMatch
}
Expand All @@ -72,43 +70,6 @@ func (dc DocumentConstraint) Matches(
return Matches
}

func (dc DocumentConstraint) checkAttributes(
d *newsdoc.Document, res []ValidationResult, vCtx *ValidationContext,
) []ValidationResult {
for k, check := range dc.Attributes {
value, ok := documentAttribute(d, k)
if !ok {
res = append(res, ValidationResult{
Error: fmt.Sprintf("unknown document attribute %q", k),
})

continue
}

// Optional attributes are empty strings.
check.AllowEmpty = check.AllowEmpty || check.Optional

err := check.Validate(value, ok, vCtx)
if err != nil {
res = append(res, ValidationResult{
Error: fmt.Sprintf("invalid %q: %v", k, err),
})

continue
}

vCtx.coll.CollectValue(ValueAnnotation{
Ref: []EntityRef{{
RefType: RefTypeAttribute,
Name: k,
}},
Value: value,
})
}

return res
}

type documentAttributeKey string

const (
Expand Down
148 changes: 148 additions & 0 deletions enum.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package revisor

import (
"errors"
"fmt"
"strings"
)

type Enum struct {
Declare string `json:"declare,omitempty"`
Match string `json:"match,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Values map[string]EnumConstraint `json:"values"`
}

type EnumConstraint struct {
Forbidden bool `json:"forbidden,omitempty"`
Deprecated *Deprecation `json:"deprecated,omitempty"`
}

type mergedEnum struct {
Values map[string][]EnumConstraint
Allowed []string
}

func mergedEnumAllowedValues(m *mergedEnum) []string {
var vals []string

valueFn := func(v string, cs []EnumConstraint) (string, bool) {
var deprecated bool

for _, c := range cs {
if c.Forbidden {
return "", false
}

deprecated = deprecated || c.Deprecated != nil
}

if deprecated {
return fmt.Sprintf("%q (deprecated)", v), true
}

return fmt.Sprintf("%q", v), true
}

for v, cs := range m.Values {
s, ok := valueFn(v, cs)
if ok {
vals = append(vals, s)
}
}

return vals
}

type enumSet struct {
extensions []Enum
enums map[string]*mergedEnum
}

func newEnumSet() *enumSet {
return &enumSet{
enums: make(map[string]*mergedEnum),
}
}

func (s *enumSet) Register(e Enum) error {
if e.Declare != "" && e.Match != "" {
return fmt.Errorf(
"the enum %q cannot both declare and match an enum",
e.Declare)
}

if e.Declare == "" && e.Match == "" {
return errors.New("an enum must declare or match an existing enum")
}

if e.Match != "" {
s.extensions = append(s.extensions, e)

return nil
}

_, declared := s.enums[e.Declare]

if declared {
return errors.New("the enum %q has already been declared")
}

m := mergedEnum{
Values: make(map[string][]EnumConstraint, len(e.Values)),
}

for k, c := range e.Values {
m.Values[k] = []EnumConstraint{c}
}

s.enums[e.Declare] = &m

return nil
}

func (s *enumSet) Resolve() error {
for _, e := range s.extensions {
m, declared := s.enums[e.Match]
if !declared {
return fmt.Errorf("the enum %q hasn't been declared and cannot be matched", e.Match)
}

for k, c := range e.Values {
m.Values[k] = append(m.Values[k], c)
}
}

for _, m := range s.enums {
m.Allowed = mergedEnumAllowedValues(m)
}

return nil
}

func (s *enumSet) ValidValue(enum string, value string) (*Deprecation, error) {
m, declared := s.enums[enum]
if !declared {
return nil, fmt.Errorf("unknown enum %q", enum)
}

constraints, hasValue := m.Values[value]
if !hasValue {
return nil, fmt.Errorf("must be one of: %s", strings.Join(m.Allowed, ", "))
}

var deprecation *Deprecation

for _, c := range constraints {
if c.Deprecated != nil && deprecation == nil {
deprecation = c.Deprecated
}

if c.Forbidden {
return nil, fmt.Errorf("%q is no longer allowed", value)
}
}

return deprecation, nil
}
12 changes: 6 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/ttab/revisor

go 1.22.0
go 1.22.3

require (
github.com/IvanZagoskin/wkt v0.0.1
Expand All @@ -9,18 +9,18 @@ require (
github.com/google/uuid v1.6.0
github.com/invopop/jsonschema v0.12.0
github.com/ttab/newsdoc v0.5.0
github.com/ttab/revisorschemas v0.0.2
github.com/urfave/cli/v2 v2.27.1
golang.org/x/net v0.21.0
github.com/ttab/revisorschemas v0.2.0
github.com/urfave/cli/v2 v2.27.2
golang.org/x/net v0.25.0
)

require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPn
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
Expand All @@ -28,16 +28,16 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/ttab/newsdoc v0.5.0 h1:b4rvvQMiiaU0WUqYKPVP5p4kUlwN6X6HUcEn+isbDAQ=
github.com/ttab/newsdoc v0.5.0/go.mod h1:a+D/6F/zNhIvNB7+JJMPx0HNm+146y68TCfVYo6H8WY=
github.com/ttab/revisorschemas v0.0.2 h1:j3SCLWTWUjD3QOZwx5WymppsEH2lKR7OoTrvGP7GQiE=
github.com/ttab/revisorschemas v0.0.2/go.mod h1:FoasqJRN0RMd9K/OpQnijVVZRKENvFAZVkotYh5boC8=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/ttab/revisorschemas v0.2.0 h1:VDjwozJW+JB+5v0nxJ2MRC7SZ+p0emRNqiciWYC7BCc=
github.com/ttab/revisorschemas v0.2.0/go.mod h1:aar8L038YwOri7FsapvOHeeEG5alN4fWBZ8phZ2hQdw=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
3 changes: 2 additions & 1 deletion html.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ func (hp *HTMLPolicy) handleToken(z *html.Tokenizer, tagStack []string) ([]strin
)
}

err := constraint.Validate(string(v), true, nil)
// TODO: Handle deprecation of HTML attribute values.
_, err := constraint.Validate(string(v), true, nil)
if err != nil {
return nil, fmt.Errorf(
"<%s> attribute %q: %w",
Expand Down
Loading