Skip to content
Draft
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
1 change: 1 addition & 0 deletions sds-go/go/dd_sds.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ void append_rule_to_list(long rule_ptr, long list_ptr);
void free_rule_list(long list_ptr);

const char* validate_regex(const char* regex, const char** error_out);
const char* explain_regex(const char* regex, const char** error_out);
95 changes: 95 additions & 0 deletions sds-go/go/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package dd_sds

/*
#include <stdlib.h>
#include <dd_sds.h>
*/
import "C"
import (
"encoding/json"
"fmt"
"unsafe"
)

// ValidateRegex validates a regex pattern and returns any error message.
// Returns (true, nil) if the regex is valid, or (false, error) if invalid.
func ValidateRegex(regex string) (bool, error) {
cRegex := C.CString(regex)
defer C.free(unsafe.Pointer(cRegex))

result := C.validate_regex(cRegex, nil)
if result == nil {
return true, nil
}

errorMsg := C.GoString(result)
C.free_string(result)
return false, fmt.Errorf("invalid regex: %s", errorMsg)
}

// AstNode represents a node in the regex abstract syntax tree.
// Each node provides detailed information about a specific part of the regex pattern.
type AstNode struct {
// NodeType is the type of syntax element (e.g., "Literal", "Alternation", "Capturing Group")
NodeType string `json:"node_type"`

// Description is a human-readable explanation of what this node does
Description string `json:"description"`

// Start is the character position where this node begins in the original pattern (for highlighting)
Start int `json:"start"`

// End is the character position where this node ends in the original pattern (for highlighting)
End int `json:"end"`

// Children contains nested AST nodes for complex patterns
Children []AstNode `json:"children,omitempty"`

// Properties contains additional metadata about the node
Properties map[string]interface{} `json:"properties,omitempty"`
}

// RegexExplanation contains the result of explaining a regex pattern.
// If the regex is invalid, IsValid will be false and Error will contain the error message.
type RegexExplanation struct {
// IsValid indicates whether the regex pattern was successfully parsed
IsValid bool `json:"is_valid"`

// Error contains the error message if the regex is invalid
Error *string `json:"error,omitempty"`

// Tree is the root node of the Abstract Syntax Tree if the regex is valid
Tree *AstNode `json:"tree,omitempty"`
}

// ExplainRegex parses a regex pattern and returns its Abstract Syntax Tree (AST)
// along with human-readable descriptions of each node.
func ExplainRegex(regex string) (RegexExplanation, error) {
cRegex := C.CString(regex)
defer C.free(unsafe.Pointer(cRegex))

result := C.explain_regex(cRegex, nil)
if result == nil {
return RegexExplanation{
IsValid: false,
Error: stringPtr("Failed to explain regex"),
}, nil
}

jsonStr := C.GoString(result)
C.free_string(result)

var explanation RegexExplanation
if err := json.Unmarshal([]byte(jsonStr), &explanation); err != nil {
return RegexExplanation{
IsValid: false,
Error: stringPtr("Failed to parse explanation JSON"),
}, err
}

return explanation, nil
}

func stringPtr(s string) *string {
return &s
}
134 changes: 134 additions & 0 deletions sds-go/go/regex_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package dd_sds

import (
"testing"
)

func TestValidateRegex(t *testing.T) {
_, err := ValidateRegex("hello")
if err != nil {
t.Fatal(err)
}

_, err = ValidateRegex("[")
if err == nil {
t.Fatal("Expected error for invalid regex")
}
}

func TestExplainRegex(t *testing.T) {
tests := []struct {
name string
pattern string
valid bool
}{
{
name: "simple literal",
pattern: "hello",
valid: true,
},
{
name: "digit class",
pattern: "\\d+",
valid: true,
},
{
name: "alternation",
pattern: "a|b|c",
valid: true,
},
{
name: "capturing group",
pattern: "(abc)",
valid: true,
},
{
name: "complex pattern",
pattern: "(\\d{3})-\\d{3}-\\d{4}",
valid: true,
},
{
name: "invalid regex",
pattern: "[",
valid: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
explanation, err := ExplainRegex(tt.pattern)
if err != nil {
t.Fatalf("ExplainRegex() error = %v", err)
}

if explanation.IsValid != tt.valid {
t.Errorf("ExplainRegex() IsValid = %v, want %v", explanation.IsValid, tt.valid)
}

if tt.valid {
if explanation.Tree == nil {
t.Error("ExplainRegex() Tree should not be nil for valid regex")
}
if explanation.Error != nil {
t.Errorf("ExplainRegex() Error should be nil for valid regex, got %v", *explanation.Error)
}

if explanation.Tree.NodeType == "" {
t.Error("ExplainRegex() Tree.NodeType should not be empty")
}
if explanation.Tree.Description == "" {
t.Error("ExplainRegex() Tree.Description should not be empty")
}
} else {
if explanation.Error == nil {
t.Error("ExplainRegex() Error should not be nil for invalid regex")
}
}
})
}
}

func TestExplainRegexWithPositions(t *testing.T) {
explanation, err := ExplainRegex("abc")
if err != nil {
t.Fatalf("ExplainRegex() error = %v", err)
}

if !explanation.IsValid {
t.Fatal("ExplainRegex() should be valid")
}

if explanation.Tree == nil {
t.Fatal("ExplainRegex() Tree should not be nil")
}

if explanation.Tree.Start < 0 {
t.Errorf("ExplainRegex() Tree.Start should be >= 0, got %d", explanation.Tree.Start)
}
if explanation.Tree.End <= explanation.Tree.Start {
t.Errorf("ExplainRegex() Tree.End (%d) should be > Start (%d)", explanation.Tree.End, explanation.Tree.Start)
}
}

func TestExplainRegexWithChildren(t *testing.T) {
explanation, err := ExplainRegex("a|b|c")
if err != nil {
t.Fatalf("ExplainRegex() error = %v", err)
}

if !explanation.IsValid {
t.Fatal("ExplainRegex() should be valid")
}

if explanation.Tree == nil {
t.Fatal("ExplainRegex() Tree should not be nil")
}

if explanation.Tree.NodeType != "Alternation" {
t.Errorf("ExplainRegex() Tree.NodeType should be 'Alternation', got %s", explanation.Tree.NodeType)
}

if len(explanation.Tree.Children) != 3 {
t.Errorf("ExplainRegex() Tree.Children should have 3 elements, got %d", len(explanation.Tree.Children))
}
}
26 changes: 0 additions & 26 deletions sds-go/go/validation.go

This file was deleted.

19 changes: 0 additions & 19 deletions sds-go/go/validation_test.go

This file was deleted.

2 changes: 1 addition & 1 deletion sds-go/rust/src/native/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use std::sync::{Arc, Mutex};

pub mod create_scanner;
pub mod delete_scanner;
pub mod regex;
pub mod rule;
pub mod scan;
pub mod validation;

pub const ERR_PANIC: i64 = -5;

Expand Down
Loading