Add meeting and meeting summary objects

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Antoine Bouchardy
2025-11-09 13:29:53 +01:00
committed by Bryan Frimin
parent 0a9033aaaf
commit 139f5984e2
63 changed files with 9395 additions and 1219 deletions

View File

@@ -63,7 +63,7 @@ func NoHTML() ValidatorFunc {
// PrintableText validates that a string contains only printable UTF-8 characters.
// It rejects:
// - Control characters (including null bytes, tabs, line breaks except space)
// - Control characters (0x00-0x1F and 0x7F-0x9F, including null bytes and tabs, but allows newlines and carriage returns)
// - Unicode direction override characters (RLO, LRO, PDF, etc.)
// - Zero-width characters (ZWSP, ZWNJ, ZWJ, etc.)
// - Other invisible or formatting characters
@@ -71,8 +71,8 @@ func NoHTML() ValidatorFunc {
// - Replacement characters
//
// This validator does NOT check for HTML tags - use NoHTML() for that.
// This is ideal for validating titles, full names, display names, and similar text fields
// where only printable characters should be allowed.
// This validator allows line breaks (newline and carriage return) for multi-line text fields.
// Use NoNewLine() or SafeTextNoNewLine() for single-line fields that should reject line breaks.
func PrintableText() ValidatorFunc {
return func(value any) *ValidationError {
actualValue, isNil := dereferenceValue(value)
@@ -96,7 +96,12 @@ func PrintableText() ValidatorFunc {
continue
}
// Reject control characters (0x00-0x1F and 0x7F-0x9F)
// Allow newline (0x0A) and carriage return (0x0D) for multi-line text
if r == '\n' || r == '\r' {
continue
}
// Reject control characters (0x00-0x1F and 0x7F-0x9F), except newline and carriage return
if r < 0x20 || (r >= 0x7F && r < 0xA0) {
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains invalid control character at position %d", i))
}
@@ -152,8 +157,46 @@ func PrintableText() ValidatorFunc {
}
}
// NoNewLine validates that a string does not contain newline or carriage return characters.
// It rejects:
// - Newline characters (\n, 0x0A)
// - Carriage return characters (\r, 0x0D)
//
// This is useful for validating single-line fields like names and titles where line breaks
// should not be allowed.
func NoNewLine() ValidatorFunc {
return func(value any) *ValidationError {
actualValue, isNil := dereferenceValue(value)
if isNil {
return nil
}
str, ok := actualValue.(string)
if !ok {
return newValidationError(ErrorCodeInvalidFormat, "value must be a string")
}
if str == "" {
return nil
}
for i, r := range str {
if r == '\n' {
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains newline character at position %d", i))
}
if r == '\r' {
return newValidationError(ErrorCodeInvalidFormat, fmt.Sprintf("contains carriage return character at position %d", i))
}
}
return nil
}
}
// SafeText validates that a string is non-empty, bounded, and contains only safe content.
// It combines NotEmpty, MaxLen, NoHTML, and PrintableText validators.
// This allows newlines and carriage returns for multi-line text fields.
// Use SafeTextNoNewLine for single-line field validation that should reject line breaks.
func SafeText(maxLen int) ValidatorFunc {
validators := []ValidatorFunc{
NotEmpty(),
@@ -171,3 +214,25 @@ func SafeText(maxLen int) ValidatorFunc {
return nil
}
}
// SafeTextNoNewLine validates that a string is non-empty, bounded, and contains only safe content
// without newlines or carriage returns. It combines NotEmpty, MaxLen, NoHTML, PrintableText, and NoNewLine validators.
// This is ideal for validating single-line fields like names, titles, and display names.
func SafeTextNoNewLine(maxLen int) ValidatorFunc {
validators := []ValidatorFunc{
NotEmpty(),
MaxLen(maxLen),
NoHTML(),
PrintableText(),
NoNewLine(),
}
return func(value any) *ValidationError {
for _, validator := range validators {
if err := validator(value); err != nil {
return err
}
}
return nil
}
}

View File

@@ -381,19 +381,27 @@ func TestPrintableText(t *testing.T) {
}
})
t.Run("invalid - newline character", func(t *testing.T) {
t.Run("valid - newline character", func(t *testing.T) {
str := "test\ntext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for newline character")
if err != nil {
t.Errorf("expected no error for newline character, got: %v", err)
}
})
t.Run("invalid - carriage return", func(t *testing.T) {
t.Run("valid - carriage return", func(t *testing.T) {
str := "test\rtext"
err := PrintableText()(&str)
if err == nil {
t.Error("expected validation error for carriage return")
if err != nil {
t.Errorf("expected no error for carriage return, got: %v", err)
}
})
t.Run("valid - multiple newlines", func(t *testing.T) {
str := "hello foo\nbar\n\njd"
err := PrintableText()(&str)
if err != nil {
t.Errorf("expected no error for multiple newlines, got: %v", err)
}
})
@@ -645,11 +653,19 @@ func TestSafeText(t *testing.T) {
}
})
t.Run("invalid - contains newline", func(t *testing.T) {
t.Run("valid - contains newline", func(t *testing.T) {
str := "test\ntext"
err := SafeText(100)(&str)
if err == nil {
t.Error("expected validation error for newline")
if err != nil {
t.Errorf("expected no error for newline, got: %v", err)
}
})
t.Run("valid - contains multiple newlines", func(t *testing.T) {
str := "hello foo\nbar\n\njd"
err := SafeText(100)(&str)
if err != nil {
t.Errorf("expected no error for multiple newlines, got: %v", err)
}
})
@@ -753,3 +769,157 @@ func TestSafeText(t *testing.T) {
}
})
}
func TestNoNewLine(t *testing.T) {
t.Run("valid text without newlines", func(t *testing.T) {
str := "Product Name 2024"
err := NoNewLine()(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
})
t.Run("invalid - contains newline", func(t *testing.T) {
str := "Line 1\nLine 2"
err := NoNewLine()(&str)
if err == nil {
t.Error("expected validation error for newline")
}
if !strings.Contains(err.Message, "newline") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - contains carriage return", func(t *testing.T) {
str := "Line 1\rLine 2"
err := NoNewLine()(&str)
if err == nil {
t.Error("expected validation error for carriage return")
}
if !strings.Contains(err.Message, "carriage return") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - contains both newline and carriage return", func(t *testing.T) {
str := "Line 1\n\rLine 3"
err := NoNewLine()(&str)
if err == nil {
t.Error("expected validation error for newline or carriage return")
}
})
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := NoNewLine()(str)
if err != nil {
t.Errorf("expected no error for nil pointer, got: %v", err)
}
})
t.Run("empty string", func(t *testing.T) {
str := ""
err := NoNewLine()(&str)
if err != nil {
t.Errorf("expected no error for empty string, got: %v", err)
}
})
}
func TestSafeTextNoNewLine(t *testing.T) {
t.Run("valid text", func(t *testing.T) {
str := "Product Name 2024"
err := SafeTextNoNewLine(100)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
})
t.Run("valid UTF-8 text", func(t *testing.T) {
str := "José García"
err := SafeTextNoNewLine(50)(&str)
if err != nil {
t.Errorf("expected no error, got: %v", err)
}
})
t.Run("invalid - contains newline", func(t *testing.T) {
str := "Line 1\nLine 2"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for newline")
}
if !strings.Contains(err.Message, "newline") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - contains carriage return", func(t *testing.T) {
str := "Line 1\rLine 2"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for carriage return")
}
if !strings.Contains(err.Message, "carriage return") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - empty string", func(t *testing.T) {
str := ""
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for empty string")
}
if !strings.Contains(err.Message, "empty") && !strings.Contains(err.Message, "required") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - exceeds max length", func(t *testing.T) {
str := "This is a very long string that exceeds the maximum length"
err := SafeTextNoNewLine(10)(&str)
if err == nil {
t.Error("expected validation error for exceeding max length")
}
if !strings.Contains(err.Message, "at most") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - contains HTML tags", func(t *testing.T) {
str := "Hello <b>World</b>"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for HTML tags")
}
if !strings.Contains(err.Message, "HTML tags") {
t.Errorf("unexpected error message: %s", err.Message)
}
})
t.Run("invalid - contains tab character", func(t *testing.T) {
str := "test\ttext"
err := SafeTextNoNewLine(100)(&str)
if err == nil {
t.Error("expected validation error for tab character")
}
})
t.Run("nil pointer", func(t *testing.T) {
var str *string
err := SafeTextNoNewLine(100)(str)
if err != nil {
t.Errorf("expected no error for nil pointer, got: %v", err)
}
})
t.Run("edge case - exactly at max length", func(t *testing.T) {
str := "12345"
err := SafeTextNoNewLine(5)(&str)
if err != nil {
t.Errorf("expected no error for string at max length, got: %v", err)
}
})
}