Skip to content
Merged
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
19 changes: 17 additions & 2 deletions oq/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,24 @@ func toonValue(v expr.Value) string {
case expr.KindBool:
return strconv.FormatBool(v.Bool)
case expr.KindArray:
return toonEscape(strings.Join(v.Arr, ";"))
return toonArrayValue(v.Arr)
default:
return "null"
}
}

func toonArrayValue(values []string) string {
parts := make([]string, len(values))
for i, value := range values {
if strings.Contains(value, ";") {
parts[i] = toonQuote(value)
continue
}
parts[i] = toonEscape(value)
}
return strings.Join(parts, ";")
}

// toonEscape quotes a string if it needs escaping for TOON format.
// A string must be quoted if it: is empty, contains comma/colon/quote/backslash/
// brackets/braces/control chars, has leading/trailing whitespace, or matches
Expand Down Expand Up @@ -387,7 +399,10 @@ func toonEscape(s string) string {
if !needsQuote {
return s
}
// Quote with escaping
return toonQuote(s)
}

func toonQuote(s string) string {
var sb strings.Builder
sb.WriteByte('"')
for _, ch := range s {
Expand Down
18 changes: 18 additions & 0 deletions oq/format_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package oq

import (
"testing"

"github.com/speakeasy-api/openapi/oq/expr"
"github.com/stretchr/testify/assert"
)

func TestToonValue_ArrayEscapesSemicolonElements(t *testing.T) {
t.Parallel()

value := expr.ArrayVal([]string{"v1;deprecated", "v2;current"})

encoded := toonValue(value)

assert.Equal(t, `"v1;deprecated";"v2;current"`, encoded, "array elements containing the delimiter should be quoted individually")
}