session_connection.go RawHistoryFolder
1package client
2
3import (
4 "errors"
5 "fmt"
6
7 "github.com/bnema/vev/internal/domain"
8 "github.com/bnema/vev/internal/ports"
9)
10
11var (
12 errNilSessionTransport = errors.New("client: nil session transport")
13 ErrEphemeralSessionName = errors.New("client: ephemeral sessions cannot have a name")
14)
15
16// SessionTarget is the validated daemon-facing session selection. Endpoint
17// details are resolved before a transport is opened and never enter this type.
18type SessionTarget struct {
19 Intent uint8
20 SessionName string
21}
22
23func (t SessionTarget) validate() error {
24 switch t.Intent {
25 case ports.IntentEphemeral:
26 if t.SessionName != "" {
27 return ErrEphemeralSessionName
28 }
29 return nil
30 case ports.IntentNew, ports.IntentAttach, ports.IntentResume:
31 if t.SessionName == "" {
32 return domain.ErrInvalidSessionName
33 }
34 default:
35 return fmt.Errorf("invalid session intent %d", t.Intent)
36 }
37 return domain.ValidateSessionName(t.SessionName)
38}
39
40// SessionConnection owns one already-open transport and its daemon-facing
41// session target. Local sockets and remote carriages use this same owner.
42type SessionConnection struct {
43 transport ports.Transport
44 target SessionTarget
45}
46
47// NewSessionConnection creates a connection for an already-selected transport.
48// Target validation happens before the connection is used for a Hello.
49func NewSessionConnection(transport ports.Transport, target SessionTarget) (*SessionConnection, error) {
50 if transport == nil {
51 return nil, errNilSessionTransport
52 }
53 if err := target.validate(); err != nil {
54 return nil, fmt.Errorf("invalid session target: %w", err)
55 }
56 return &SessionConnection{transport: transport, target: target}, nil
57}
58
59// Transport returns the owned carriage for this connection.
60func (c *SessionConnection) Transport() ports.Transport {
61 if c == nil {
62 return nil
63 }
64 return c.transport
65}
66
67// AttachRequest returns the common request shape used after transport
68// selection. It contains no remote/proxy semantic.
69func (c *SessionConnection) AttachRequest() AttachRequest {
70 if c == nil {
71 return AttachRequest{}
72 }
73 return AttachRequest{Intent: c.target.Intent, SessionName: c.target.SessionName}
74}
75
76// cloneAttachRequest keeps the immutable request independent from transport
77// ownership while protecting the exact remote target from caller mutation.
78func cloneAttachRequest(request AttachRequest) AttachRequest {
79 if request.RemoteTarget != nil {
80 target := *request.RemoteTarget
81 request.RemoteTarget = &target
82 }
83 if request.ExactTarget != nil {
84 target := *request.ExactTarget
85 request.ExactTarget = &target
86 }
87 return request
88}
89
90// Close releases the owned transport.
91func (c *SessionConnection) Close() error {
92 if c == nil || c.transport == nil {
93 return nil
94 }
95 return c.transport.Close()
96}