Skip to content

Commit

Permalink
Adding MissingValues to field and the required constraint. #42
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfireman committed Sep 25, 2017
1 parent 55dc4d2 commit b229779
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 1 deletion.
23 changes: 23 additions & 0 deletions schema/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,15 @@ const (
AnyDateFormat = "any"
)

// Constraints can be used by consumers to list constraints for validating
// field values.
type Constraints struct {
// Required indicates whether this field is allowed to be null.
// Schema.MissingValues define how the string representation can
// represent null values.
Required bool `json:"required,omitempty"`
}

// Field describes a single field in the table schema.
// More: https://specs.frictionlessdata.io/table-schema/#field-descriptors
type Field struct {
Expand Down Expand Up @@ -71,6 +80,14 @@ type Field struct {
// If false the contents of this field may contain leading and/or trailing non-numeric characters which
// are going to be stripped. Default value is true:
BareNumber bool `json:"bareNumber,omitempty"`

// MissingValues is a map which dictates which string values should be treated as null
// values.
MissingValues map[string]struct{} `json:"-"`

// Constraints can be used by consumers to list constraints for validating
// field values.
Constraints Constraints
}

// UnmarshalJSON sets *f to a copy of data. It will respect the default values
Expand All @@ -97,6 +114,12 @@ func (f *Field) UnmarshalJSON(data []byte) error {
// Decode decodes the passed-in string against field type. Returns an error
// if the value can not be cast or any field constraint can not be satisfied.
func (f *Field) Decode(value string) (interface{}, error) {
if f.Constraints.Required {
_, ok := f.MissingValues[value]
if ok {
return nil, fmt.Errorf("%s is required", f.Name)
}
}
switch f.Type {
case IntegerType:
return castInt(f.BareNumber, value)
Expand Down
10 changes: 9 additions & 1 deletion schema/field_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,17 @@ func TestField_Decode(t *testing.T) {
t.Run("InvalidFieldType", func(t *testing.T) {
f := Field{Type: "invalidType"}
if _, err := f.Decode("42"); err == nil {
t.Errorf("err want:err, got:nil")
t.Errorf("err want:err got:nil")
}
})
t.Run("Constraints", func(t *testing.T) {
t.Run("Required", func(t *testing.T) {
f := Field{Type: StringType, Constraints: Constraints{Required: true}, MissingValues: map[string]struct{}{"NA": struct{}{}}}
if _, err := f.Decode("NA"); err == nil {
t.Fatalf("err want:err got:nil")
}
})
})
}

func TestUnmarshalJSON_InvalidField(t *testing.T) {
Expand Down
15 changes: 15 additions & 0 deletions schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ func Read(r io.Reader) (*Schema, error) {
if err := dec.Decode(&s); err != nil {
return nil, err
}
if len(s.MissingValues) == 0 {
return &s, nil
}
// Transforming the list in a set.
valueSet := make(map[string]struct{}, len(s.MissingValues))
for _, v := range s.MissingValues {
valueSet[v] = struct{}{}
}
// Updating fields.
for i := range s.Fields {
s.Fields[i].MissingValues = make(map[string]struct{}, len(valueSet))
for k, v := range valueSet {
s.Fields[i].MissingValues[k] = v
}
}
return &s, nil
}

Expand Down
11 changes: 11 additions & 0 deletions schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,17 @@ func TestRead_Sucess(t *testing.T) {
}
})
}
t.Run("MissingValues", func(t *testing.T) {
reader := strings.NewReader(`{"fields":[{"name":"n","type":"integer"}],"missingValues":["na"]}`)
s, err := Read(reader)
if err != nil {
t.Fatalf("want:nil, got:%q", err)
}
f := s.Fields[0]
if _, ok := f.MissingValues["na"]; !ok {
t.Fatalf("want:ok got:!ok")
}
})
}

func TestRead_Error(t *testing.T) {
Expand Down

0 comments on commit b229779

Please sign in to comment.