Skip to content

Commit 0b2bd3e

Browse files
feat(oq): add GCF as --format gcf output option
1 parent 5b7841f commit 0b2bd3e

6 files changed

Lines changed: 59 additions & 5 deletions

File tree

cmd/openapi/commands/openapi/query.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ var queryOutputFormat string
6060
var queryFromFile string
6161

6262
func init() {
63-
queryCmd.Flags().StringVar(&queryOutputFormat, "format", "table", "output format: table, json, markdown, or toon")
63+
queryCmd.Flags().StringVar(&queryOutputFormat, "format", "table", "output format: table, json, markdown, toon, or gcf")
6464
queryCmd.Flags().StringVarP(&queryFromFile, "file", "f", "", "read query from file instead of argument")
6565

6666
// Custom help template: Usage + Flags together, then Examples last
@@ -167,6 +167,8 @@ func queryOpenAPI(ctx context.Context, processor *OpenAPIProcessor, queryStr str
167167
output = oq.FormatMarkdown(result, g)
168168
case "toon":
169169
output = oq.FormatToon(result, g)
170+
case "gcf":
171+
output = oq.FormatGCF(result, g)
170172
default:
171173
output = oq.FormatTable(result, g)
172174
}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/speakeasy-api/openapi
22

3-
go 1.25.0
3+
go 1.26.1
44

55
require (
66
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
@@ -13,6 +13,7 @@ require (
1313
)
1414

1515
require (
16+
github.com/blackwell-systems/gcf-go v1.2.1 // indirect
1617
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1718
github.com/dlclark/regexp2 v1.11.4 // indirect
1819
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/blackwell-systems/gcf-go v1.2.1 h1:NsoGZKQxT8O3WKl+QssiFdvhPM0cCCyJ3a3x1H5fGDI=
2+
github.com/blackwell-systems/gcf-go v1.2.1/go.mod h1:sROSBELq+iEDf3pD1x4AcY2QccFd1KHsGajFzjVlCNc=
13
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
24
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
35
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

oq/format.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"strconv"
66
"strings"
77

8+
gcf "github.com/blackwell-systems/gcf-go"
89
"github.com/speakeasy-api/openapi/graph"
910
"github.com/speakeasy-api/openapi/oq/expr"
1011
"gopkg.in/yaml.v3"
@@ -221,6 +222,54 @@ func FormatToon(result *Result, g *graph.SchemaGraph) string {
221222
return sb.String()
222223
}
223224

225+
// FormatGCF formats a result using GCF (Graph Compact Format).
226+
// GCF uses positional pipe-delimited rows with inline schemas, achieving
227+
// higher LLM comprehension accuracy than TOON or JSON (90.7% vs 68.5% vs 53.6%).
228+
// See https://gcformat.com
229+
func FormatGCF(result *Result, g *graph.SchemaGraph) string {
230+
if result.Explain != "" {
231+
return result.Explain
232+
}
233+
234+
if result.IsCount {
235+
return gcf.EncodeGeneric(map[string]interface{}{"count": result.Count})
236+
}
237+
238+
syncGroupsFromRows(result)
239+
240+
if len(result.Rows) == 0 {
241+
return gcf.EncodeGeneric([]interface{}{})
242+
}
243+
244+
fields := result.Fields
245+
if len(fields) == 0 {
246+
fields = resolveDefaultFields(result.Rows)
247+
}
248+
249+
rows := make([]interface{}, 0, len(result.Rows))
250+
for _, row := range result.Rows {
251+
m := make(map[string]interface{}, len(fields))
252+
for _, f := range fields {
253+
v := fieldValue(row, f, g)
254+
switch v.Kind {
255+
case expr.KindString:
256+
m[f] = v.Str
257+
case expr.KindInt:
258+
m[f] = v.Int
259+
case expr.KindBool:
260+
m[f] = v.Bool
261+
case expr.KindArray:
262+
m[f] = v.Arr
263+
default:
264+
m[f] = nil
265+
}
266+
}
267+
rows = append(rows, m)
268+
}
269+
270+
return gcf.EncodeGeneric(rows)
271+
}
272+
224273
// FormatYAML formats results as raw YAML from the underlying schema/operation objects.
225274
// For multiple results, outputs a YAML stream with --- separators.
226275
// This enables piping into yq for content-level queries.

oq/oq.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ type Result struct {
8585
Count int
8686
Groups []GroupResult
8787
Explain string // human-readable pipeline explanation
88-
FormatHint string // format preference from format stage (table, json, markdown, toon)
88+
FormatHint string // format preference from format stage (table, json, markdown, toon, gcf)
8989
EmitYAML bool // emit raw YAML nodes instead of formatted output
9090
}
9191

oq/parse.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -322,8 +322,8 @@ func parseStage(s string) (Stage, error) {
322322

323323
case "format":
324324
f := strings.TrimSpace(args)
325-
if f != "table" && f != "json" && f != "markdown" && f != "toon" {
326-
return Stage{}, fmt.Errorf("format must be table, json, markdown, or toon, got %q", f)
325+
if f != "table" && f != "json" && f != "markdown" && f != "toon" && f != "gcf" {
326+
return Stage{}, fmt.Errorf("format must be table, json, markdown, toon, or gcf, got %q", f)
327327
}
328328
return Stage{Kind: StageFormat, Format: f}, nil
329329

0 commit comments

Comments
 (0)