Implement reflection and list facets

This commit is contained in:
Ryan McGuire 2024-11-26 10:54:55 -05:00
parent cba9c76a83
commit b97b24d394
9 changed files with 195 additions and 82 deletions

View File

@ -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:

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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 <name>",
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()
}

View File

@ -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")

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

25
pkg/eia/eia_reflection.go Normal file
View File

@ -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)
}