refactor(ports,ipc): move wire codecs to ports — restore usecase layering + guard
- commit
- 4ab7e23613fded903be5907236386e1542921802
- author
- brice · · 2026-07-02 11:34 UTC
- parent
- 2795b89
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +71 -55
M boundary_test.go +5 -0
| @@ -24,6 +24,11 @@ func TestImportBoundaries(t *testing.T) { | |||
| 24 | 24 | root: "pkg", | |
| 25 | 25 | disallowedPrefix: "github.com/bnema/vev/internal", | |
| 26 | 26 | }, | |
| 27 | + | { | |
| 28 | + | name: "usecase must not import adapters", | |
| 29 | + | root: "internal/usecase", | |
| 30 | + | disallowedPrefix: "github.com/bnema/vev/internal/adapters", | |
| 31 | + | }, | |
| 27 | 32 | } | |
| 28 | 33 | ||
| 29 | 34 | for _, rule := range rules { | |
| @@ -24,6 +24,11 @@ func TestImportBoundaries(t *testing.T) { | |||||
| 24 | root: "pkg", | 24 | root: "pkg", | ||
| 25 | disallowedPrefix: "github.com/bnema/vev/internal", | 25 | disallowedPrefix: "github.com/bnema/vev/internal", | ||
| 26 | }, | 26 | }, | ||
| 27 | + | { | |||
| 28 | + | name: "usecase must not import adapters", | |||
| 29 | + | root: "internal/usecase", | |||
| 30 | + | disallowedPrefix: "github.com/bnema/vev/internal/adapters", | |||
| 31 | + | }, | |||
| 27 | } | 32 | } | ||
| 28 | 33 | ||||
| 29 | for _, rule := range rules { | 34 | for _, rule := range rules { | ||
M internal/adapters/ipc/transport_test.go +4 -4
| @@ -21,8 +21,8 @@ func TestTransportSendRecvBothDirections(t *testing.T) { | |||
| 21 | 21 | client := NewTransport(c1) | |
| 22 | 22 | server := NewTransport(c2) | |
| 23 | 23 | ||
| 24 | - | clientToServer := ports.Frame{Type: ports.MsgHello, Payload: MarshalHello(Hello{Version: 1, Intent: IntentNew, Name: "w0"})} | |
| 25 | - | serverToClient := ports.Frame{Type: ports.MsgWelcome, Payload: MarshalWelcome(Welcome{SessionID: "s1", SessionName: "main"})} | |
| 24 | + | clientToServer := ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(ports.Hello{Version: 1, Intent: ports.IntentNew, Name: "w0"})} | |
| 25 | + | serverToClient := ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(ports.Welcome{SessionID: "s1", SessionName: "main"})} | |
| 26 | 26 | ||
| 27 | 27 | var wg sync.WaitGroup | |
| 28 | 28 | wg.Add(1) | |
| @@ -143,8 +143,8 @@ func TestTransportManySmallFramesBackToBack(t *testing.T) { | |||
| 143 | 143 | frames[i] = ports.Frame{Type: ports.MsgPing, Payload: nil} | |
| 144 | 144 | } | |
| 145 | 145 | // Vary a couple to prove ordering and content are both preserved. | |
| 146 | - | frames[7] = ports.Frame{Type: ports.MsgInput, Payload: MarshalInput(Input{Data: []byte("hop")})} | |
| 147 | - | frames[150] = ports.Frame{Type: ports.MsgResize, Payload: MarshalResize(Resize{Size: domain.Size{Cols: 5, Rows: 6}})} | |
| 146 | + | frames[7] = ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("hop")})} | |
| 147 | + | frames[150] = ports.Frame{Type: ports.MsgResize, Payload: ports.MarshalResize(ports.Resize{Size: domain.Size{Cols: 5, Rows: 6}})} | |
| 148 | 148 | ||
| 149 | 149 | var wg sync.WaitGroup | |
| 150 | 150 | wg.Add(1) | |
| @@ -21,8 +21,8 @@ func TestTransportSendRecvBothDirections(t *testing.T) { | |||||
| 21 | client := NewTransport(c1) | 21 | client := NewTransport(c1) | ||
| 22 | server := NewTransport(c2) | 22 | server := NewTransport(c2) | ||
| 23 | 23 | ||||
| 24 | - | clientToServer := ports.Frame{Type: ports.MsgHello, Payload: MarshalHello(Hello{Version: 1, Intent: IntentNew, Name: "w0"})} | 24 | + | clientToServer := ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(ports.Hello{Version: 1, Intent: ports.IntentNew, Name: "w0"})} |
| 25 | - | serverToClient := ports.Frame{Type: ports.MsgWelcome, Payload: MarshalWelcome(Welcome{SessionID: "s1", SessionName: "main"})} | 25 | + | serverToClient := ports.Frame{Type: ports.MsgWelcome, Payload: ports.MarshalWelcome(ports.Welcome{SessionID: "s1", SessionName: "main"})} |
| 26 | 26 | ||||
| 27 | var wg sync.WaitGroup | 27 | var wg sync.WaitGroup | ||
| 28 | wg.Add(1) | 28 | wg.Add(1) | ||
| @@ -143,8 +143,8 @@ func TestTransportManySmallFramesBackToBack(t *testing.T) { | |||||
| 143 | frames[i] = ports.Frame{Type: ports.MsgPing, Payload: nil} | 143 | frames[i] = ports.Frame{Type: ports.MsgPing, Payload: nil} | ||
| 144 | } | 144 | } | ||
| 145 | // Vary a couple to prove ordering and content are both preserved. | 145 | // Vary a couple to prove ordering and content are both preserved. | ||
| 146 | - | frames[7] = ports.Frame{Type: ports.MsgInput, Payload: MarshalInput(Input{Data: []byte("hop")})} | 146 | + | frames[7] = ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: []byte("hop")})} |
| 147 | - | frames[150] = ports.Frame{Type: ports.MsgResize, Payload: MarshalResize(Resize{Size: domain.Size{Cols: 5, Rows: 6}})} | 147 | + | frames[150] = ports.Frame{Type: ports.MsgResize, Payload: ports.MarshalResize(ports.Resize{Size: domain.Size{Cols: 5, Rows: 6}})} |
| 148 | 148 | ||||
| 149 | var wg sync.WaitGroup | 149 | var wg sync.WaitGroup | ||
| 150 | wg.Add(1) | 150 | wg.Add(1) | ||
M internal/app/run.go +9 -9
| @@ -80,7 +80,7 @@ func Run(args []string) error { | |||
| 80 | 80 | // I/O. | |
| 81 | 81 | func parseArgs(args []string) (command, error) { | |
| 82 | 82 | if len(args) == 0 { | |
| 83 | - | return command{kind: kindAttach, intent: ipc.IntentEphemeral}, nil | |
| 83 | + | return command{kind: kindAttach, intent: ports.IntentEphemeral}, nil | |
| 84 | 84 | } | |
| 85 | 85 | ||
| 86 | 86 | switch args[0] { | |
| @@ -90,12 +90,12 @@ func parseArgs(args []string) (command, error) { | |||
| 90 | 90 | if len(args) < 2 || args[1] == "" { | |
| 91 | 91 | return command{}, usagef("`new` requires a session name") | |
| 92 | 92 | } | |
| 93 | - | return command{kind: kindAttach, intent: ipc.IntentNew, name: args[1]}, nil | |
| 93 | + | return command{kind: kindAttach, intent: ports.IntentNew, name: args[1]}, nil | |
| 94 | 94 | case "attach", "a": | |
| 95 | 95 | if len(args) < 2 || args[1] == "" { | |
| 96 | 96 | return command{}, usagef("`attach` requires a session name") | |
| 97 | 97 | } | |
| 98 | - | return command{kind: kindAttach, intent: ipc.IntentAttach, name: args[1]}, nil | |
| 98 | + | return command{kind: kindAttach, intent: ports.IntentAttach, name: args[1]}, nil | |
| 99 | 99 | case "ls", "list": | |
| 100 | 100 | return command{kind: kindList}, nil | |
| 101 | 101 | case "kill": | |
| @@ -184,7 +184,7 @@ func runList(_ context.Context) error { | |||
| 184 | 184 | } | |
| 185 | 185 | defer transport.Close() | |
| 186 | 186 | ||
| 187 | - | if err := transport.Send(ports.Frame{Type: ports.MsgList, Payload: ipc.MarshalList(ipc.List{})}); err != nil { | |
| 187 | + | if err := transport.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})}); err != nil { | |
| 188 | 188 | return fmt.Errorf("vev: requesting session list: %w", err) | |
| 189 | 189 | } | |
| 190 | 190 | reply, err := transport.Recv() | |
| @@ -192,13 +192,13 @@ func runList(_ context.Context) error { | |||
| 192 | 192 | return fmt.Errorf("vev: reading session list: %w", err) | |
| 193 | 193 | } | |
| 194 | 194 | if reply.Type == ports.MsgError { | |
| 195 | - | em, _ := ipc.UnmarshalErrorMsg(reply.Payload) | |
| 195 | + | em, _ := ports.UnmarshalErrorMsg(reply.Payload) | |
| 196 | 196 | return fmt.Errorf("vev: %s", em.Text) | |
| 197 | 197 | } | |
| 198 | 198 | if reply.Type != ports.MsgSessions { | |
| 199 | 199 | return fmt.Errorf("vev: unexpected reply type %d to list", reply.Type) | |
| 200 | 200 | } | |
| 201 | - | sessions, err := ipc.UnmarshalSessions(reply.Payload) | |
| 201 | + | sessions, err := ports.UnmarshalSessions(reply.Payload) | |
| 202 | 202 | if err != nil { | |
| 203 | 203 | return fmt.Errorf("vev: decoding session list: %w", err) | |
| 204 | 204 | } | |
| @@ -207,7 +207,7 @@ func runList(_ context.Context) error { | |||
| 207 | 207 | } | |
| 208 | 208 | ||
| 209 | 209 | // printSessions renders a session table (or a friendly note when empty). | |
| 210 | - | func printSessions(w io.Writer, sessions []ipc.SessionInfo) { | |
| 210 | + | func printSessions(w io.Writer, sessions []ports.SessionInfo) { | |
| 211 | 211 | if len(sessions) == 0 { | |
| 212 | 212 | fmt.Fprintln(w, "no sessions") | |
| 213 | 213 | return | |
| @@ -232,7 +232,7 @@ func runKill(_ context.Context, name string) error { | |||
| 232 | 232 | } | |
| 233 | 233 | defer transport.Close() | |
| 234 | 234 | ||
| 235 | - | if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ipc.MarshalKill(ipc.Kill{Name: name})}); err != nil { | |
| 235 | + | if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(ports.Kill{Name: name})}); err != nil { | |
| 236 | 236 | return fmt.Errorf("vev: requesting kill: %w", err) | |
| 237 | 237 | } | |
| 238 | 238 | reply, err := transport.Recv() | |
| @@ -246,7 +246,7 @@ func runKill(_ context.Context, name string) error { | |||
| 246 | 246 | return fmt.Errorf("vev: reading kill reply: %w", err) | |
| 247 | 247 | } | |
| 248 | 248 | if reply.Type == ports.MsgError { | |
| 249 | - | em, _ := ipc.UnmarshalErrorMsg(reply.Payload) | |
| 249 | + | em, _ := ports.UnmarshalErrorMsg(reply.Payload) | |
| 250 | 250 | return fmt.Errorf("vev: %s", em.Text) | |
| 251 | 251 | } | |
| 252 | 252 | fmt.Printf("killed %s\n", name) | |
| @@ -80,7 +80,7 @@ func Run(args []string) error { | |||||
| 80 | // I/O. | 80 | // I/O. | ||
| 81 | func parseArgs(args []string) (command, error) { | 81 | func parseArgs(args []string) (command, error) { | ||
| 82 | if len(args) == 0 { | 82 | if len(args) == 0 { | ||
| 83 | - | return command{kind: kindAttach, intent: ipc.IntentEphemeral}, nil | 83 | + | return command{kind: kindAttach, intent: ports.IntentEphemeral}, nil |
| 84 | } | 84 | } | ||
| 85 | 85 | ||||
| 86 | switch args[0] { | 86 | switch args[0] { | ||
| @@ -90,12 +90,12 @@ func parseArgs(args []string) (command, error) { | |||||
| 90 | if len(args) < 2 || args[1] == "" { | 90 | if len(args) < 2 || args[1] == "" { | ||
| 91 | return command{}, usagef("`new` requires a session name") | 91 | return command{}, usagef("`new` requires a session name") | ||
| 92 | } | 92 | } | ||
| 93 | - | return command{kind: kindAttach, intent: ipc.IntentNew, name: args[1]}, nil | 93 | + | return command{kind: kindAttach, intent: ports.IntentNew, name: args[1]}, nil |
| 94 | case "attach", "a": | 94 | case "attach", "a": | ||
| 95 | if len(args) < 2 || args[1] == "" { | 95 | if len(args) < 2 || args[1] == "" { | ||
| 96 | return command{}, usagef("`attach` requires a session name") | 96 | return command{}, usagef("`attach` requires a session name") | ||
| 97 | } | 97 | } | ||
| 98 | - | return command{kind: kindAttach, intent: ipc.IntentAttach, name: args[1]}, nil | 98 | + | return command{kind: kindAttach, intent: ports.IntentAttach, name: args[1]}, nil |
| 99 | case "ls", "list": | 99 | case "ls", "list": | ||
| 100 | return command{kind: kindList}, nil | 100 | return command{kind: kindList}, nil | ||
| 101 | case "kill": | 101 | case "kill": | ||
| @@ -184,7 +184,7 @@ func runList(_ context.Context) error { | |||||
| 184 | } | 184 | } | ||
| 185 | defer transport.Close() | 185 | defer transport.Close() | ||
| 186 | 186 | ||||
| 187 | - | if err := transport.Send(ports.Frame{Type: ports.MsgList, Payload: ipc.MarshalList(ipc.List{})}); err != nil { | 187 | + | if err := transport.Send(ports.Frame{Type: ports.MsgList, Payload: ports.MarshalList(ports.List{})}); err != nil { |
| 188 | return fmt.Errorf("vev: requesting session list: %w", err) | 188 | return fmt.Errorf("vev: requesting session list: %w", err) | ||
| 189 | } | 189 | } | ||
| 190 | reply, err := transport.Recv() | 190 | reply, err := transport.Recv() | ||
| @@ -192,13 +192,13 @@ func runList(_ context.Context) error { | |||||
| 192 | return fmt.Errorf("vev: reading session list: %w", err) | 192 | return fmt.Errorf("vev: reading session list: %w", err) | ||
| 193 | } | 193 | } | ||
| 194 | if reply.Type == ports.MsgError { | 194 | if reply.Type == ports.MsgError { | ||
| 195 | - | em, _ := ipc.UnmarshalErrorMsg(reply.Payload) | 195 | + | em, _ := ports.UnmarshalErrorMsg(reply.Payload) |
| 196 | return fmt.Errorf("vev: %s", em.Text) | 196 | return fmt.Errorf("vev: %s", em.Text) | ||
| 197 | } | 197 | } | ||
| 198 | if reply.Type != ports.MsgSessions { | 198 | if reply.Type != ports.MsgSessions { | ||
| 199 | return fmt.Errorf("vev: unexpected reply type %d to list", reply.Type) | 199 | return fmt.Errorf("vev: unexpected reply type %d to list", reply.Type) | ||
| 200 | } | 200 | } | ||
| 201 | - | sessions, err := ipc.UnmarshalSessions(reply.Payload) | 201 | + | sessions, err := ports.UnmarshalSessions(reply.Payload) |
| 202 | if err != nil { | 202 | if err != nil { | ||
| 203 | return fmt.Errorf("vev: decoding session list: %w", err) | 203 | return fmt.Errorf("vev: decoding session list: %w", err) | ||
| 204 | } | 204 | } | ||
| @@ -207,7 +207,7 @@ func runList(_ context.Context) error { | |||||
| 207 | } | 207 | } | ||
| 208 | 208 | ||||
| 209 | // printSessions renders a session table (or a friendly note when empty). | 209 | // printSessions renders a session table (or a friendly note when empty). | ||
| 210 | - | func printSessions(w io.Writer, sessions []ipc.SessionInfo) { | 210 | + | func printSessions(w io.Writer, sessions []ports.SessionInfo) { |
| 211 | if len(sessions) == 0 { | 211 | if len(sessions) == 0 { | ||
| 212 | fmt.Fprintln(w, "no sessions") | 212 | fmt.Fprintln(w, "no sessions") | ||
| 213 | return | 213 | return | ||
| @@ -232,7 +232,7 @@ func runKill(_ context.Context, name string) error { | |||||
| 232 | } | 232 | } | ||
| 233 | defer transport.Close() | 233 | defer transport.Close() | ||
| 234 | 234 | ||||
| 235 | - | if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ipc.MarshalKill(ipc.Kill{Name: name})}); err != nil { | 235 | + | if err := transport.Send(ports.Frame{Type: ports.MsgKill, Payload: ports.MarshalKill(ports.Kill{Name: name})}); err != nil { |
| 236 | return fmt.Errorf("vev: requesting kill: %w", err) | 236 | return fmt.Errorf("vev: requesting kill: %w", err) | ||
| 237 | } | 237 | } | ||
| 238 | reply, err := transport.Recv() | 238 | reply, err := transport.Recv() | ||
| @@ -246,7 +246,7 @@ func runKill(_ context.Context, name string) error { | |||||
| 246 | return fmt.Errorf("vev: reading kill reply: %w", err) | 246 | return fmt.Errorf("vev: reading kill reply: %w", err) | ||
| 247 | } | 247 | } | ||
| 248 | if reply.Type == ports.MsgError { | 248 | if reply.Type == ports.MsgError { | ||
| 249 | - | em, _ := ipc.UnmarshalErrorMsg(reply.Payload) | 249 | + | em, _ := ports.UnmarshalErrorMsg(reply.Payload) |
| 250 | return fmt.Errorf("vev: %s", em.Text) | 250 | return fmt.Errorf("vev: %s", em.Text) | ||
| 251 | } | 251 | } | ||
| 252 | fmt.Printf("killed %s\n", name) | 252 | fmt.Printf("killed %s\n", name) | ||
M internal/app/run_test.go +6 -6
| @@ -4,7 +4,7 @@ import ( | |||
| 4 | 4 | "errors" | |
| 5 | 5 | "testing" | |
| 6 | 6 | ||
| 7 | - | "github.com/bnema/vev/internal/adapters/ipc" | |
| 7 | + | "github.com/bnema/vev/internal/ports" | |
| 8 | 8 | ) | |
| 9 | 9 | ||
| 10 | 10 | func TestParseArgs(t *testing.T) { | |
| @@ -16,13 +16,13 @@ func TestParseArgs(t *testing.T) { | |||
| 16 | 16 | wantName string | |
| 17 | 17 | wantErr bool | |
| 18 | 18 | }{ | |
| 19 | - | {name: "no args -> ephemeral attach", args: nil, wantKind: kindAttach, wantIntent: ipc.IntentEphemeral}, | |
| 20 | - | {name: "empty slice -> ephemeral attach", args: []string{}, wantKind: kindAttach, wantIntent: ipc.IntentEphemeral}, | |
| 21 | - | {name: "new named", args: []string{"new", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentNew, wantName: "work"}, | |
| 19 | + | {name: "no args -> ephemeral attach", args: nil, wantKind: kindAttach, wantIntent: ports.IntentEphemeral}, | |
| 20 | + | {name: "empty slice -> ephemeral attach", args: []string{}, wantKind: kindAttach, wantIntent: ports.IntentEphemeral}, | |
| 21 | + | {name: "new named", args: []string{"new", "work"}, wantKind: kindAttach, wantIntent: ports.IntentNew, wantName: "work"}, | |
| 22 | 22 | {name: "new without name", args: []string{"new"}, wantErr: true}, | |
| 23 | 23 | {name: "new empty name", args: []string{"new", ""}, wantErr: true}, | |
| 24 | - | {name: "attach named", args: []string{"attach", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentAttach, wantName: "work"}, | |
| 25 | - | {name: "attach alias a", args: []string{"a", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentAttach, wantName: "work"}, | |
| 24 | + | {name: "attach named", args: []string{"attach", "work"}, wantKind: kindAttach, wantIntent: ports.IntentAttach, wantName: "work"}, | |
| 25 | + | {name: "attach alias a", args: []string{"a", "work"}, wantKind: kindAttach, wantIntent: ports.IntentAttach, wantName: "work"}, | |
| 26 | 26 | {name: "attach without name", args: []string{"attach"}, wantErr: true}, | |
| 27 | 27 | {name: "ls", args: []string{"ls"}, wantKind: kindList}, | |
| 28 | 28 | {name: "list", args: []string{"list"}, wantKind: kindList}, | |
| @@ -4,7 +4,7 @@ import ( | |||||
| 4 | "errors" | 4 | "errors" | ||
| 5 | "testing" | 5 | "testing" | ||
| 6 | 6 | ||||
| 7 | - | "github.com/bnema/vev/internal/adapters/ipc" | 7 | + | "github.com/bnema/vev/internal/ports" |
| 8 | ) | 8 | ) | ||
| 9 | 9 | ||||
| 10 | func TestParseArgs(t *testing.T) { | 10 | func TestParseArgs(t *testing.T) { | ||
| @@ -16,13 +16,13 @@ func TestParseArgs(t *testing.T) { | |||||
| 16 | wantName string | 16 | wantName string | ||
| 17 | wantErr bool | 17 | wantErr bool | ||
| 18 | }{ | 18 | }{ | ||
| 19 | - | {name: "no args -> ephemeral attach", args: nil, wantKind: kindAttach, wantIntent: ipc.IntentEphemeral}, | 19 | + | {name: "no args -> ephemeral attach", args: nil, wantKind: kindAttach, wantIntent: ports.IntentEphemeral}, |
| 20 | - | {name: "empty slice -> ephemeral attach", args: []string{}, wantKind: kindAttach, wantIntent: ipc.IntentEphemeral}, | 20 | + | {name: "empty slice -> ephemeral attach", args: []string{}, wantKind: kindAttach, wantIntent: ports.IntentEphemeral}, |
| 21 | - | {name: "new named", args: []string{"new", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentNew, wantName: "work"}, | 21 | + | {name: "new named", args: []string{"new", "work"}, wantKind: kindAttach, wantIntent: ports.IntentNew, wantName: "work"}, |
| 22 | {name: "new without name", args: []string{"new"}, wantErr: true}, | 22 | {name: "new without name", args: []string{"new"}, wantErr: true}, | ||
| 23 | {name: "new empty name", args: []string{"new", ""}, wantErr: true}, | 23 | {name: "new empty name", args: []string{"new", ""}, wantErr: true}, | ||
| 24 | - | {name: "attach named", args: []string{"attach", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentAttach, wantName: "work"}, | 24 | + | {name: "attach named", args: []string{"attach", "work"}, wantKind: kindAttach, wantIntent: ports.IntentAttach, wantName: "work"}, |
| 25 | - | {name: "attach alias a", args: []string{"a", "work"}, wantKind: kindAttach, wantIntent: ipc.IntentAttach, wantName: "work"}, | 25 | + | {name: "attach alias a", args: []string{"a", "work"}, wantKind: kindAttach, wantIntent: ports.IntentAttach, wantName: "work"}, |
| 26 | {name: "attach without name", args: []string{"attach"}, wantErr: true}, | 26 | {name: "attach without name", args: []string{"attach"}, wantErr: true}, | ||
| 27 | {name: "ls", args: []string{"ls"}, wantKind: kindList}, | 27 | {name: "ls", args: []string{"ls"}, wantKind: kindList}, | ||
| 28 | {name: "list", args: []string{"list"}, wantKind: kindList}, | 28 | {name: "list", args: []string{"list"}, wantKind: kindList}, | ||
R internal/adapters/ipc/protocol.go → internal/ports/wire.go +9 -3 renamed, 95% similar
| @@ -1,4 +1,10 @@ | |||
| 1 | - | package ipc | |
| 1 | + | // Wire codecs for vev's IPC message payloads. They live in ports (alongside | |
| 2 | + | // Frame and the MsgType constants) because wire encoding is protocol | |
| 3 | + | // surface, not I/O: everything here is pure, stdlib-only byte marshalling. | |
| 4 | + | // Adapters keep what actually performs I/O (framing over a connection, | |
| 5 | + | // listening on a socket) and import these types for the payloads they carry. | |
| 6 | + | ||
| 7 | + | package ports | |
| 2 | 8 | ||
| 3 | 9 | import ( | |
| 4 | 10 | "encoding/binary" | |
| @@ -9,12 +15,12 @@ import ( | |||
| 9 | 15 | ||
| 10 | 16 | // errShortPayload is returned when a payload ends before a required field | |
| 11 | 17 | // has been fully read. | |
| 12 | - | var errShortPayload = errors.New("ipc: payload too short") | |
| 18 | + | var errShortPayload = errors.New("ports: payload too short") | |
| 13 | 19 | ||
| 14 | 20 | // errTrailingBytes is returned when a fixed-shape payload has bytes left | |
| 15 | 21 | // over after every field has been consumed. Protocol strictness here | |
| 16 | 22 | // catches version drift early instead of silently ignoring extra data. | |
| 17 | - | var errTrailingBytes = errors.New("ipc: unexpected trailing bytes in payload") | |
| 23 | + | var errTrailingBytes = errors.New("ports: unexpected trailing bytes in payload") | |
| 18 | 24 | ||
| 19 | 25 | // Intent values carried by Hello, describing what the client wants to do. | |
| 20 | 26 | const ( | |
| @@ -1,4 +1,10 @@ | |||||
| 1 | - | package ipc | 1 | + | // Wire codecs for vev's IPC message payloads. They live in ports (alongside |
| 2 | + | // Frame and the MsgType constants) because wire encoding is protocol | |||
| 3 | + | // surface, not I/O: everything here is pure, stdlib-only byte marshalling. | |||
| 4 | + | // Adapters keep what actually performs I/O (framing over a connection, | |||
| 5 | + | // listening on a socket) and import these types for the payloads they carry. | |||
| 6 | + | ||||
| 7 | + | package ports | |||
| 2 | 8 | ||||
| 3 | import ( | 9 | import ( | ||
| 4 | "encoding/binary" | 10 | "encoding/binary" | ||
| @@ -9,12 +15,12 @@ import ( | |||||
| 9 | 15 | ||||
| 10 | // errShortPayload is returned when a payload ends before a required field | 16 | // errShortPayload is returned when a payload ends before a required field | ||
| 11 | // has been fully read. | 17 | // has been fully read. | ||
| 12 | - | var errShortPayload = errors.New("ipc: payload too short") | 18 | + | var errShortPayload = errors.New("ports: payload too short") |
| 13 | 19 | ||||
| 14 | // errTrailingBytes is returned when a fixed-shape payload has bytes left | 20 | // errTrailingBytes is returned when a fixed-shape payload has bytes left | ||
| 15 | // over after every field has been consumed. Protocol strictness here | 21 | // over after every field has been consumed. Protocol strictness here | ||
| 16 | // catches version drift early instead of silently ignoring extra data. | 22 | // catches version drift early instead of silently ignoring extra data. | ||
| 17 | - | var errTrailingBytes = errors.New("ipc: unexpected trailing bytes in payload") | 23 | + | var errTrailingBytes = errors.New("ports: unexpected trailing bytes in payload") |
| 18 | 24 | ||||
| 19 | // Intent values carried by Hello, describing what the client wants to do. | 25 | // Intent values carried by Hello, describing what the client wants to do. | ||
| 20 | const ( | 26 | const ( | ||
R internal/adapters/ipc/protocol_test.go → internal/ports/wire_test.go +1 -1 renamed, 99% similar
| @@ -1,4 +1,4 @@ | |||
| 1 | - | package ipc | |
| 1 | + | package ports | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| @@ -1,4 +1,4 @@ | |||||
| 1 | - | package ipc | 1 | + | package ports |
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
M internal/usecase/client/client.go +21 -15
| @@ -2,9 +2,9 @@ | |||
| 2 | 2 | // IPC protocol to a daemon, pumping terminal input, resize events, and PTY | |
| 3 | 3 | // output between the controlling terminal and the session. | |
| 4 | 4 | // | |
| 5 | - | // The package depends only on ports (transport, terminal) plus the ipc | |
| 6 | - | // message codecs, never on a concrete transport or terminal implementation; | |
| 7 | - | // the app layer wires those in. | |
| 5 | + | // The package depends only on ports (transport, terminal, wire codecs) and | |
| 6 | + | // domain, never on a concrete transport or terminal implementation; the app | |
| 7 | + | // layer wires those in. | |
| 8 | 8 | package client | |
| 9 | 9 | ||
| 10 | 10 | import ( | |
| @@ -15,7 +15,6 @@ import ( | |||
| 15 | 15 | "log/slog" | |
| 16 | 16 | "os" | |
| 17 | 17 | ||
| 18 | - | "github.com/bnema/vev/internal/adapters/ipc" | |
| 19 | 18 | "github.com/bnema/vev/internal/domain" | |
| 20 | 19 | "github.com/bnema/vev/internal/ports" | |
| 21 | 20 | ) | |
| @@ -112,14 +111,14 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||
| 112 | 111 | if err != nil { | |
| 113 | 112 | return fmt.Errorf("vev: reading terminal size: %w", err) | |
| 114 | 113 | } | |
| 115 | - | hello := ipc.Hello{ | |
| 114 | + | hello := ports.Hello{ | |
| 116 | 115 | Version: ports.ProtocolVersion, | |
| 117 | 116 | Intent: intent, | |
| 118 | 117 | Name: name, | |
| 119 | 118 | Size: size, | |
| 120 | 119 | TermEnv: os.Getenv("TERM"), | |
| 121 | 120 | } | |
| 122 | - | if err := transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ipc.MarshalHello(hello)}); err != nil { | |
| 121 | + | if err := transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}); err != nil { | |
| 123 | 122 | return fmt.Errorf("vev: sending hello: %w", err) | |
| 124 | 123 | } | |
| 125 | 124 | ms.helloSent = true | |
| @@ -133,7 +132,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||
| 133 | 132 | case ports.MsgWelcome: | |
| 134 | 133 | ms.welcomed = true | |
| 135 | 134 | case ports.MsgError: | |
| 136 | - | em, derr := ipc.UnmarshalErrorMsg(reply.Payload) | |
| 135 | + | em, derr := ports.UnmarshalErrorMsg(reply.Payload) | |
| 137 | 136 | if derr != nil { | |
| 138 | 137 | return fmt.Errorf("vev: decoding error reply: %w", derr) | |
| 139 | 138 | } | |
| @@ -191,7 +190,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||
| 191 | 190 | } | |
| 192 | 191 | switch r.frame.Type { | |
| 193 | 192 | case ports.MsgOutput: | |
| 194 | - | o, derr := ipc.UnmarshalOutput(r.frame.Payload) | |
| 193 | + | o, derr := ports.UnmarshalOutput(r.frame.Payload) | |
| 195 | 194 | if derr != nil { | |
| 196 | 195 | return fmt.Errorf("vev: decoding output: %w", derr) | |
| 197 | 196 | } | |
| @@ -203,7 +202,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||
| 203 | 202 | } | |
| 204 | 203 | ms.firstOutput = true | |
| 205 | 204 | case ports.MsgDetached: | |
| 206 | - | d, derr := ipc.UnmarshalDetached(r.frame.Payload) | |
| 205 | + | d, derr := ports.UnmarshalDetached(r.frame.Payload) | |
| 207 | 206 | if derr != nil { | |
| 208 | 207 | return fmt.Errorf("vev: decoding detached: %w", derr) | |
| 209 | 208 | } | |
| @@ -266,6 +265,13 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||
| 266 | 265 | // runStdin pumps terminal input to the daemon as Input frames. A read error | |
| 267 | 266 | // or EOF is treated as a detach: it best-effort sends a Detach frame and | |
| 268 | 267 | // cancels the loop. | |
| 268 | + | // | |
| 269 | + | // Known MVP limitation: a bare io.Reader cannot be unblocked from outside, | |
| 270 | + | // so on shutdown initiated elsewhere (daemon detach, transport loss) this | |
| 271 | + | // goroutine stays parked in Read until the next byte arrives or the process | |
| 272 | + | // exits. That is harmless here — Attach has already returned and restored | |
| 273 | + | // the terminal — and matches the standard pattern for stdin pumps; a | |
| 274 | + | // closable stdin duplicate could lift it later if ever needed. | |
| 269 | 275 | func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out chan<- ports.Frame) { | |
| 270 | 276 | buf := make([]byte, stdinBufSize) | |
| 271 | 277 | for { | |
| @@ -273,7 +279,7 @@ func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out | |||
| 273 | 279 | if n > 0 { | |
| 274 | 280 | data := make([]byte, n) | |
| 275 | 281 | copy(data, buf[:n]) | |
| 276 | - | frame := ports.Frame{Type: ports.MsgInput, Payload: ipc.MarshalInput(ipc.Input{Data: data})} | |
| 282 | + | frame := ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: data})} | |
| 277 | 283 | select { | |
| 278 | 284 | case out <- frame: | |
| 279 | 285 | case <-ctx.Done(): | |
| @@ -283,7 +289,7 @@ func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out | |||
| 283 | 289 | if rerr != nil { | |
| 284 | 290 | // Best-effort detach notification, then unwind. | |
| 285 | 291 | select { | |
| 286 | - | case out <- ports.Frame{Type: ports.MsgDetach, Payload: ipc.MarshalDetach(ipc.Detach{})}: | |
| 292 | + | case out <- ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(ports.Detach{})}: | |
| 287 | 293 | case <-ctx.Done(): | |
| 288 | 294 | } | |
| 289 | 295 | cancel() | |
| @@ -302,7 +308,7 @@ func runResize(ctx context.Context, events <-chan domain.Size, out chan<- ports. | |||
| 302 | 308 | if !ok { | |
| 303 | 309 | return | |
| 304 | 310 | } | |
| 305 | - | frame := ports.Frame{Type: ports.MsgResize, Payload: ipc.MarshalResize(ipc.Resize{Size: sz})} | |
| 311 | + | frame := ports.Frame{Type: ports.MsgResize, Payload: ports.MarshalResize(ports.Resize{Size: sz})} | |
| 306 | 312 | select { | |
| 307 | 313 | case out <- frame: | |
| 308 | 314 | case <-ctx.Done(): | |
| @@ -319,11 +325,11 @@ func runResize(ctx context.Context, events <-chan domain.Size, out chan<- ports. | |||
| 319 | 325 | // the user learns why the session went away. | |
| 320 | 326 | func detachedResult(reason uint8) error { | |
| 321 | 327 | switch reason { | |
| 322 | - | case ipc.ReasonDetach: | |
| 328 | + | case ports.ReasonDetach: | |
| 323 | 329 | return nil | |
| 324 | - | case ipc.ReasonSessionKilled: | |
| 330 | + | case ports.ReasonSessionKilled: | |
| 325 | 331 | return &DetachedError{Reason: reason, Text: "session was killed"} | |
| 326 | - | case ipc.ReasonServerShutdown: | |
| 332 | + | case ports.ReasonServerShutdown: | |
| 327 | 333 | return &DetachedError{Reason: reason, Text: "daemon shut down"} | |
| 328 | 334 | default: | |
| 329 | 335 | return &DetachedError{Reason: reason, Text: "detached by daemon"} | |
| @@ -2,9 +2,9 @@ | |||||
| 2 | // IPC protocol to a daemon, pumping terminal input, resize events, and PTY | 2 | // IPC protocol to a daemon, pumping terminal input, resize events, and PTY | ||
| 3 | // output between the controlling terminal and the session. | 3 | // output between the controlling terminal and the session. | ||
| 4 | // | 4 | // | ||
| 5 | - | // The package depends only on ports (transport, terminal) plus the ipc | 5 | + | // The package depends only on ports (transport, terminal, wire codecs) and |
| 6 | - | // message codecs, never on a concrete transport or terminal implementation; | 6 | + | // domain, never on a concrete transport or terminal implementation; the app |
| 7 | - | // the app layer wires those in. | 7 | + | // layer wires those in. |
| 8 | package client | 8 | package client | ||
| 9 | 9 | ||||
| 10 | import ( | 10 | import ( | ||
| @@ -15,7 +15,6 @@ import ( | |||||
| 15 | "log/slog" | 15 | "log/slog" | ||
| 16 | "os" | 16 | "os" | ||
| 17 | 17 | ||||
| 18 | - | "github.com/bnema/vev/internal/adapters/ipc" | |||
| 19 | "github.com/bnema/vev/internal/domain" | 18 | "github.com/bnema/vev/internal/domain" | ||
| 20 | "github.com/bnema/vev/internal/ports" | 19 | "github.com/bnema/vev/internal/ports" | ||
| 21 | ) | 20 | ) | ||
| @@ -112,14 +111,14 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||||
| 112 | if err != nil { | 111 | if err != nil { | ||
| 113 | return fmt.Errorf("vev: reading terminal size: %w", err) | 112 | return fmt.Errorf("vev: reading terminal size: %w", err) | ||
| 114 | } | 113 | } | ||
| 115 | - | hello := ipc.Hello{ | 114 | + | hello := ports.Hello{ |
| 116 | Version: ports.ProtocolVersion, | 115 | Version: ports.ProtocolVersion, | ||
| 117 | Intent: intent, | 116 | Intent: intent, | ||
| 118 | Name: name, | 117 | Name: name, | ||
| 119 | Size: size, | 118 | Size: size, | ||
| 120 | TermEnv: os.Getenv("TERM"), | 119 | TermEnv: os.Getenv("TERM"), | ||
| 121 | } | 120 | } | ||
| 122 | - | if err := transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ipc.MarshalHello(hello)}); err != nil { | 121 | + | if err := transport.Send(ports.Frame{Type: ports.MsgHello, Payload: ports.MarshalHello(hello)}); err != nil { |
| 123 | return fmt.Errorf("vev: sending hello: %w", err) | 122 | return fmt.Errorf("vev: sending hello: %w", err) | ||
| 124 | } | 123 | } | ||
| 125 | ms.helloSent = true | 124 | ms.helloSent = true | ||
| @@ -133,7 +132,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||||
| 133 | case ports.MsgWelcome: | 132 | case ports.MsgWelcome: | ||
| 134 | ms.welcomed = true | 133 | ms.welcomed = true | ||
| 135 | case ports.MsgError: | 134 | case ports.MsgError: | ||
| 136 | - | em, derr := ipc.UnmarshalErrorMsg(reply.Payload) | 135 | + | em, derr := ports.UnmarshalErrorMsg(reply.Payload) |
| 137 | if derr != nil { | 136 | if derr != nil { | ||
| 138 | return fmt.Errorf("vev: decoding error reply: %w", derr) | 137 | return fmt.Errorf("vev: decoding error reply: %w", derr) | ||
| 139 | } | 138 | } | ||
| @@ -191,7 +190,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||||
| 191 | } | 190 | } | ||
| 192 | switch r.frame.Type { | 191 | switch r.frame.Type { | ||
| 193 | case ports.MsgOutput: | 192 | case ports.MsgOutput: | ||
| 194 | - | o, derr := ipc.UnmarshalOutput(r.frame.Payload) | 193 | + | o, derr := ports.UnmarshalOutput(r.frame.Payload) |
| 195 | if derr != nil { | 194 | if derr != nil { | ||
| 196 | return fmt.Errorf("vev: decoding output: %w", derr) | 195 | return fmt.Errorf("vev: decoding output: %w", derr) | ||
| 197 | } | 196 | } | ||
| @@ -203,7 +202,7 @@ func Attach(ctx context.Context, transport ports.Transport, term ports.Terminal, | |||||
| 203 | } | 202 | } | ||
| 204 | ms.firstOutput = true | 203 | ms.firstOutput = true | ||
| 205 | case ports.MsgDetached: | 204 | case ports.MsgDetached: | ||
| 206 | - | d, derr := ipc.UnmarshalDetached(r.frame.Payload) | 205 | + | d, derr := ports.UnmarshalDetached(r.frame.Payload) |
| 207 | if derr != nil { | 206 | if derr != nil { | ||
| 208 | return fmt.Errorf("vev: decoding detached: %w", derr) | 207 | return fmt.Errorf("vev: decoding detached: %w", derr) | ||
| 209 | } | 208 | } | ||
| @@ -266,6 +265,13 @@ func runSender(ctx context.Context, cancel context.CancelFunc, transport ports.T | |||||
| 266 | // runStdin pumps terminal input to the daemon as Input frames. A read error | 265 | // runStdin pumps terminal input to the daemon as Input frames. A read error | ||
| 267 | // or EOF is treated as a detach: it best-effort sends a Detach frame and | 266 | // or EOF is treated as a detach: it best-effort sends a Detach frame and | ||
| 268 | // cancels the loop. | 267 | // cancels the loop. | ||
| 268 | + | // | |||
| 269 | + | // Known MVP limitation: a bare io.Reader cannot be unblocked from outside, | |||
| 270 | + | // so on shutdown initiated elsewhere (daemon detach, transport loss) this | |||
| 271 | + | // goroutine stays parked in Read until the next byte arrives or the process | |||
| 272 | + | // exits. That is harmless here — Attach has already returned and restored | |||
| 273 | + | // the terminal — and matches the standard pattern for stdin pumps; a | |||
| 274 | + | // closable stdin duplicate could lift it later if ever needed. | |||
| 269 | func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out chan<- ports.Frame) { | 275 | func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out chan<- ports.Frame) { | ||
| 270 | buf := make([]byte, stdinBufSize) | 276 | buf := make([]byte, stdinBufSize) | ||
| 271 | for { | 277 | for { | ||
| @@ -273,7 +279,7 @@ func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out | |||||
| 273 | if n > 0 { | 279 | if n > 0 { | ||
| 274 | data := make([]byte, n) | 280 | data := make([]byte, n) | ||
| 275 | copy(data, buf[:n]) | 281 | copy(data, buf[:n]) | ||
| 276 | - | frame := ports.Frame{Type: ports.MsgInput, Payload: ipc.MarshalInput(ipc.Input{Data: data})} | 282 | + | frame := ports.Frame{Type: ports.MsgInput, Payload: ports.MarshalInput(ports.Input{Data: data})} |
| 277 | select { | 283 | select { | ||
| 278 | case out <- frame: | 284 | case out <- frame: | ||
| 279 | case <-ctx.Done(): | 285 | case <-ctx.Done(): | ||
| @@ -283,7 +289,7 @@ func runStdin(ctx context.Context, cancel context.CancelFunc, in io.Reader, out | |||||
| 283 | if rerr != nil { | 289 | if rerr != nil { | ||
| 284 | // Best-effort detach notification, then unwind. | 290 | // Best-effort detach notification, then unwind. | ||
| 285 | select { | 291 | select { | ||
| 286 | - | case out <- ports.Frame{Type: ports.MsgDetach, Payload: ipc.MarshalDetach(ipc.Detach{})}: | 292 | + | case out <- ports.Frame{Type: ports.MsgDetach, Payload: ports.MarshalDetach(ports.Detach{})}: |
| 287 | case <-ctx.Done(): | 293 | case <-ctx.Done(): | ||
| 288 | } | 294 | } | ||
| 289 | cancel() | 295 | cancel() | ||
| @@ -302,7 +308,7 @@ func runResize(ctx context.Context, events <-chan domain.Size, out chan<- ports. | |||||
| 302 | if !ok { | 308 | if !ok { | ||
| 303 | return | 309 | return | ||
| 304 | } | 310 | } | ||
| 305 | - | frame := ports.Frame{Type: ports.MsgResize, Payload: ipc.MarshalResize(ipc.Resize{Size: sz})} | 311 | + | frame := ports.Frame{Type: ports.MsgResize, Payload: ports.MarshalResize(ports.Resize{Size: sz})} |
| 306 | select { | 312 | select { | ||
| 307 | case out <- frame: | 313 | case out <- frame: | ||
| 308 | case <-ctx.Done(): | 314 | case <-ctx.Done(): | ||
| @@ -319,11 +325,11 @@ func runResize(ctx context.Context, events <-chan domain.Size, out chan<- ports. | |||||
| 319 | // the user learns why the session went away. | 325 | // the user learns why the session went away. | ||
| 320 | func detachedResult(reason uint8) error { | 326 | func detachedResult(reason uint8) error { | ||
| 321 | switch reason { | 327 | switch reason { | ||
| 322 | - | case ipc.ReasonDetach: | 328 | + | case ports.ReasonDetach: |
| 323 | return nil | 329 | return nil | ||
| 324 | - | case ipc.ReasonSessionKilled: | 330 | + | case ports.ReasonSessionKilled: |
| 325 | return &DetachedError{Reason: reason, Text: "session was killed"} | 331 | return &DetachedError{Reason: reason, Text: "session was killed"} | ||
| 326 | - | case ipc.ReasonServerShutdown: | 332 | + | case ports.ReasonServerShutdown: |
| 327 | return &DetachedError{Reason: reason, Text: "daemon shut down"} | 333 | return &DetachedError{Reason: reason, Text: "daemon shut down"} | ||
| 328 | default: | 334 | default: | ||
| 329 | return &DetachedError{Reason: reason, Text: "detached by daemon"} | 335 | return &DetachedError{Reason: reason, Text: "detached by daemon"} | ||
M internal/usecase/client/client_test.go +16 -17
| @@ -13,7 +13,6 @@ import ( | |||
| 13 | 13 | "github.com/stretchr/testify/mock" | |
| 14 | 14 | "github.com/stretchr/testify/require" | |
| 15 | 15 | ||
| 16 | - | "github.com/bnema/vev/internal/adapters/ipc" | |
| 17 | 16 | "github.com/bnema/vev/internal/domain" | |
| 18 | 17 | "github.com/bnema/vev/internal/ports" | |
| 19 | 18 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | |
| @@ -96,14 +95,14 @@ func TestAttachHappyPath(t *testing.T) { | |||
| 96 | 95 | tr := portsmocks.NewMockTransport(t) | |
| 97 | 96 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | |
| 98 | 97 | unblock := scriptRecv(tr, | |
| 99 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1", SessionName: "main"}))}, | |
| 100 | - | recvItem{f: frameOf(ports.MsgOutput, ipc.MarshalOutput(ipc.Output{Data: []byte("hello world")}))}, | |
| 101 | - | recvItem{f: frameOf(ports.MsgDetached, ipc.MarshalDetached(ipc.Detached{Reason: ipc.ReasonDetach}))}, | |
| 98 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1", SessionName: "main"}))}, | |
| 99 | + | recvItem{f: frameOf(ports.MsgOutput, ports.MarshalOutput(ports.Output{Data: []byte("hello world")}))}, | |
| 100 | + | recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(ports.Detached{Reason: ports.ReasonDetach}))}, | |
| 102 | 101 | ) | |
| 103 | 102 | defer unblock() | |
| 104 | 103 | tr.EXPECT().Close().Return(nil).Once() | |
| 105 | 104 | ||
| 106 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | |
| 105 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") | |
| 107 | 106 | require.NoError(t, err) | |
| 108 | 107 | require.Equal(t, "hello world", out.String()) | |
| 109 | 108 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run exactly once") | |
| @@ -117,16 +116,16 @@ func TestAttachVersionMismatch(t *testing.T) { | |||
| 117 | 116 | tr := portsmocks.NewMockTransport(t) | |
| 118 | 117 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | |
| 119 | 118 | tr.EXPECT().Recv().Return( | |
| 120 | - | frameOf(ports.MsgError, ipc.MarshalErrorMsg(ipc.ErrorMsg{Code: ipc.ErrVersionMismatch, Text: "version mismatch"})), | |
| 119 | + | frameOf(ports.MsgError, ports.MarshalErrorMsg(ports.ErrorMsg{Code: ports.ErrVersionMismatch, Text: "version mismatch"})), | |
| 121 | 120 | nil, | |
| 122 | 121 | ).Once() | |
| 123 | 122 | tr.EXPECT().Close().Return(nil).Once() | |
| 124 | 123 | ||
| 125 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentAttach, "main") | |
| 124 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentAttach, "main") | |
| 126 | 125 | require.Error(t, err) | |
| 127 | 126 | var pe *client.ProtocolError | |
| 128 | 127 | require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err) | |
| 129 | - | require.Equal(t, ipc.ErrVersionMismatch, pe.Code) | |
| 128 | + | require.Equal(t, ports.ErrVersionMismatch, pe.Code) | |
| 130 | 129 | } | |
| 131 | 130 | ||
| 132 | 131 | func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | |
| @@ -140,13 +139,13 @@ func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | |||
| 140 | 139 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | |
| 141 | 140 | boom := errors.New("connection reset") | |
| 142 | 141 | unblock := scriptRecv(tr, | |
| 143 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"}))}, | |
| 142 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"}))}, | |
| 144 | 143 | recvItem{err: boom}, | |
| 145 | 144 | ) | |
| 146 | 145 | defer unblock() | |
| 147 | 146 | tr.EXPECT().Close().Return(nil).Once() | |
| 148 | 147 | ||
| 149 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | |
| 148 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") | |
| 150 | 149 | require.Error(t, err) | |
| 151 | 150 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run after mid-stream Recv error") | |
| 152 | 151 | } | |
| @@ -161,13 +160,13 @@ func TestAttachDaemonVanishedOnEOF(t *testing.T) { | |||
| 161 | 160 | tr := portsmocks.NewMockTransport(t) | |
| 162 | 161 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | |
| 163 | 162 | unblock := scriptRecv(tr, | |
| 164 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"}))}, | |
| 163 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"}))}, | |
| 165 | 164 | recvItem{err: io.EOF}, | |
| 166 | 165 | ) | |
| 167 | 166 | defer unblock() | |
| 168 | 167 | tr.EXPECT().Close().Return(nil).Once() | |
| 169 | 168 | ||
| 170 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | |
| 169 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") | |
| 171 | 170 | require.Error(t, err) | |
| 172 | 171 | require.ErrorIs(t, err, io.EOF) | |
| 173 | 172 | require.Equal(t, int32(1), restoreCount.Load()) | |
| @@ -185,16 +184,16 @@ func TestAttachForwardsResize(t *testing.T) { | |||
| 185 | 184 | tr := portsmocks.NewMockTransport(t) | |
| 186 | 185 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | |
| 187 | 186 | // The resize frame is forwarded via the sender goroutine. | |
| 188 | - | gotResize := make(chan ipc.Resize, 1) | |
| 187 | + | gotResize := make(chan ports.Resize, 1) | |
| 189 | 188 | tr.EXPECT().Send(isType(ports.MsgResize)).RunAndReturn(func(f ports.Frame) error { | |
| 190 | - | r, _ := ipc.UnmarshalResize(f.Payload) | |
| 189 | + | r, _ := ports.UnmarshalResize(f.Payload) | |
| 191 | 190 | gotResize <- r | |
| 192 | 191 | close(detachAfterResize) | |
| 193 | 192 | return nil | |
| 194 | 193 | }).Once() | |
| 195 | 194 | ||
| 196 | - | welcome := frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"})) | |
| 197 | - | detached := frameOf(ports.MsgDetached, ipc.MarshalDetached(ipc.Detached{Reason: ipc.ReasonDetach})) | |
| 195 | + | welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"})) | |
| 196 | + | detached := frameOf(ports.MsgDetached, ports.MarshalDetached(ports.Detached{Reason: ports.ReasonDetach})) | |
| 198 | 197 | recvCh := make(chan recvItem, 2) | |
| 199 | 198 | recvCh <- recvItem{f: welcome} | |
| 200 | 199 | done := make(chan struct{}) | |
| @@ -223,7 +222,7 @@ func TestAttachForwardsResize(t *testing.T) { | |||
| 223 | 222 | resizeCh <- domain.Size{Cols: 120, Rows: 40} | |
| 224 | 223 | }() | |
| 225 | 224 | ||
| 226 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | |
| 225 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") | |
| 227 | 226 | require.NoError(t, err) | |
| 228 | 227 | select { | |
| 229 | 228 | case r := <-gotResize: | |
| @@ -13,7 +13,6 @@ import ( | |||||
| 13 | "github.com/stretchr/testify/mock" | 13 | "github.com/stretchr/testify/mock" | ||
| 14 | "github.com/stretchr/testify/require" | 14 | "github.com/stretchr/testify/require" | ||
| 15 | 15 | ||||
| 16 | - | "github.com/bnema/vev/internal/adapters/ipc" | |||
| 17 | "github.com/bnema/vev/internal/domain" | 16 | "github.com/bnema/vev/internal/domain" | ||
| 18 | "github.com/bnema/vev/internal/ports" | 17 | "github.com/bnema/vev/internal/ports" | ||
| 19 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | 18 | portsmocks "github.com/bnema/vev/internal/ports/mocks" | ||
| @@ -96,14 +95,14 @@ func TestAttachHappyPath(t *testing.T) { | |||||
| 96 | tr := portsmocks.NewMockTransport(t) | 95 | tr := portsmocks.NewMockTransport(t) | ||
| 97 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | 96 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | ||
| 98 | unblock := scriptRecv(tr, | 97 | unblock := scriptRecv(tr, | ||
| 99 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1", SessionName: "main"}))}, | 98 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1", SessionName: "main"}))}, |
| 100 | - | recvItem{f: frameOf(ports.MsgOutput, ipc.MarshalOutput(ipc.Output{Data: []byte("hello world")}))}, | 99 | + | recvItem{f: frameOf(ports.MsgOutput, ports.MarshalOutput(ports.Output{Data: []byte("hello world")}))}, |
| 101 | - | recvItem{f: frameOf(ports.MsgDetached, ipc.MarshalDetached(ipc.Detached{Reason: ipc.ReasonDetach}))}, | 100 | + | recvItem{f: frameOf(ports.MsgDetached, ports.MarshalDetached(ports.Detached{Reason: ports.ReasonDetach}))}, |
| 102 | ) | 101 | ) | ||
| 103 | defer unblock() | 102 | defer unblock() | ||
| 104 | tr.EXPECT().Close().Return(nil).Once() | 103 | tr.EXPECT().Close().Return(nil).Once() | ||
| 105 | 104 | ||||
| 106 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | 105 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") |
| 107 | require.NoError(t, err) | 106 | require.NoError(t, err) | ||
| 108 | require.Equal(t, "hello world", out.String()) | 107 | require.Equal(t, "hello world", out.String()) | ||
| 109 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run exactly once") | 108 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run exactly once") | ||
| @@ -117,16 +116,16 @@ func TestAttachVersionMismatch(t *testing.T) { | |||||
| 117 | tr := portsmocks.NewMockTransport(t) | 116 | tr := portsmocks.NewMockTransport(t) | ||
| 118 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | 117 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | ||
| 119 | tr.EXPECT().Recv().Return( | 118 | tr.EXPECT().Recv().Return( | ||
| 120 | - | frameOf(ports.MsgError, ipc.MarshalErrorMsg(ipc.ErrorMsg{Code: ipc.ErrVersionMismatch, Text: "version mismatch"})), | 119 | + | frameOf(ports.MsgError, ports.MarshalErrorMsg(ports.ErrorMsg{Code: ports.ErrVersionMismatch, Text: "version mismatch"})), |
| 121 | nil, | 120 | nil, | ||
| 122 | ).Once() | 121 | ).Once() | ||
| 123 | tr.EXPECT().Close().Return(nil).Once() | 122 | tr.EXPECT().Close().Return(nil).Once() | ||
| 124 | 123 | ||||
| 125 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentAttach, "main") | 124 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentAttach, "main") |
| 126 | require.Error(t, err) | 125 | require.Error(t, err) | ||
| 127 | var pe *client.ProtocolError | 126 | var pe *client.ProtocolError | ||
| 128 | require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err) | 127 | require.True(t, errors.As(err, &pe), "want *client.ProtocolError, got %T", err) | ||
| 129 | - | require.Equal(t, ipc.ErrVersionMismatch, pe.Code) | 128 | + | require.Equal(t, ports.ErrVersionMismatch, pe.Code) |
| 130 | } | 129 | } | ||
| 131 | 130 | ||||
| 132 | func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | 131 | func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | ||
| @@ -140,13 +139,13 @@ func TestAttachRestoredOnRecvErrorMidStream(t *testing.T) { | |||||
| 140 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | 139 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | ||
| 141 | boom := errors.New("connection reset") | 140 | boom := errors.New("connection reset") | ||
| 142 | unblock := scriptRecv(tr, | 141 | unblock := scriptRecv(tr, | ||
| 143 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"}))}, | 142 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"}))}, |
| 144 | recvItem{err: boom}, | 143 | recvItem{err: boom}, | ||
| 145 | ) | 144 | ) | ||
| 146 | defer unblock() | 145 | defer unblock() | ||
| 147 | tr.EXPECT().Close().Return(nil).Once() | 146 | tr.EXPECT().Close().Return(nil).Once() | ||
| 148 | 147 | ||||
| 149 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | 148 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") |
| 150 | require.Error(t, err) | 149 | require.Error(t, err) | ||
| 151 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run after mid-stream Recv error") | 150 | require.Equal(t, int32(1), restoreCount.Load(), "restore must run after mid-stream Recv error") | ||
| 152 | } | 151 | } | ||
| @@ -161,13 +160,13 @@ func TestAttachDaemonVanishedOnEOF(t *testing.T) { | |||||
| 161 | tr := portsmocks.NewMockTransport(t) | 160 | tr := portsmocks.NewMockTransport(t) | ||
| 162 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | 161 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | ||
| 163 | unblock := scriptRecv(tr, | 162 | unblock := scriptRecv(tr, | ||
| 164 | - | recvItem{f: frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"}))}, | 163 | + | recvItem{f: frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"}))}, |
| 165 | recvItem{err: io.EOF}, | 164 | recvItem{err: io.EOF}, | ||
| 166 | ) | 165 | ) | ||
| 167 | defer unblock() | 166 | defer unblock() | ||
| 168 | tr.EXPECT().Close().Return(nil).Once() | 167 | tr.EXPECT().Close().Return(nil).Once() | ||
| 169 | 168 | ||||
| 170 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | 169 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") |
| 171 | require.Error(t, err) | 170 | require.Error(t, err) | ||
| 172 | require.ErrorIs(t, err, io.EOF) | 171 | require.ErrorIs(t, err, io.EOF) | ||
| 173 | require.Equal(t, int32(1), restoreCount.Load()) | 172 | require.Equal(t, int32(1), restoreCount.Load()) | ||
| @@ -185,16 +184,16 @@ func TestAttachForwardsResize(t *testing.T) { | |||||
| 185 | tr := portsmocks.NewMockTransport(t) | 184 | tr := portsmocks.NewMockTransport(t) | ||
| 186 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | 185 | tr.EXPECT().Send(isType(ports.MsgHello)).Return(nil).Once() | ||
| 187 | // The resize frame is forwarded via the sender goroutine. | 186 | // The resize frame is forwarded via the sender goroutine. | ||
| 188 | - | gotResize := make(chan ipc.Resize, 1) | 187 | + | gotResize := make(chan ports.Resize, 1) |
| 189 | tr.EXPECT().Send(isType(ports.MsgResize)).RunAndReturn(func(f ports.Frame) error { | 188 | tr.EXPECT().Send(isType(ports.MsgResize)).RunAndReturn(func(f ports.Frame) error { | ||
| 190 | - | r, _ := ipc.UnmarshalResize(f.Payload) | 189 | + | r, _ := ports.UnmarshalResize(f.Payload) |
| 191 | gotResize <- r | 190 | gotResize <- r | ||
| 192 | close(detachAfterResize) | 191 | close(detachAfterResize) | ||
| 193 | return nil | 192 | return nil | ||
| 194 | }).Once() | 193 | }).Once() | ||
| 195 | 194 | ||||
| 196 | - | welcome := frameOf(ports.MsgWelcome, ipc.MarshalWelcome(ipc.Welcome{SessionID: "s1"})) | 195 | + | welcome := frameOf(ports.MsgWelcome, ports.MarshalWelcome(ports.Welcome{SessionID: "s1"})) |
| 197 | - | detached := frameOf(ports.MsgDetached, ipc.MarshalDetached(ipc.Detached{Reason: ipc.ReasonDetach})) | 196 | + | detached := frameOf(ports.MsgDetached, ports.MarshalDetached(ports.Detached{Reason: ports.ReasonDetach})) |
| 198 | recvCh := make(chan recvItem, 2) | 197 | recvCh := make(chan recvItem, 2) | ||
| 199 | recvCh <- recvItem{f: welcome} | 198 | recvCh <- recvItem{f: welcome} | ||
| 200 | done := make(chan struct{}) | 199 | done := make(chan struct{}) | ||
| @@ -223,7 +222,7 @@ func TestAttachForwardsResize(t *testing.T) { | |||||
| 223 | resizeCh <- domain.Size{Cols: 120, Rows: 40} | 222 | resizeCh <- domain.Size{Cols: 120, Rows: 40} | ||
| 224 | }() | 223 | }() | ||
| 225 | 224 | ||||
| 226 | - | err := client.Attach(context.Background(), tr, tm, ipc.IntentEphemeral, "") | 225 | + | err := client.Attach(context.Background(), tr, tm, ports.IntentEphemeral, "") |
| 227 | require.NoError(t, err) | 226 | require.NoError(t, err) | ||
| 228 | select { | 227 | select { | ||
| 229 | case r := <-gotResize: | 228 | case r := <-gotResize: | ||