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

feat(schema): add arbitrary width integers and field length limits #21244

Closed
wants to merge 4 commits into from
Closed
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
63 changes: 61 additions & 2 deletions schema/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ type Field struct {
// the same values for the same enum name. This possibly introduces some duplication of
// definitions but makes it easier to reason about correctness and validation in isolation.
EnumType EnumType

// Width specifies the width of the field for IntNKind and UintNKind fields.
// It is invalid to have a non-zero Width for other kinds.
// Width must be a multiple of 8 and values of 8, 16, 32, and 64 are invalid because there
// are more specific kinds for these widths.
Width uint16

// MaxLength specifies a maximum length for StringKind, BytesKind, AddressKind, and JSONKind fields.
// If it is 0, the field has no maximum length.
// It is invalid to have a non-zero MaxLength for other kinds.
// Negative values are invalid.
MaxLength int
}

// Validate validates the field.
Expand All @@ -41,6 +53,33 @@ func (c Field) Validate() error {
return fmt.Errorf("enum definition is only valid for field %q with type EnumKind", c.Name)
}

switch c.Kind {
case IntNKind, UIntNKind:
switch c.Width {
case 0, 8, 16, 32, 64:
return fmt.Errorf("invalid width %d for field %q, use a more specific type", c.Width, c.Name)
default:
if c.Width%8 != 0 {
return fmt.Errorf("invalid width %d for field %q, must be a multiple of 8", c.Width, c.Name)
}
}
default:
if c.Width != 0 {
return fmt.Errorf("width %d is only valid for IntNKind and UIntNKind fields", c.Width)
}
}

switch c.Kind {
case StringKind, BytesKind, AddressKind, JSONKind:
if c.MaxLength < 0 {
return fmt.Errorf("negative max length %d for field %q", c.MaxLength, c.Name)
}
default:
if c.MaxLength != 0 {
return fmt.Errorf("max length %d is only valid for StringKind, BytesKind, AddressKind, and JSONKind fields", c.MaxLength)
}
}

return nil
}

Expand All @@ -54,13 +93,33 @@ func (c Field) ValidateValue(value interface{}) error {
}
return nil
}
err := c.Kind.ValidateValueType(value)
err := c.Kind.ValidateValue(value)
if err != nil {
return fmt.Errorf("invalid value for field %q: %v", c.Name, err) //nolint:errorlint // false positive due to using go1.12
}

if c.Kind == EnumKind {
switch c.Kind {
case EnumKind:
return c.EnumType.ValidateValue(value.(string))
case IntNKind, UIntNKind:
bz := value.([]byte)
if len(bz) != int(c.Width/8) {
return fmt.Errorf("invalid byte length %d for field %q, expected %d", len(bz), c.Name, c.Width/8)
}
case StringKind:
if c.MaxLength > 0 {
str := value.(string)
if len(str) > c.MaxLength {
return fmt.Errorf("value for field %q exceeds max length %d", c.Name, c.MaxLength)
}
}
case BytesKind, AddressKind, JSONKind:
if c.MaxLength > 0 {
bz := value.([]byte)
if len(bz) > c.MaxLength {
return fmt.Errorf("value for field %q exceeds max length %d", c.Name, c.MaxLength)
}
}
}

return nil
Expand Down
27 changes: 26 additions & 1 deletion schema/kind.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,26 @@ const (
// Go Encoding: json.RawMessage
// JSON Encoding: any valid JSON value
JSONKind

// UIntNKind represents a signed integer type with a width in bits specified by the Width field in the
// field definition.
// N must be a multiple of 8, and it is invalid for N to equal 8, 16, 32, 64 as there are more specific
// types for these widths.
// Go Encoding: []byte where len([]byte) == Width / 8, little-endian encoded.
// JSON Encoding: base10 integer string matching the IntegerFormat regex, canonically with no leading zeros.
UIntNKind

// IntNKind represents an unsigned integer type with a width in bits specified by the Width field in the
// field definition. N must be a multiple of 8.
// N must be a multiple of 8, and it is invalid for N to equal 8, 16, 32, 64 as there are more specific
// types for these widths.
// Go Encoding: []byte where len([]byte) == Width / 8, two's complement little-endian encoded.
// JSON Encoding: base10 integer string matching the IntegerFormat regex, canonically with no leading zeros.
IntNKind
)

// MAX_VALID_KIND is the maximum valid kind value.
const MAX_VALID_KIND = JSONKind
const MAX_VALID_KIND = IntNKind

const (
// IntegerFormat is a regex that describes the format integer number strings must match. It specifies
Expand Down Expand Up @@ -210,6 +226,10 @@ func (t Kind) String() string {
return "enum"
case JSONKind:
return "json"
case UIntNKind:
return "uint"
case IntNKind:
return "int"
default:
return fmt.Sprintf("invalid(%d)", t)
}
Expand Down Expand Up @@ -323,6 +343,11 @@ func (t Kind) ValidateValueType(value interface{}) error {
if !ok {
return fmt.Errorf("expected json.RawMessage, got %T", value)
}
case UIntNKind:
_, ok := value.([]byte)
if !ok {
return fmt.Errorf("expected []byte, got %T", value)
}
default:
return fmt.Errorf("invalid type: %d", t)
}
Expand Down
Loading