Skip to content

Commit 1f1cc72

Browse files
committed
updates from comments
1 parent 248c151 commit 1f1cc72

10 files changed

Lines changed: 155 additions & 56 deletions

File tree

cmd/dbc/add.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"bytes"
1819
"encoding/json"
1920
"errors"
2021
"fmt"
@@ -34,14 +35,20 @@ import (
3435
var msgStyle = lipgloss.NewStyle().Faint(true)
3536

3637
func marshalEnvelope(kind string, payload any) string {
37-
payloadBytes, _ := json.Marshal(payload)
38+
var b bytes.Buffer
39+
enc := json.NewEncoder(&b)
40+
enc.SetEscapeHTML(false) // don't escape <, >, & in JSON output
41+
enc.Encode(payload)
42+
3843
env := jsonschema.Envelope{
3944
SchemaVersion: jsonschema.SchemaVersion,
4045
Kind: kind,
41-
Payload: json.RawMessage(payloadBytes),
46+
Payload: json.RawMessage(b.Bytes()),
4247
}
43-
out, _ := json.Marshal(env)
44-
return string(out)
48+
49+
b.Reset()
50+
enc.Encode(env)
51+
return b.String()
4552
}
4653

4754
func driverListPath(path string) (string, error) {

cmd/dbc/add_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,36 @@ func (suite *SubcommandTestSuite) TestAdd_JSON() {
454454
suite.NotEmpty(resp.DriverListPath)
455455
}
456456

457+
func (suite *SubcommandTestSuite) TestAdd_JSON_Constraint() {
458+
m := InitCmd{Path: filepath.Join(suite.tempdir, "dbc.toml")}.GetModel()
459+
suite.runCmd(m)
460+
461+
m = AddCmd{
462+
Path: filepath.Join(suite.tempdir, "dbc.toml"),
463+
Driver: []string{"test-driver-1>=1.0.0"},
464+
Json: true,
465+
}.GetModelCustom(
466+
baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg})
467+
468+
out := suite.runCmd(m)
469+
470+
var env jsonschema.Envelope
471+
suite.Require().NoError(json.Unmarshal([]byte(out), &env), "output must be valid JSON: %s", out)
472+
suite.Equal(1, env.SchemaVersion)
473+
suite.Equal("add.response", env.Kind)
474+
475+
// verify we aren't escaping the > character in the JSON output
476+
suite.Equal(string(env.Payload), `{"driver_list_path":"`+filepath.Join(suite.tempdir, "dbc.toml")+
477+
`","drivers":[{"name":"test-driver-1","version_constraint":">=1.0.0"}]}`)
478+
479+
var resp jsonschema.AddResponse
480+
suite.Require().NoError(json.Unmarshal(env.Payload, &resp))
481+
suite.Require().Len(resp.Drivers, 1)
482+
suite.Equal("test-driver-1", resp.Drivers[0].Name)
483+
suite.Equal(">=1.0.0", resp.Drivers[0].VersionConstraint)
484+
suite.NotEmpty(resp.DriverListPath)
485+
}
486+
457487
func (suite *SubcommandTestSuite) TestAddMultipleOutput() {
458488
m := InitCmd{Path: filepath.Join(suite.tempdir, "dbc.toml")}.GetModel()
459489
suite.runCmd(m)

cmd/dbc/completions/dbc.bash

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ _dbc_install_completions() {
6969
esac
7070

7171
if [[ "$cur" == -* ]]; then
72-
COMPREPLY=($(compgen -W "--json --no-verify --level -l --pre" -- "$cur"))
72+
COMPREPLY=($(compgen -W "--json --json-stream-progress --no-verify --level -l --pre" -- "$cur"))
7373
return 0
7474
fi
7575

@@ -162,7 +162,7 @@ _dbc_sync_completions() {
162162
esac
163163

164164
if [[ "$cur" == -* ]]; then
165-
COMPREPLY=($(compgen -W "-h --level -l --path -p --no-verify" -- "$cur"))
165+
COMPREPLY=($(compgen -W "-h --level -l --path -p --no-verify --json --json-stream-progress" -- "$cur"))
166166
return 0
167167
fi
168168

cmd/dbc/completions/dbc.fish

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ complete -f -c dbc -n '__fish_dbc_needs_command' -a 'auth' -d 'Authenticate with
4444
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -s h -d 'Show Help'
4545
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l help -d 'Show Help'
4646
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l json -d 'Print output as JSON instead of plaintext'
47+
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l json-stream-progress -d 'Stream progress events as JSON lines (implies --json)'
4748
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l no-verify -d 'Do not verify the driver after installation'
4849
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l pre -d 'Allow implicit installation of pre-release versions'
4950
complete -f -c dbc -n '__fish_dbc_using_subcommand install' -l level -s l -d 'Installation level' -xa 'user system'
@@ -69,6 +70,8 @@ complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l help -d 'Help'
6970
complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l level -s l -d 'Installation level' -xa 'user system'
7071
complete -c dbc -n '__fish_dbc_using_subcommand sync' -l path -s p -r -F -a '*.toml' -d 'Driver list to sync'
7172
complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l no-verify -d 'Do not verify the driver after installation'
73+
complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l json -d 'Print output as JSON instead of plaintext'
74+
complete -f -c dbc -n '__fish_dbc_using_subcommand sync' -l json-stream-progress -d 'Stream progress events as JSON lines (implies --json)'
7275

7376
# search subcommand
7477
complete -f -c dbc -n '__fish_dbc_using_subcommand search' -s h -d 'Help'

cmd/dbc/completions/dbc.zsh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ function _dbc_install_completions {
7676
'(-h)--help[Help]' \
7777
'--no-verify[do not verify the driver after installation]' \
7878
'--json[Print output as JSON instead of plaintext]' \
79+
'--json-stream-progress[Stream progress events as JSON lines (implies --json)]' \
7980
'--pre[Allow implicit installation of pre-release versions]' \
8081
'(-l)--level[installation level]: :(user system)' \
8182
'(--level)-l[installation level]: :(user system)' \
@@ -117,7 +118,9 @@ function _dbc_sync_completions {
117118
'(--level)-l[installation level]: :(user system)' \
118119
'(-p)--path[driver list to add to]: :_files -g \*.toml' \
119120
'(--path)-p[driver list to add to]: :_files -g \*.toml' \
120-
'--no-verify[do not verify the driver after installation]'
121+
'--no-verify[do not verify the driver after installation]' \
122+
'--json[Print output as JSON instead of plaintext]' \
123+
'--json-stream-progress[Stream progress events as JSON lines (implies --json)]'
121124
}
122125

123126
function _dbc_search_completions {

cmd/dbc/install.go

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"encoding/json"
1919
"errors"
2020
"fmt"
21+
"io"
2122
"io/fs"
2223
"os"
2324
"path/filepath"
@@ -67,6 +68,7 @@ type InstallCmd struct {
6768
Driver string `arg:"positional,required" help:"Driver to install, optionally with a version constraint (for example: mysql, mysql=0.1.0, mysql>=1,<2)"`
6869
Level config.ConfigLevel `arg:"-l" help:"Config level to install to (user, system)"`
6970
Json bool `arg:"--json" help:"Print output as JSON instead of plaintext"`
71+
JsonStreamProgress bool `arg:"--json-stream-progress" help:"Stream progress events as JSON lines (implies --json)"`
7072
NoVerify bool `arg:"--no-verify" help:"Allow installation of drivers without a signature file"`
7173
Pre bool `arg:"--pre" help:"Allow implicit installation of pre-release versions"`
7274
InsecureNoChecksum bool `arg:"--insecure-no-checksum" help:"Skip sha256 checksum recording (not recommended)"`
@@ -89,7 +91,8 @@ func (c InstallCmd) GetModelCustom(baseModel baseModel) tea.Model {
8991
return progressiveInstallModel{
9092
Driver: c.Driver,
9193
NoVerify: c.NoVerify,
92-
jsonOutput: c.Json,
94+
jsonOutput: c.Json || c.JsonStreamProgress,
95+
jsonStreamProgress: c.JsonStreamProgress,
9396
Pre: c.Pre,
9497
insecureNoChecksum: c.InsecureNoChecksum,
9598
spinner: s,
@@ -181,8 +184,21 @@ func (progressiveInstallModel) NeedsRenderer() {}
181184

182185
func (m progressiveInstallModel) IsJSONMode() bool { return m.jsonOutput }
183186

187+
func (m progressiveInstallModel) WithJSONWriter(w io.Writer) tea.Model {
188+
m.jsonOut = w
189+
return m
190+
}
191+
192+
func (m progressiveInstallModel) emitJSON(kind string, payload any) {
193+
out := m.jsonOut
194+
if out == nil {
195+
out = os.Stdout
196+
}
197+
fmt.Fprintln(out, marshalEnvelope(kind, payload))
198+
}
199+
184200
func (m progressiveInstallModel) addEvent(event string, extra ...func(*jsonschema.InstallProgressEvent)) progressiveInstallModel {
185-
if !m.jsonOutput {
201+
if !m.jsonStreamProgress {
186202
return m
187203
}
188204
evt := jsonschema.InstallProgressEvent{
@@ -192,19 +208,20 @@ func (m progressiveInstallModel) addEvent(event string, extra ...func(*jsonschem
192208
for _, fn := range extra {
193209
fn(&evt)
194210
}
195-
m.jsonEvents = append(m.jsonEvents, marshalEnvelope("install.progress", evt))
211+
m.emitJSON("install.progress", evt)
196212
return m
197213
}
198214

199215
type progressiveInstallModel struct {
200216
baseModel
201217

202-
Driver string
203-
VersionInput *semver.Version
204-
NoVerify bool
205-
jsonOutput bool
206-
Pre bool
207-
cfg config.Config
218+
Driver string
219+
VersionInput *semver.Version
220+
NoVerify bool
221+
jsonOutput bool
222+
jsonStreamProgress bool
223+
Pre bool
224+
cfg config.Config
208225

209226
insecureNoChecksum bool
210227
installedDriverInfo config.DriverInfo
@@ -222,8 +239,8 @@ type progressiveInstallModel struct {
222239
localPackagePath string
223240

224241
registryErrors error
225-
jsonEvents []string
226242
alreadyInstalledChecksum string
243+
jsonOut io.Writer
227244
jsonErrorOutput string // JSON error envelope to emit via FinalOutput
228245
}
229246

@@ -353,31 +370,15 @@ func (m progressiveInstallModel) FinalOutput() string {
353370

354371
if m.jsonOutput {
355372
if installStatus.Checksum != "" {
356-
m = m.addEvent("verify.checksum.ok", func(e *jsonschema.InstallProgressEvent) {
373+
m.addEvent("verify.checksum.ok", func(e *jsonschema.InstallProgressEvent) {
357374
e.Checksum = installStatus.Checksum
358375
})
359376
}
360-
payloadBytes, err := json.Marshal(installStatus)
361-
if err != nil {
362-
return fmt.Sprintf(`{"schema_version":1,"kind":"error","payload":{"code":"marshal_error","message":"%s"}}`, err.Error())
363-
}
364-
env := jsonschema.Envelope{
365-
SchemaVersion: jsonschema.SchemaVersion,
366-
Kind: "install.status",
367-
Payload: json.RawMessage(payloadBytes),
368-
}
369-
jsonOutput, err := json.Marshal(env)
370-
if err != nil {
371-
return fmt.Sprintf(`{"schema_version":1,"kind":"error","payload":{"code":"marshal_error","message":"%s"}}`, err.Error())
372-
}
373-
completeLine := marshalEnvelope("install.progress", jsonschema.InstallProgressEvent{
377+
m.emitJSON("install.progress", jsonschema.InstallProgressEvent{
374378
Event: "install.complete",
375379
Driver: m.Driver,
376380
})
377-
allLines := make([]string, 0, len(m.jsonEvents)+2)
378-
allLines = append(allLines, m.jsonEvents...)
379-
allLines = append(allLines, completeLine, string(jsonOutput))
380-
return strings.Join(allLines, "\n")
381+
return marshalEnvelope("install.status", installStatus)
381382
}
382383

383384
if installStatus.Conflict != "" {

cmd/dbc/install_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ func (suite *SubcommandTestSuite) TestInstall_InsecureNoChecksumFlag() {
530530
}
531531

532532
func (suite *SubcommandTestSuite) TestInstall_JSONProgressStream() {
533-
m := InstallCmd{Driver: "test-driver-1", Level: suite.configLevel, Json: true}.
533+
m := InstallCmd{Driver: "test-driver-1", Level: suite.configLevel, JsonStreamProgress: true}.
534534
GetModelCustom(baseModel{getDriverRegistry: getTestDriverRegistry, downloadPkg: downloadTestPkg})
535535
out := suite.runCmd(m)
536536

cmd/dbc/subcommand_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"context"
2020
"encoding/json"
2121
"fmt"
22+
"io"
2223
"io/fs"
2324
"net/url"
2425
"os"
@@ -145,11 +146,19 @@ func (suite *SubcommandTestSuite) Dir() string {
145146
return suite.configLevel.ConfigLocation()
146147
}
147148

149+
// HasJSONWriter is implemented by models that stream JSON events directly to an io.Writer.
150+
type HasJSONWriter interface {
151+
WithJSONWriter(io.Writer) tea.Model
152+
}
153+
148154
func (suite *SubcommandTestSuite) runCmdErr(m tea.Model) string {
149155
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
150156
defer cancel()
151157

152158
var out bytes.Buffer
159+
if jw, ok := m.(HasJSONWriter); ok {
160+
m = jw.WithJSONWriter(&out)
161+
}
153162
prog = tea.NewProgram(m, tea.WithInput(nil), tea.WithOutput(&out),
154163
tea.WithoutRenderer(), tea.WithContext(ctx))
155164
defer func() {
@@ -185,6 +194,9 @@ func (suite *SubcommandTestSuite) runCmd(m tea.Model) string {
185194
defer cancel()
186195

187196
var out bytes.Buffer
197+
if jw, ok := m.(HasJSONWriter); ok {
198+
m = jw.WithJSONWriter(&out)
199+
}
188200
prog = tea.NewProgram(m, tea.WithInput(nil), tea.WithOutput(&out),
189201
tea.WithoutRenderer(), tea.WithContext(ctx))
190202
defer func() {

0 commit comments

Comments
 (0)