diff --git a/sds-go/go/regex_rule.go b/sds-go/go/regex_rule.go index cedf3aee..05ab71ad 100644 --- a/sds-go/go/regex_rule.go +++ b/sds-go/go/regex_rule.go @@ -7,6 +7,7 @@ package dd_sds import "C" import ( + "bytes" "encoding/json" "fmt" "unsafe" @@ -288,3 +289,40 @@ func (m MatchAction) MarshalJSON() ([]byte, error) { return json.Marshal(o) } + +// UnmarshalJSON decodes MatchAction JSON produced by MarshalJSON. The inner +// match_action key mirrors type for round-trip with Go-marshaled rules; Rust +// only uses the type tag on MatchAction. +func (m *MatchAction) UnmarshalJSON(data []byte) error { + var raw struct { + Type MatchActionType `json:"type"` + MatchAction MatchActionType `json:"match_action"` + RedactionValue string `json:"replacement"` + CharacterCount uint32 `json:"character_count"` + Direction PartialRedactionDirection `json:"direction"` + } + + decoder := json.NewDecoder(bytes.NewReader(data)) + decoder.DisallowUnknownFields() + if err := decoder.Decode(&raw); err != nil { + return err + } + + actionType := raw.Type + if actionType == "" { + actionType = raw.MatchAction + } + if actionType == "" { + actionType = MatchActionNone + } + + m.Type = actionType + switch actionType { + case MatchActionRedact: + m.RedactionValue = raw.RedactionValue + case MatchActionPartialRedact: + m.CharacterCount = raw.CharacterCount + m.Direction = raw.Direction + } + return nil +} diff --git a/sds-go/go/regex_rule_test.go b/sds-go/go/regex_rule_test.go new file mode 100644 index 00000000..e179b113 --- /dev/null +++ b/sds-go/go/regex_rule_test.go @@ -0,0 +1,99 @@ +package dd_sds + +import ( + "bytes" + "encoding/json" + "errors" + "reflect" + "testing" +) + +func TestMatchActionUnmarshalJSON_exportPlaceholder(t *testing.T) { + var got MatchAction + if err := json.Unmarshal([]byte(`{"type":"","match_action":""}`), &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + want := MatchAction{Type: MatchActionNone} + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %#v, want %#v", got, want) + } +} + +func TestMatchActionUnmarshalJSON_redact(t *testing.T) { + var got MatchAction + raw := `{"type":"Redact","match_action":"Redact","replacement":"[REDACTED]"}` + if err := json.Unmarshal([]byte(raw), &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + want := MatchAction{ + Type: MatchActionRedact, + RedactionValue: "[REDACTED]", + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %#v, want %#v", got, want) + } +} + +func TestMatchActionUnmarshalJSON_partialRedact(t *testing.T) { + var got MatchAction + raw := `{"type":"PartialRedact","match_action":"PartialRedact","character_count":4,"direction":"FirstCharacters"}` + if err := json.Unmarshal([]byte(raw), &got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + want := MatchAction{ + Type: MatchActionPartialRedact, + CharacterCount: 4, + Direction: FirstCharacters, + } + if !reflect.DeepEqual(got, want) { + t.Fatalf("got %#v, want %#v", got, want) + } +} + +func TestMatchActionUnmarshalJSON_unknownField(t *testing.T) { + err := json.Unmarshal([]byte(`{"type":"None","unexpected":true}`), &MatchAction{}) + if err == nil { + t.Fatal("expected error for unknown field") + } + if !errors.Is(err, errors.New("json: unknown field \"unexpected\"")) && err.Error() != `json: unknown field "unexpected"` { + t.Fatalf("error = %q", err) + } +} + +func TestMatchActionMarshalJSON_roundTrip(t *testing.T) { + in := MatchAction{ + Type: MatchActionRedact, + RedactionValue: "[REDACTED]", + } + data, err := json.Marshal(in) + if err != nil { + t.Fatalf("marshal: %v", err) + } + + var got MatchAction + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() + if err := dec.Decode(&got); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if !reflect.DeepEqual(got, in) { + t.Fatalf("round trip mismatch: got %#v, want %#v", got, in) + } +} + +func TestRegexRuleConfigUnmarshalJSON_withMatchAction(t *testing.T) { + raw := `{ + "id": "r", + "pattern": "secret", + "match_action": {"type":"","match_action":""} + }` + var cfg RegexRuleConfig + dec := json.NewDecoder(bytes.NewReader([]byte(raw))) + dec.DisallowUnknownFields() + if err := dec.Decode(&cfg); err != nil { + t.Fatalf("decode: %v", err) + } + if cfg.MatchAction.Type != MatchActionNone { + t.Fatalf("MatchAction.Type = %q, want %q", cfg.MatchAction.Type, MatchActionNone) + } +}