diff --git a/Makefile b/Makefile index e2c3c7b..cb7bd7e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ # Variables CLIENT_PKG := ./cmd/eia-client +CLIENT_GEN_FILE := ./api/eiaapi.gen.go +GO_FORMATTER := gofumpt .PHONY: all generate build install clean @@ -9,6 +11,11 @@ all: generate build # Generate code generate: go generate ./... + # Fix errors in generated code + sed -E -i '' 's/Total[[:space:]]+\*int/Total *string/g' $(CLIENT_GEN_FILE) + sed -E -i '' 's/Command[[:space:]]+\*\[\]string/Command *string/g' $(CLIENT_GEN_FILE) + # Pretty it up + $(GO_FORMATTER) -w $(CLIENT_GEN_FILE) # Build the client command binary build: generate @@ -16,7 +23,7 @@ build: generate # Install the client command binary install: generate - go install $(CLIENT_PKG) + go install -v $(CLIENT_PKG) # Clean up generated files and build artifacts clean: diff --git a/cmd/eia-client/cmd/list/list.go b/cmd/eia-client/cmd/list/list.go new file mode 100644 index 0000000..58a727b --- /dev/null +++ b/cmd/eia-client/cmd/list/list.go @@ -0,0 +1,13 @@ +package list + +import "github.com/spf13/cobra" + +var ListCmd = &cobra.Command{ + Use: "list", + Aliases: []string{"ls", "l"}, + Short: "Commands for listing metadata", +} + +func init() { + ListCmd.AddCommand(ListFacetsCmd) +} diff --git a/cmd/eia-client/cmd/list/list_facets.go b/cmd/eia-client/cmd/list/list_facets.go new file mode 100644 index 0000000..1c5ed9b --- /dev/null +++ b/cmd/eia-client/cmd/list/list_facets.go @@ -0,0 +1,21 @@ +package list + +import ( + "github.com/spf13/cobra" + + "gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util" +) + +var ListFacetsCmd = &cobra.Command{ + Use: "facets route", + Args: cobra.ExactArgs(1), + Short: "List facets for given API route", + ValidArgsFunction: util.CompleteRoutes, + PreRun: util.SetClient, + Run: RunListFacetsCmd, +} + +func RunListFacetsCmd(cmd *cobra.Command, args []string) { + resp, err := util.GetFacets(cmd, args[0]) + util.Logger(cmd).Info().Any("resp", resp).Err(err).Send() +} diff --git a/cmd/eia-client/cmd/list/list_series.go b/cmd/eia-client/cmd/list/list_series.go deleted file mode 100644 index 33c9331..0000000 --- a/cmd/eia-client/cmd/list/list_series.go +++ /dev/null @@ -1,80 +0,0 @@ -package list - -import ( - "github.com/spf13/cobra" - "k8s.io/utils/ptr" - - eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api" - "gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util" - "gitea.libretechconsulting.com/50W/eia-api-go/pkg/eia" -) - -var ListCmd = &cobra.Command{ - Use: "list ", - Args: cobra.RangeArgs(0, 1), - Short: "List Available EIA Series", - PreRun: util.SetClient, - Run: runListCmd, -} - -func runListCmd(cmd *cobra.Command, _ []string) { - logger := util.Logger(cmd) - - client, err := util.Client(cmd) - if err != nil { - logger.Fatal().Err(err).Send() - } - - // Facets - ctx, cncl := util.RequestCtx(cmd) - defer cncl() - facets, err := client.GetV2AeoRoute1FacetWithResponse(ctx, "2023") - if err != nil { - logger.Fatal().Err(err).Send() - } - logger.Debug().Any("facets", facets.JSON200).Send() - - // Facet - for _, option := range *facets.JSON200.Response.FacetOptions { - ctx, cncl = util.RequestCtx(cmd) - defer cncl() - facet, err := client.GetV2AeoRoute1FacetFacetIdWithResponse(ctx, "2023", option) - if err != nil { - logger.Err(err).Send() - return - } - logger.Debug().Str("facet", option).Any("options", facet.JSON200.Response.Facets).Send() - } - - ctx, cncl = util.RequestCtx(cmd) - defer cncl() - resp, err := client.GetV2AeoRoute1DataWithResponse(ctx, "2023", &eiaapi.GetV2AeoRoute1DataParams{ - Start: ptr.To("2023"), - End: ptr.To("2023"), - Facets: eia.NewFacets( - &eia.Facet{ - Name: "regionId", - Data: "5-4", - }, - &eia.Facet{ - Name: "seriesId", - Data: "gen_NA_elep_NA_nuc_NA_mcc_blnkwh", - }, - &eia.Facet{ - Name: "seriesId", - Data: "cap_NA_elep_NA_nup_NA_mcc_gw", - }, - &eia.Facet{ - Name: "scenario", - Data: "highmacro", - }, - ), - }) - if err != nil { - logger.Fatal().Err(err).Send() - } - - // body, err := io.ReadAll(resp.Body) - // defer resp.Body.Close() - logger.Debug().Any("body", resp.JSON200).Send() -} diff --git a/cmd/eia-client/cmd/root.go b/cmd/eia-client/cmd/root.go index dd05dc9..9966494 100644 --- a/cmd/eia-client/cmd/root.go +++ b/cmd/eia-client/cmd/root.go @@ -25,6 +25,7 @@ import ( "context" "os" "os/signal" + "strings" "time" "github.com/rs/zerolog" @@ -52,6 +53,10 @@ func PreRun(cmd *cobra.Command, _ []string) { util.SetLogger(cmd, &logger) + if strings.Contains(cmd.CommandPath(), "comple") { + return + } + util.Logger(cmd).Debug(). Str("logLevel", util.Logger(cmd).GetLevel().String()). Msg("logging configured") diff --git a/cmd/eia-client/internal/util/util_completion.go b/cmd/eia-client/internal/util/util_completion.go new file mode 100644 index 0000000..cf2f6ef --- /dev/null +++ b/cmd/eia-client/internal/util/util_completion.go @@ -0,0 +1,23 @@ +package util + +import ( + "slices" + "strings" + + "github.com/spf13/cobra" + + "gitea.libretechconsulting.com/50W/eia-api-go/pkg/eia" +) + +func CompleteRoutes(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + routes := eia.GetRoutes() + compRoutes := make([]string, 0, len(routes)) + + for _, r := range routes { + if strings.HasPrefix(r, toComplete) { + compRoutes = append(compRoutes, r) + } + } + + return slices.Clip(compRoutes), cobra.ShellCompDirectiveNoFileComp +} diff --git a/cmd/eia-client/internal/util/util_reflect.go b/cmd/eia-client/internal/util/util_reflect.go new file mode 100644 index 0000000..de0d516 --- /dev/null +++ b/cmd/eia-client/internal/util/util_reflect.go @@ -0,0 +1,92 @@ +package util + +import ( + "context" + "errors" + "fmt" + "net/http" + "reflect" + "strconv" + "time" + + "github.com/spf13/cobra" + + eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api" +) + +func GetFacets(cmd *cobra.Command, route string) (any, error) { + client, err := Client(cmd) + if err != nil { + return nil, err + } + + // Get the reflect.Value of the target object + targetValue := reflect.ValueOf(client) + + // Get the method by name + method := targetValue.MethodByName(route) + if !method.IsValid() { + return nil, fmt.Errorf("method %q not found", route) + } + + // Create a slice of reflect.Value for the method's arguments + methodType := method.Type() + args := make([]reflect.Value, 0, methodType.NumIn()) + + // Populate arguments with zero values for their respective types + for i := 0; i < methodType.NumIn(); i++ { + argType := methodType.In(i) + + // Don't supply request editor Fn args + if methodType.IsVariadic() && i == methodType.NumIn()-1 { + continue + } + + if argType == reflect.TypeOf((*context.Context)(nil)).Elem() { + args = append(args, reflect.ValueOf(cmd.Context())) + continue + } + + // Default to last year for Route1 + if argType == reflect.TypeOf(eiaapi.Route1("1999")) { + lastYear := time.Now().AddDate(-1, 0, 0).Year() + args = append(args, reflect.ValueOf(eiaapi.Route1(strconv.Itoa(lastYear)))) + continue + } + + // Zero value of other stuff + args = append(args, reflect.Zero(argType)) + } + + results := method.Call(args) + if len(results) != 2 { + return nil, errors.New("unexpected response from get facet call") + } + + var resp *http.Response + var ok bool + err = nil + + if resp, ok = results[0].Interface().(*http.Response); !ok { + return nil, errors.New("no or invalid response received from call") + } + + if results[1].IsValid() && !results[1].IsNil() { + if err, ok = results[1].Interface().(error); !ok { + return nil, errors.New("unexpected call response") + } + } + + if err != nil { + return nil, err + } + + parser := reflect.ValueOf(eiaapi).MethodByName(fmt.Sprintf("Parse%sResponse", route)) + if !parser.IsValid() { + return nil, errors.New("unable to find parser for facet response") + } + + parser.Call([]reflect.Value{reflect.ValueOf(resp)}) + + return resp, nil +} diff --git a/pkg/eia/eia.go b/pkg/eia/eia.go index bc7edf7..3b3b224 100644 --- a/pkg/eia/eia.go +++ b/pkg/eia/eia.go @@ -51,7 +51,14 @@ type Facet struct { func NewFacets(facets ...*Facet) *eiaapi.Facets { newFacets := make(map[string]interface{}, len(facets)) for _, f := range facets { - newFacets[fmt.Sprintf("facets[%s][]", f.Name)] = f.Data + for i := 0; ; i++ { + if _, set := newFacets[fmt.Sprintf("facets[%s][%d]", f.Name, i)]; set { + continue + } + + newFacets[fmt.Sprintf("facets[%s][%d]", f.Name, i)] = f.Data + break + } } return &newFacets } diff --git a/pkg/eia/eia_reflection.go b/pkg/eia/eia_reflection.go new file mode 100644 index 0000000..b38c049 --- /dev/null +++ b/pkg/eia/eia_reflection.go @@ -0,0 +1,25 @@ +package eia + +import ( + "reflect" + "slices" + "strings" + + eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api" +) + +func GetRoutes() []string { + eiaClientInterface := new(eiaapi.ClientInterface) + t := reflect.TypeOf(eiaClientInterface).Elem() + + routes := make([]string, 0, t.NumMethod()) + + for i := 0; i < t.NumMethod(); i++ { + method := t.Method(i) + if strings.HasSuffix(method.Name, "Facet") { + routes = append(routes, method.Name) + } + } + + return slices.Clip(routes) +}