Skip to content

Commit 2c055b5

Browse files
authored
new resolver formats (tab:N), and also make the structure of the resolvers much more robust (#1254)
1 parent 58f2f4a commit 2c055b5

10 files changed

Lines changed: 226 additions & 123 deletions

File tree

cmd/wsh/cmd/wshcmd-deleteblock.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,13 @@ func init() {
2121

2222
func deleteBlockRun(cmd *cobra.Command, args []string) {
2323
oref := blockArg
24-
err := validateEasyORef(oref)
25-
if err != nil {
26-
WriteStderr("[error]%v\n", err)
27-
return
28-
}
2924
fullORef, err := resolveSimpleId(oref)
3025
if err != nil {
31-
WriteStderr("[error] resolving oref: %v\n", err)
26+
WriteStderr("[error] %v\n", err)
3227
return
3328
}
3429
if fullORef.OType != "block" {
35-
WriteStderr("[error] oref is not a block\n")
30+
WriteStderr("[error] object reference is not a block\n")
3631
return
3732
}
3833
deleteBlockData := &wshrpc.CommandDeleteBlockData{

cmd/wsh/cmd/wshcmd-getmeta.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,9 @@ func getMetaRun(cmd *cobra.Command, args []string) {
7474
WriteStderr("[error] oref is required")
7575
return
7676
}
77-
err := validateEasyORef(oref)
78-
if err != nil {
79-
WriteStderr("[error] %v\n", err)
80-
return
81-
}
8277
fullORef, err := resolveSimpleId(oref)
8378
if err != nil {
84-
WriteStderr("[error] resolving oref: %v\n", err)
79+
WriteStderr("[error] %v\n", err)
8580
return
8681
}
8782
resp, err := wshclient.GetMetaCommand(RpcClient, wshrpc.CommandGetMetaData{ORef: *fullORef}, &wshrpc.RpcOpts{Timeout: 2000})

cmd/wsh/cmd/wshcmd-readfile.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,9 @@ func runReadFile(cmd *cobra.Command, args []string) {
2929
WriteStderr("[error] oref is required\n")
3030
return
3131
}
32-
err := validateEasyORef(oref)
33-
if err != nil {
34-
WriteStderr("[error] %v\n", err)
35-
return
36-
}
3732
fullORef, err := resolveSimpleId(oref)
3833
if err != nil {
39-
WriteStderr("error resolving oref: %v\n", err)
34+
WriteStderr("[error] %v\n", err)
4035
return
4136
}
4237
resp64, err := wshclient.FileReadCommand(RpcClient, wshrpc.CommandFileData{ZoneId: fullORef.OID, FileName: args[1]}, &wshrpc.RpcOpts{Timeout: 5000})

cmd/wsh/cmd/wshcmd-root.go

Lines changed: 0 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,9 @@ import (
99
"os"
1010
"regexp"
1111
"runtime/debug"
12-
"strconv"
1312
"strings"
1413
"time"
1514

16-
"github.com/google/uuid"
1715
"github.com/spf13/cobra"
1816
"github.com/wavetermdev/waveterm/pkg/waveobj"
1917
"github.com/wavetermdev/waveterm/pkg/wshrpc"
@@ -76,10 +74,6 @@ func resolveBlockArg() (*waveobj.ORef, error) {
7674
if oref == "" {
7775
return nil, fmt.Errorf("blockid is required")
7876
}
79-
err := validateEasyORef(oref)
80-
if err != nil {
81-
return nil, err
82-
}
8377
fullORef, err := resolveSimpleId(oref)
8478
if err != nil {
8579
return nil, fmt.Errorf("resolving blockid: %w", err)
@@ -128,33 +122,6 @@ func setTermHtmlMode() {
128122

129123
var oidRe = regexp.MustCompile(`^[0-9a-f]{8}$`)
130124

131-
func validateEasyORef(oref string) error {
132-
if oref == "this" || oref == "tab" {
133-
return nil
134-
}
135-
if num, err := strconv.Atoi(oref); err == nil && num >= 1 {
136-
return nil
137-
}
138-
if strings.Contains(oref, ":") {
139-
_, err := waveobj.ParseORef(oref)
140-
if err != nil {
141-
return fmt.Errorf("invalid ORef: %v", err)
142-
}
143-
return nil
144-
}
145-
if len(oref) == 8 {
146-
if !oidRe.MatchString(oref) {
147-
return fmt.Errorf("invalid short OID format, must only use 0-9a-f: %q", oref)
148-
}
149-
return nil
150-
}
151-
_, err := uuid.Parse(oref)
152-
if err != nil {
153-
return fmt.Errorf("invalid object reference (must be UUID, or a positive integer): %v", err)
154-
}
155-
return nil
156-
}
157-
158125
func isFullORef(orefStr string) bool {
159126
_, err := waveobj.ParseORef(orefStr)
160127
return err == nil

cmd/wsh/cmd/wshcmd-setmeta.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,14 +112,9 @@ func setMetaRun(cmd *cobra.Command, args []string) {
112112
WriteStderr("[error] block (oref) is required\n")
113113
return
114114
}
115-
err := validateEasyORef(blockArg)
116-
if err != nil {
117-
WriteStderr("[error] %v\n", err)
118-
return
119-
}
120-
121115
var jsonMeta map[string]interface{}
122116
if setMetaJsonFilePath != "" {
117+
var err error
123118
jsonMeta, err = loadJSONFile(setMetaJsonFilePath)
124119
if err != nil {
125120
WriteStderr("[error] %v\n", err)
@@ -146,7 +141,7 @@ func setMetaRun(cmd *cobra.Command, args []string) {
146141
}
147142
fullORef, err := resolveSimpleId(blockArg)
148143
if err != nil {
149-
WriteStderr("[error] resolving oref: %v\n", err)
144+
WriteStderr("[error] %v\n", err)
150145
return
151146
}
152147

cmd/wsh/cmd/wshcmd-web.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,6 @@ func webGetRun(cmd *cobra.Command, args []string) error {
5555
if oref == "" {
5656
return fmt.Errorf("blockid not specified")
5757
}
58-
err := validateEasyORef(oref)
59-
if err != nil {
60-
return err
61-
}
6258
fullORef, err := resolveSimpleId(oref)
6359
if err != nil {
6460
return fmt.Errorf("resolving blockid: %w", err)

pkg/waveobj/waveobj.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ func ParseORef(orefStr string) (ORef, error) {
8686
if !otypeRe.MatchString(otype) {
8787
return ORef{}, fmt.Errorf("invalid object type: %q", otype)
8888
}
89+
if !ValidOTypes[otype] {
90+
return ORef{}, fmt.Errorf("unknown object type: %q", otype)
91+
}
8992
oid := fields[1]
9093
_, err := uuid.Parse(oid)
9194
if err != nil {

pkg/waveobj/wtype.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,15 @@ const (
3030
OType_Block = "block"
3131
)
3232

33+
var ValidOTypes = map[string]bool{
34+
OType_Client: true,
35+
OType_Window: true,
36+
OType_Workspace: true,
37+
OType_Tab: true,
38+
OType_LayoutState: true,
39+
OType_Block: true,
40+
}
41+
3342
type WaveObjUpdate struct {
3443
UpdateType string `json:"updatetype"`
3544
OType string `json:"otype"`

pkg/wshrpc/wshserver/resolvers.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// Copyright 2024, Command Line Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package wshserver
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"regexp"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/google/uuid"
14+
"github.com/wavetermdev/waveterm/pkg/waveobj"
15+
"github.com/wavetermdev/waveterm/pkg/wshrpc"
16+
"github.com/wavetermdev/waveterm/pkg/wstore"
17+
)
18+
19+
const SimpleId_This = "this"
20+
const SimpleId_Tab = "tab"
21+
22+
var (
23+
simpleTabNumRe = regexp.MustCompile(`^tab:(\d{1,3})$`)
24+
shortUUIDRe = regexp.MustCompile(`^[0-9a-f]{8}$`)
25+
SimpleId_BlockNum_Regex = regexp.MustCompile(`^\d+$`)
26+
)
27+
28+
// Helper function to validate UUIDs or 8-char UUIDs format
29+
func isValidSimpleUUID(s string) bool {
30+
// Try parsing as full UUID
31+
_, err := uuid.Parse(s)
32+
if err == nil {
33+
return true
34+
}
35+
36+
// Check if it's an 8-char hex prefix
37+
shortUUIDPattern := regexp.MustCompile(`^[0-9a-f]{8}$`)
38+
return shortUUIDPattern.MatchString(strings.ToLower(s))
39+
}
40+
41+
// First function: detect/choose discriminator
42+
func parseSimpleId(simpleId string) (discriminator string, value string, err error) {
43+
// Check for explicit discriminator with @
44+
if parts := strings.SplitN(simpleId, "@", 2); len(parts) == 2 {
45+
return parts[0], parts[1], nil
46+
}
47+
48+
// Handle special keywords
49+
if simpleId == SimpleId_This || simpleId == SimpleId_Tab {
50+
return "this", simpleId, nil
51+
}
52+
53+
// Check if it's a simple ORef (type:uuid)
54+
if _, err := waveobj.ParseORef(simpleId); err == nil {
55+
return "oref", simpleId, nil
56+
}
57+
58+
// Check for tab:N format
59+
if simpleTabNumRe.MatchString(simpleId) {
60+
return "tabnum", simpleId, nil
61+
}
62+
63+
// Check for plain number (block reference)
64+
if _, err := strconv.Atoi(simpleId); err == nil {
65+
return "blocknum", simpleId, nil
66+
}
67+
68+
// Check for UUIDs
69+
if _, err := uuid.Parse(simpleId); err == nil {
70+
return "uuid", simpleId, nil
71+
}
72+
if shortUUIDRe.MatchString(strings.ToLower(simpleId)) {
73+
return "uuid8", simpleId, nil
74+
}
75+
76+
return "", "", fmt.Errorf("invalid simple id format: %s", simpleId)
77+
}
78+
79+
// Individual resolvers
80+
func resolveThis(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
81+
if data.BlockId == "" {
82+
return nil, fmt.Errorf("no blockid in request")
83+
}
84+
85+
if value == SimpleId_This {
86+
return &waveobj.ORef{OType: waveobj.OType_Block, OID: data.BlockId}, nil
87+
}
88+
if value == SimpleId_Tab {
89+
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
90+
if err != nil {
91+
return nil, fmt.Errorf("error finding tab: %v", err)
92+
}
93+
return &waveobj.ORef{OType: waveobj.OType_Tab, OID: tabId}, nil
94+
}
95+
return nil, fmt.Errorf("invalid value for 'this' resolver: %s", value)
96+
}
97+
98+
func resolveORef(ctx context.Context, value string) (*waveobj.ORef, error) {
99+
parsedORef, err := waveobj.ParseORef(value)
100+
if err != nil {
101+
return nil, fmt.Errorf("error parsing oref: %v", err)
102+
}
103+
return &parsedORef, nil
104+
}
105+
106+
func resolveTabNum(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
107+
m := simpleTabNumRe.FindStringSubmatch(value)
108+
if m == nil {
109+
return nil, fmt.Errorf("error parsing simple tab id: %s", value)
110+
}
111+
112+
tabNum, err := strconv.Atoi(m[1])
113+
if err != nil {
114+
return nil, fmt.Errorf("error parsing simple tab num: %v", err)
115+
}
116+
117+
curTabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
118+
if err != nil {
119+
return nil, fmt.Errorf("error finding tab for block: %v", err)
120+
}
121+
122+
wsId, err := wstore.DBFindWorkspaceForTabId(ctx, curTabId)
123+
if err != nil {
124+
return nil, fmt.Errorf("error finding current workspace: %v", err)
125+
}
126+
127+
ws, err := wstore.DBMustGet[*waveobj.Workspace](ctx, wsId)
128+
if err != nil {
129+
return nil, fmt.Errorf("error getting workspace: %v", err)
130+
}
131+
132+
if tabNum < 1 || tabNum > len(ws.TabIds) {
133+
return nil, fmt.Errorf("tab num out of range, workspace has %d tabs", len(ws.TabIds))
134+
}
135+
136+
resolvedTabId := ws.TabIds[tabNum-1]
137+
return &waveobj.ORef{OType: waveobj.OType_Tab, OID: resolvedTabId}, nil
138+
}
139+
140+
func resolveBlock(ctx context.Context, data wshrpc.CommandResolveIdsData, value string) (*waveobj.ORef, error) {
141+
blockNum, err := strconv.Atoi(value)
142+
if err != nil {
143+
return nil, fmt.Errorf("error parsing block number: %v", err)
144+
}
145+
146+
tabId, err := wstore.DBFindTabForBlockId(ctx, data.BlockId)
147+
if err != nil {
148+
return nil, fmt.Errorf("error finding tab for blockid %s: %w", data.BlockId, err)
149+
}
150+
151+
tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
152+
if err != nil {
153+
return nil, fmt.Errorf("error retrieving tab %s: %w", tabId, err)
154+
}
155+
156+
layout, err := wstore.DBGet[*waveobj.LayoutState](ctx, tab.LayoutState)
157+
if err != nil {
158+
return nil, fmt.Errorf("error retrieving layout state %s: %w", tab.LayoutState, err)
159+
}
160+
161+
if layout.LeafOrder == nil {
162+
return nil, fmt.Errorf("could not resolve block num %v, leaf order is empty", blockNum)
163+
}
164+
165+
leafIndex := blockNum - 1 // block nums are 1-indexed
166+
if len(*layout.LeafOrder) <= leafIndex {
167+
return nil, fmt.Errorf("could not find a node in the layout matching blockNum %v", blockNum)
168+
}
169+
170+
leafEntry := (*layout.LeafOrder)[leafIndex]
171+
return &waveobj.ORef{OType: waveobj.OType_Block, OID: leafEntry.BlockId}, nil
172+
}
173+
174+
func resolveUUID(ctx context.Context, value string) (*waveobj.ORef, error) {
175+
return wstore.DBResolveEasyOID(ctx, value)
176+
}
177+
178+
// Main resolver function
179+
func resolveSimpleId(ctx context.Context, data wshrpc.CommandResolveIdsData, simpleId string) (*waveobj.ORef, error) {
180+
discriminator, value, err := parseSimpleId(simpleId)
181+
if err != nil {
182+
return nil, err
183+
}
184+
switch discriminator {
185+
case "this":
186+
return resolveThis(ctx, data, value)
187+
case "oref":
188+
return resolveORef(ctx, value)
189+
case "tabnum":
190+
return resolveTabNum(ctx, data, value)
191+
case "blocknum":
192+
return resolveBlock(ctx, data, value)
193+
case "uuid", "uuid8":
194+
return resolveUUID(ctx, value)
195+
default:
196+
return nil, fmt.Errorf("unknown discriminator: %s", discriminator)
197+
}
198+
}

0 commit comments

Comments
 (0)