Skip to content

Commit

Permalink
[path params] interpolate path params in handler data
Browse files Browse the repository at this point in the history
  • Loading branch information
lispyclouds committed Dec 16, 2024
1 parent b8e4cf5 commit 169fadb
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 8 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ What if you can influence the CLI behaviour from the server? This enables you to

### Status

Experimental, in dev flux and looking for design/usage feedback!
Alpha, stabilising API and looking for design/usage feedback!

### TODO:
- Interpolate the HTTP paths when sending to the handler with the path params
- Support more of the OpenAPI types and their checks. eg arrays, enums, objects, multi types etc
- Type checking request bodies
### Ideally support:
- More of the OpenAPI types and their checks. eg arrays, enums, objects, multi types etc
- Type checking request bodies of certain MIME types

### Installation

Expand All @@ -32,7 +31,7 @@ climate allows the server to influence the CLI behaviour by using OpenAPI's [ext
Overall, the way it works:
- Each operation is converted to a Cobra command
- Each parameter is converted to a flag with its corresponding type
- Request bodies are a flag as of now, subject to change. Name defaults to `climate-data` unless specified via `x-cli-name`
- As of now, request bodies are a flag and treated as a string regardless of MIME type. Name defaults to `climate-data` unless specified via `x-cli-name`. All subject to change
- The provided handlers are attached to each command, grouped and attached to the rootCmd

Influenced by some of the ideas behind [restish](https://rest.sh/) it uses the following extensions as of now:
Expand Down
40 changes: 38 additions & 2 deletions lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log/slog"
"os"
"regexp"

"github.com/pb33f/libopenapi"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
Expand Down Expand Up @@ -31,7 +32,7 @@ type ParamMeta struct {
// Data passed into each handler
type HandlerData struct {
Method string // the HTTP method
Path string // the parameterised path. currently non interpolated
Path string // the path with the path params filled in
PathParams []ParamMeta // List of path params
QueryParams []ParamMeta // List of query params
HeaderParams []ParamMeta // List of header params
Expand Down Expand Up @@ -92,6 +93,8 @@ func addParams(cmd *cobra.Command, op *v3.Operation, handlerData *HandlerData) {
t := String
if schema != nil {
t = OpenAPIType(schema.Type[0])
} else {
slog.Warn("No type set for param, defaulting to string", "param", param.Name, "id", op.OperationId)
}

switch t {
Expand Down Expand Up @@ -159,6 +162,36 @@ func addRequestBody(cmd *cobra.Command, op *v3.Operation, handlerData *HandlerDa
return nil
}

func interpolatePath(cmd *cobra.Command, h *HandlerData) error {
for _, param := range h.PathParams {
pattern, err := regexp.Compile(fmt.Sprintf("({%s})+", param.Name))
if err != nil {
return err
}

var value string
flags := cmd.Flags()

switch param.Type {
case String:
value, _ = flags.GetString(param.Name)
case Integer:
v, _ := flags.GetInt(param.Name)
value = fmt.Sprintf("%d", v)
case Number:
v, _ := flags.GetFloat64(param.Name)
value = fmt.Sprintf("%g", v)
case Boolean:
v, _ := flags.GetBool(param.Name)
value = fmt.Sprintf("%t", v)
}

h.Path = pattern.ReplaceAllString(h.Path, value)
}

return nil
}

// Loads and verifies an OpenAPI spec frpm an array of bytes
func LoadV3(data []byte) (*libopenapi.DocumentModel[v3.Document], error) {
document, err := libopenapi.NewDocument(data)
Expand Down Expand Up @@ -219,7 +252,10 @@ func BootstrapV3(rootCmd *cobra.Command, model libopenapi.DocumentModel[v3.Docum
cmd.Short = op.Summary
}
cmd.Run = func(opts *cobra.Command, args []string) {
// TODO: Interpolate path
if err := interpolatePath(&cmd, &hData); err != nil {
slog.Error("Error interpolating path", "err", err)
}

handler(opts, args, hData)
}

Expand Down
24 changes: 24 additions & 0 deletions lib_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ func TestLoadFileV3(t *testing.T) {
}
}

func TestInterpolatePath(t *testing.T) {
cmd := cobra.Command{}
hData := HandlerData{
Method: "get",
Path: "/path/{foo}/to/{bar}/with/{baz}/and/{quxx}/together",
PathParams: []ParamMeta{
{Name: "foo", Type: String},
{Name: "bar", Type: Integer},
{Name: "baz", Type: Number},
{Name: "quxx", Type: Boolean},
},
}

cmd.Flags().String("foo", "yes", "foo usage")
cmd.Flags().Int("bar", 420, "bar usage")
cmd.Flags().Float64("baz", 420.69, "baz usage")
cmd.Flags().Bool("quxx", false, "quxx usage")

err := interpolatePath(&cmd, &hData)
assert.NoError(t, err)

assert.Equal(t, hData.Path, "/path/yes/to/420/with/420.69/and/false/together")
}

func assertCmdTree(t *testing.T, cmd *cobra.Command, assertConf map[string]map[string]any, prefix string) {
fmt.Println("Checking cmd level " + prefix)

Expand Down

0 comments on commit 169fadb

Please sign in to comment.