Expand generated code, add routes list
This commit is contained in:
parent
4211bb3892
commit
48678912f0
2
Makefile
2
Makefile
@ -1,6 +1,7 @@
|
|||||||
# Variables
|
# Variables
|
||||||
CLIENT_PKG := ./cmd/eia-client
|
CLIENT_PKG := ./cmd/eia-client
|
||||||
CLIENT_GEN_FILE := ./api/eiaapi.gen.go
|
CLIENT_GEN_FILE := ./api/eiaapi.gen.go
|
||||||
|
MAPPER_GEN_FILE := ./api/eiaapi_funcmap.gen.go
|
||||||
GO_FORMATTER := gofumpt
|
GO_FORMATTER := gofumpt
|
||||||
IS_GNU_SED := $(shell sed --version >/dev/null 2>&1 && echo true || echo false)
|
IS_GNU_SED := $(shell sed --version >/dev/null 2>&1 && echo true || echo false)
|
||||||
|
|
||||||
@ -23,6 +24,7 @@ else
|
|||||||
endif
|
endif
|
||||||
# Pretty it up
|
# Pretty it up
|
||||||
$(GO_FORMATTER) -w $(CLIENT_GEN_FILE)
|
$(GO_FORMATTER) -w $(CLIENT_GEN_FILE)
|
||||||
|
$(GO_FORMATTER) -w $(MAPPER_GEN_FILE)
|
||||||
|
|
||||||
# Build the client command binary
|
# Build the client command binary
|
||||||
build: generate
|
build: generate
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,13 @@ const (
|
|||||||
|
|
||||||
const tmplParseFuncs = `package ` + packageName + `
|
const tmplParseFuncs = `package ` + packageName + `
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
// Generated map of Parse functions to support
|
// Generated map of Parse functions to support
|
||||||
// dynamic reflection upon parser functions
|
// dynamic reflection upon parser functions
|
||||||
var ParseFunctionsMap = map[string]interface{}{
|
var ParseFunctionsMap = map[string]interface{}{
|
||||||
@ -28,6 +35,23 @@ var ParseFunctionsMap = map[string]interface{}{
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
type FunctionParamInfo struct {
|
||||||
|
FunctionName string
|
||||||
|
Params []string
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmplFuncParams = `
|
||||||
|
var FunctionParams = map[string][]reflect.Type{
|
||||||
|
{{- range .}}
|
||||||
|
"{{ .FunctionName }}": {
|
||||||
|
{{- range .Params }}
|
||||||
|
reflect.TypeOf((*{{ . }})(nil)).Elem(),
|
||||||
|
{{- end }}
|
||||||
|
},
|
||||||
|
{{- end}}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Parse the package
|
// Parse the package
|
||||||
fset := token.NewFileSet()
|
fset := token.NewFileSet()
|
||||||
@ -37,15 +61,44 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect function names that start with "Parse"
|
parseFunctions := make([]string, 0)
|
||||||
var functions []string
|
functionParams := make([]FunctionParamInfo, 0)
|
||||||
|
|
||||||
for _, pkg := range node {
|
for _, pkg := range node {
|
||||||
for _, file := range pkg.Files {
|
for _, file := range pkg.Files {
|
||||||
for _, decl := range file.Decls {
|
for _, decl := range file.Decls {
|
||||||
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
if funcDecl, ok := decl.(*ast.FuncDecl); ok {
|
||||||
|
// Load up Parse functions
|
||||||
if strings.HasPrefix(funcDecl.Name.Name, "Parse") {
|
if strings.HasPrefix(funcDecl.Name.Name, "Parse") {
|
||||||
// Add function name to the list
|
// Add function name to the list
|
||||||
functions = append(functions, funcDecl.Name.Name)
|
parseFunctions = append(parseFunctions, funcDecl.Name.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(funcDecl.Name.Name, "Status") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load up params for all functions
|
||||||
|
if funcDecl.Name.IsExported() {
|
||||||
|
paramTypes := make([]string, 0)
|
||||||
|
if funcDecl.Type.Params != nil {
|
||||||
|
for _, param := range funcDecl.Type.Params.List {
|
||||||
|
// Convert the type expression to a string representation
|
||||||
|
typeExpr := param.Type
|
||||||
|
typeStr := exprToString(typeExpr)
|
||||||
|
if typeStr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Append the type once for each name in the parameter
|
||||||
|
for range param.Names {
|
||||||
|
paramTypes = append(paramTypes, typeStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
functionParams = append(functionParams, FunctionParamInfo{
|
||||||
|
FunctionName: funcDecl.Name.String(),
|
||||||
|
Params: paramTypes,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -60,12 +113,38 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer mapFile.Close()
|
defer mapFile.Close()
|
||||||
|
|
||||||
// Execute the template and write to the file
|
// Execute the template for parse funcs and write to the file
|
||||||
t := template.Must(template.New("mapTemplate").Parse(tmplParseFuncs))
|
parseFuncsTpl := template.Must(template.New("mapTemplate").Parse(tmplParseFuncs))
|
||||||
if err := t.Execute(mapFile, functions); err != nil {
|
if err := parseFuncsTpl.Execute(mapFile, parseFunctions); err != nil {
|
||||||
fmt.Println("Error generating Go code:", err)
|
fmt.Println("Error generating Go code:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Execute the template for func params and write to the file
|
||||||
|
funcParamsTpl := template.Must(template.New("paramsTemplate").Parse(tmplFuncParams))
|
||||||
|
if err := funcParamsTpl.Execute(mapFile, functionParams); err != nil {
|
||||||
|
fmt.Println("Error generating FunctionParams:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%s has been generated successfully!\n", mapperFile)
|
fmt.Printf("%s has been generated successfully!\n", mapperFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func exprToString(expr ast.Expr) string {
|
||||||
|
switch t := expr.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
return t.Name // Simple type like "int" or "string"
|
||||||
|
case *ast.ArrayType:
|
||||||
|
return "[]" + exprToString(t.Elt) // Array type
|
||||||
|
case *ast.StarExpr:
|
||||||
|
return exprToString(t.X) // Pointer type
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
return fmt.Sprintf("%s.%s", exprToString(t.X), t.Sel.Name) // Qualified type like "pkg.Type"
|
||||||
|
case *ast.FuncType:
|
||||||
|
return "func" // Handle function types minimally
|
||||||
|
case *ast.Ellipsis:
|
||||||
|
return ""
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%T", expr) // Fallback to the type's Go type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package get
|
package get
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/k0kubun/pp/v3"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util"
|
"gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util"
|
||||||
@ -15,4 +16,12 @@ var GetFacetsCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RunGetFacetCmd(cmd *cobra.Command, args []string) {
|
func RunGetFacetCmd(cmd *cobra.Command, args []string) {
|
||||||
|
logger := util.Logger(cmd)
|
||||||
|
|
||||||
|
facet, err := util.GetFacet(cmd, args[0], args[1])
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatal().Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
pp.Println(facet)
|
||||||
}
|
}
|
||||||
|
@ -9,5 +9,6 @@ var ListCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
ListCmd.AddCommand(ListFacetsCmd)
|
ListCmd.AddCommand(listFacetsCmd)
|
||||||
|
ListCmd.AddCommand(listRoutesCmd)
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util"
|
"gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ListFacetsCmd = &cobra.Command{
|
var listFacetsCmd = &cobra.Command{
|
||||||
Use: "facets route",
|
Use: "facets route",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
Short: "List facets for given API route",
|
Short: "List facets for given API route",
|
||||||
|
79
cmd/eia-client/cmd/list/list_routes.go
Normal file
79
cmd/eia-client/cmd/list/list_routes.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/k0kubun/pp/v3"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"gitea.libretechconsulting.com/50W/eia-api-go/cmd/eia-client/internal/util"
|
||||||
|
"gitea.libretechconsulting.com/50W/eia-api-go/pkg/eia"
|
||||||
|
)
|
||||||
|
|
||||||
|
var listRoutesCmd = &cobra.Command{
|
||||||
|
Use: "routes",
|
||||||
|
Aliases: []string{"methods", "r"},
|
||||||
|
Short: "List routes, optionally by type (default is Data)",
|
||||||
|
Args: cobra.MaximumNArgs(2),
|
||||||
|
ValidArgs: []string{
|
||||||
|
"Data",
|
||||||
|
"Facet",
|
||||||
|
},
|
||||||
|
Run: RunListRoutesCmd,
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
allMethodsFlag = "all"
|
||||||
|
filtersFlag = "filters"
|
||||||
|
routePrefixFlag = "routePrefix"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunListRoutesCmd(cmd *cobra.Command, args []string) {
|
||||||
|
logger := util.Logger(cmd)
|
||||||
|
|
||||||
|
// Command flags
|
||||||
|
allRoutes, _ := cmd.Flags().GetBool(allMethodsFlag)
|
||||||
|
routePrefix, _ := cmd.Flags().GetString(routePrefixFlag)
|
||||||
|
filters, _ := cmd.Flags().GetStringSlice(filtersFlag)
|
||||||
|
|
||||||
|
if allRoutes && len(args) > 0 {
|
||||||
|
logger.Fatal().Msg("can't specify all methods while also filtering by type")
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := make([]string, 0)
|
||||||
|
if !allRoutes && len(args) == 1 {
|
||||||
|
filter = append(filter, args[0])
|
||||||
|
} else if !allRoutes {
|
||||||
|
filter = append(filter, "Data")
|
||||||
|
}
|
||||||
|
|
||||||
|
routes := eia.GetRoutes(filter...)
|
||||||
|
filteredRoutes := make([]string, 0, len(routes))
|
||||||
|
|
||||||
|
// Apply filters for routePrefix, and optional filters
|
||||||
|
for _, route := range routes {
|
||||||
|
if !allRoutes && routePrefix != "" {
|
||||||
|
if !strings.HasPrefix(strings.ToLower(route), strings.ToLower(routePrefix)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(filters) > 0 {
|
||||||
|
for _, f := range filters {
|
||||||
|
if strings.Contains(strings.ToLower(route), strings.ToLower(f)) {
|
||||||
|
filteredRoutes = append(filteredRoutes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filteredRoutes = append(filteredRoutes, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pp.Println(filteredRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
listRoutesCmd.PersistentFlags().BoolP(allMethodsFlag, "a", false, "List all methods, no filtering")
|
||||||
|
listRoutesCmd.PersistentFlags().StringSliceP(filtersFlag, "f", []string{}, "Optional filters, case insensitive")
|
||||||
|
listRoutesCmd.PersistentFlags().StringP(routePrefixFlag, "p", "Get", "Prefix for routes, ignore with -a flag")
|
||||||
|
}
|
@ -89,8 +89,8 @@ func init() {
|
|||||||
rootCmd.PersistentFlags().StringP(util.FLAG_API_LOG_LEVEL, "L", "",
|
rootCmd.PersistentFlags().StringP(util.FLAG_API_LOG_LEVEL, "L", "",
|
||||||
"Log Level for EIA API Middleware, or set "+util.ENV_API_LOG_LEVEL+" in environment")
|
"Log Level for EIA API Middleware, or set "+util.ENV_API_LOG_LEVEL+" in environment")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().StringP(util.FLAG_APIKEY,
|
rootCmd.PersistentFlags().String(util.FLAG_APIKEY,
|
||||||
"a", "", "API Key, or set "+util.ENV_API+" in environment")
|
"", "API Key, or set "+util.ENV_API+" in environment")
|
||||||
|
|
||||||
rootCmd.PersistentFlags().DurationP(util.FLAG_TMOUT, "t", util.DefRequestTimeout,
|
rootCmd.PersistentFlags().DurationP(util.FLAG_TMOUT, "t", util.DefRequestTimeout,
|
||||||
"Request timeout, or set "+util.ENV_TMOUT+" in environment")
|
"Request timeout, or set "+util.ENV_TMOUT+" in environment")
|
||||||
|
@ -39,7 +39,7 @@ func CompleteFacet(cmd *cobra.Command, args []string, toComplete string) ([]stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CompleteRoute(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
func CompleteRoute(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
routes := eia.GetRoutes()
|
routes := eia.GetRoutes("Facet")
|
||||||
compRoutes := make([]string, 0, len(routes))
|
compRoutes := make([]string, 0, len(routes))
|
||||||
|
|
||||||
for _, r := range routes {
|
for _, r := range routes {
|
||||||
|
@ -1,177 +1,26 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api"
|
eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api"
|
||||||
|
"gitea.libretechconsulting.com/50W/eia-api-go/pkg/eia"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MethodSubs struct {
|
func GetFacet(cmd *cobra.Command, route string, facet string) (*eiaapi.FacetDetails, error) {
|
||||||
TypedParams map[reflect.Type]reflect.Value // Replace field of specific type with value
|
client, err := Client(cmd)
|
||||||
NameParams map[string]reflect.Value // Replace field with specific name with value
|
if err != nil {
|
||||||
NameContainsParams map[string]reflect.Value // Replace fields with name containing string with value
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultMethodSubs = MethodSubs{
|
return client.GetFacet(cmd.Context(), route, facet, eia.DefaultMethodSubs(cmd.Context()))
|
||||||
NameContainsParams: map[string]reflect.Value{
|
|
||||||
"route": reflect.ValueOf(strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year())),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// func GetFacet(cmd *cobra.Command, route string) (*eiaapi.FacetOptionList, error) {
|
|
||||||
// }
|
|
||||||
|
|
||||||
func GetFacets(cmd *cobra.Command, route string) (*eiaapi.FacetOptionList, error) {
|
func GetFacets(cmd *cobra.Command, route string) (*eiaapi.FacetOptionList, error) {
|
||||||
client, err := Client(cmd)
|
client, err := Client(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the reflect.Value of the target object
|
return client.GetFacets(cmd.Context(), route, eia.DefaultMethodSubs(cmd.Context()))
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
args := prepMethodArgs(cmd.Context(), method, GetMethodSubs(cmd))
|
|
||||||
|
|
||||||
// Prepare a parser func for our facet response
|
|
||||||
parserFunc, exists := eiaapi.ParseFunctionsMap[fmt.Sprintf("Parse%sResponse", route)]
|
|
||||||
if !exists {
|
|
||||||
return nil, fmt.Errorf("parser func for %s not found", route)
|
|
||||||
}
|
|
||||||
|
|
||||||
parser := reflect.ValueOf(parserFunc)
|
|
||||||
if !parser.IsValid() {
|
|
||||||
return nil, errors.New("unable to find parser for facet response")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the API call
|
|
||||||
results := method.Call(args)
|
|
||||||
if len(results) != 2 {
|
|
||||||
return nil, errors.New("unexpected response from get facet call")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prepare *http.Response, error
|
|
||||||
resp, err := getResponse(results)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call the parser with our response, then extract the JSON200 response,
|
|
||||||
// and return the expected FacetOptiionsList from the container
|
|
||||||
|
|
||||||
results = parser.Call([]reflect.Value{reflect.ValueOf(resp)})
|
|
||||||
if len(results) != 2 {
|
|
||||||
return nil, errors.New("unexpected response while parsing facet response")
|
|
||||||
}
|
|
||||||
if err := checkCallErr(results[1]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := results[0]
|
|
||||||
if result.Kind() == reflect.Ptr {
|
|
||||||
result = result.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.Kind() != reflect.Struct {
|
|
||||||
return nil, fmt.Errorf("unexpected parse result kind %s", result.Kind().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
field := result.FieldByName("JSON200")
|
|
||||||
if !field.IsValid() {
|
|
||||||
return nil, errors.New("invalid facet data field in response")
|
|
||||||
}
|
|
||||||
|
|
||||||
facetOptions, ok := field.Interface().(*eiaapi.FacetOptionListContainer)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("response does not contain facet options")
|
|
||||||
}
|
|
||||||
|
|
||||||
if facetOptions == nil {
|
|
||||||
return nil, errors.New("no facet options found for facet request")
|
|
||||||
}
|
|
||||||
|
|
||||||
return facetOptions.Response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Given an API method, replaces any named fields with the provided value
|
|
||||||
// from the subs map, otherwise uses built-in logic for common fields
|
|
||||||
// such as a context.Context. Skips request editor funcs
|
|
||||||
func prepMethodArgs(ctx context.Context, method reflect.Value, _ MethodSubs) []reflect.Value {
|
|
||||||
// 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(ctx))
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
return args
|
|
||||||
}
|
|
||||||
|
|
||||||
func getResponse(responses []reflect.Value) (*http.Response, error) {
|
|
||||||
resp, ok := responses[0].Interface().(*http.Response)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("no or invalid response received from call")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := checkCallErr(responses[1]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkCallErr(val reflect.Value) error {
|
|
||||||
var err error
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if val.IsValid() && !val.IsNil() {
|
|
||||||
if err, ok = val.Interface().(error); !ok {
|
|
||||||
return errors.New("unexpected call response")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetMethodSubs(cmd *cobra.Command) MethodSubs {
|
|
||||||
subs := defaultMethodSubs
|
|
||||||
|
|
||||||
subs.TypedParams[reflect.TypeOf(context.Background())] = reflect.ValueOf(cmd.Context())
|
|
||||||
|
|
||||||
return subs
|
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,187 @@
|
|||||||
package eia
|
package eia
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api"
|
eiaapi "gitea.libretechconsulting.com/50W/eia-api-go/api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetRoutes() []string {
|
// For reflected API requests, dynamically replace certain
|
||||||
|
// parameters by type
|
||||||
|
type MethodSubs struct {
|
||||||
|
TypedParams map[reflect.Type]reflect.Value // Replace field of specific type with value, must be ptr to type
|
||||||
|
RequestEditorFns []eiaapi.RequestEditorFn // Optional request editor functions
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default replace all routes with a string year
|
||||||
|
// To be more specific, set NameParams, which will overwrite
|
||||||
|
// NameContainsParams
|
||||||
|
var defaultMethodSubs = MethodSubs{
|
||||||
|
TypedParams: map[reflect.Type]reflect.Value{
|
||||||
|
reflect.TypeOf((*eiaapi.Route1)(nil)): reflect.ValueOf(
|
||||||
|
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year())),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given an API route and a facet ID, retrieve information about the facet
|
||||||
|
func (client *Client) GetFacet(ctx context.Context, route string, facet string, subs *MethodSubs) (*eiaapi.FacetDetails, error) {
|
||||||
|
eiaClient := reflect.ValueOf(client)
|
||||||
|
|
||||||
|
// Get the method for describing this facet
|
||||||
|
methodName := fmt.Sprintf("%sFacetId", route)
|
||||||
|
method := eiaClient.MethodByName(methodName)
|
||||||
|
if !method.IsValid() {
|
||||||
|
return nil, fmt.Errorf("method %s not found", methodName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate facet param anywhere in func call
|
||||||
|
subs.TypedParams[reflect.TypeOf((*eiaapi.FacetId)(nil))] = reflect.ValueOf(facet)
|
||||||
|
|
||||||
|
args := prepMethodArgs(method, methodName, subs)
|
||||||
|
|
||||||
|
// Prepare a parser func for our facet response
|
||||||
|
parserFunc, exists := eiaapi.ParseFunctionsMap[fmt.Sprintf("Parse%sResponse", methodName)]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("parser func for %s not found", route)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := reflect.ValueOf(parserFunc)
|
||||||
|
if !parser.IsValid() {
|
||||||
|
return nil, errors.New("unable to find parser for facet response")
|
||||||
|
}
|
||||||
|
|
||||||
|
results := method.Call(args)
|
||||||
|
resp, err := getResponse(results)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
results = parser.Call([]reflect.Value{reflect.ValueOf(resp)})
|
||||||
|
if len(results) != 2 {
|
||||||
|
return nil, errors.New("unexpected response while parsing facet response")
|
||||||
|
}
|
||||||
|
if err := checkCallErr(results[1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := results[0]
|
||||||
|
if result.Kind() == reflect.Ptr {
|
||||||
|
result = result.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("unexpected parse result kind %s", result.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
field := result.FieldByName("JSON200")
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, errors.New("invalid facet data field in response")
|
||||||
|
}
|
||||||
|
|
||||||
|
facetDetails, ok := field.Interface().(*eiaapi.FacetDetailsContainer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("response does not contain facet details")
|
||||||
|
}
|
||||||
|
|
||||||
|
if facetDetails == nil {
|
||||||
|
return nil, errors.New("no facet details found for facet")
|
||||||
|
}
|
||||||
|
|
||||||
|
return facetDetails.Response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a list of facets given a named route
|
||||||
|
func (client *Client) GetFacets(ctx context.Context, route string, subs *MethodSubs) (*eiaapi.FacetOptionList, error) {
|
||||||
|
eiaClient := reflect.ValueOf(client)
|
||||||
|
|
||||||
|
// Get the method by name
|
||||||
|
method := eiaClient.MethodByName(route)
|
||||||
|
if !method.IsValid() {
|
||||||
|
return nil, fmt.Errorf("method %q not found", route)
|
||||||
|
}
|
||||||
|
|
||||||
|
args := prepMethodArgs(method, route, subs)
|
||||||
|
|
||||||
|
// Prepare a parser func for our facet response
|
||||||
|
parserFunc, exists := eiaapi.ParseFunctionsMap[fmt.Sprintf("Parse%sResponse", route)]
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("parser func for %s not found", route)
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := reflect.ValueOf(parserFunc)
|
||||||
|
if !parser.IsValid() {
|
||||||
|
return nil, errors.New("unable to find parser for facet response")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform the API call
|
||||||
|
results := method.Call(args)
|
||||||
|
if len(results) != 2 {
|
||||||
|
return nil, errors.New("unexpected response from get facet call")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare *http.Response, error
|
||||||
|
resp, err := getResponse(results)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the parser with our response, then extract the JSON200 response,
|
||||||
|
// and return the expected FacetOptiionsList from the container
|
||||||
|
|
||||||
|
results = parser.Call([]reflect.Value{reflect.ValueOf(resp)})
|
||||||
|
if len(results) != 2 {
|
||||||
|
return nil, errors.New("unexpected response while parsing facet response")
|
||||||
|
}
|
||||||
|
if err := checkCallErr(results[1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
result := results[0]
|
||||||
|
if result.Kind() == reflect.Ptr {
|
||||||
|
result = result.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("unexpected parse result kind %s", result.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
field := result.FieldByName("JSON200")
|
||||||
|
if !field.IsValid() {
|
||||||
|
return nil, errors.New("invalid facet data field in response")
|
||||||
|
}
|
||||||
|
|
||||||
|
facetOptions, ok := field.Interface().(*eiaapi.FacetOptionListContainer)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("response does not contain facet options")
|
||||||
|
}
|
||||||
|
|
||||||
|
if facetOptions == nil {
|
||||||
|
return nil, errors.New("no facet options found for facet request")
|
||||||
|
}
|
||||||
|
|
||||||
|
return facetOptions.Response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare default substitutions with a provided context
|
||||||
|
func DefaultMethodSubs(ctx context.Context) *MethodSubs {
|
||||||
|
subs := defaultMethodSubs
|
||||||
|
|
||||||
|
subs.TypedParams[reflect.TypeOf((*context.Context)(nil)).Elem()] = reflect.ValueOf(ctx)
|
||||||
|
|
||||||
|
return &subs
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a list of API routes with a given list of
|
||||||
|
// function suffixes
|
||||||
|
func GetRoutes(suffixes ...string) []string {
|
||||||
eiaClientInterface := new(eiaapi.ClientInterface)
|
eiaClientInterface := new(eiaapi.ClientInterface)
|
||||||
t := reflect.TypeOf(eiaClientInterface).Elem()
|
t := reflect.TypeOf(eiaClientInterface).Elem()
|
||||||
|
|
||||||
@ -16,10 +189,103 @@ func GetRoutes() []string {
|
|||||||
|
|
||||||
for i := 0; i < t.NumMethod(); i++ {
|
for i := 0; i < t.NumMethod(); i++ {
|
||||||
method := t.Method(i)
|
method := t.Method(i)
|
||||||
if strings.HasSuffix(method.Name, "Facet") {
|
if len(suffixes) < 1 {
|
||||||
routes = append(routes, method.Name)
|
routes = append(routes, method.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, suffix := range suffixes {
|
||||||
|
if strings.HasSuffix(method.Name, suffix) {
|
||||||
|
routes = append(routes, method.Name)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return slices.Clip(routes)
|
return slices.Clip(routes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Given an API method, replaces any named fields with the provided value
|
||||||
|
// from the subs map, otherwise uses built-in logic for common fields
|
||||||
|
// such as a context.Context. Skips request editor funcs
|
||||||
|
func prepMethodArgs(method reflect.Value, name string, subs *MethodSubs) []reflect.Value {
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Supply provided request editor Fn args if this function
|
||||||
|
// is varidic and if we're in the lest arg
|
||||||
|
if methodType.IsVariadic() && i == methodType.NumIn()-1 {
|
||||||
|
for _, reFn := range subs.RequestEditorFns {
|
||||||
|
args = append(args, reflect.ValueOf(reFn))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve arg from generated arg mapping due to use of
|
||||||
|
// type aliases (e.g. type Route1 = string) rather than use of
|
||||||
|
// discrete types (e.g. type Route1 string) in generated code
|
||||||
|
funcArgs, ok := eiaapi.FunctionParams[name]
|
||||||
|
if ok {
|
||||||
|
if i > len(funcArgs) {
|
||||||
|
// WARN: This indicates a generator issues
|
||||||
|
} else {
|
||||||
|
argType = funcArgs[i]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// WARN: This indicates a generator issues
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run through type substitutions
|
||||||
|
for t, v := range subs.TypedParams {
|
||||||
|
if argType.Kind() == reflect.Ptr && argType.Elem() == t {
|
||||||
|
args = append(args, v)
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to less-specific types
|
||||||
|
for t, v := range subs.TypedParams {
|
||||||
|
if argType == t {
|
||||||
|
args = append(args, v)
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero value of other stuff
|
||||||
|
args = append(args, reflect.Zero(argType))
|
||||||
|
next:
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResponse(responses []reflect.Value) (*http.Response, error) {
|
||||||
|
resp, ok := responses[0].Interface().(*http.Response)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("no or invalid response received from call")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkCallErr(responses[1]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkCallErr(val reflect.Value) error {
|
||||||
|
var err error
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if val.IsValid() && !val.IsNil() {
|
||||||
|
if err, ok = val.Interface().(error); !ok {
|
||||||
|
return errors.New("unexpected call response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user