fix lying swagger spec response type

This commit is contained in:
Ryan McGuire 2024-11-27 13:52:15 -05:00
parent 521e376455
commit 8ba2caa16d
5 changed files with 8714 additions and 2186 deletions

View File

@ -2,7 +2,7 @@
## EIA Package ## EIA Package
- [ ] Make reflection match a STRING type instead of the underlying type - [x] Make reflection match a STRING type instead of the underlying type
- [x] Move cmd/eia-client/internal/util/util_reflect to pkg/eia - [x] Move cmd/eia-client/internal/util/util_reflect to pkg/eia
- [x] Figure out how the fuck to support multiple facets in this piece of shit fucking API spec. Seriously, amateur bullshit. - [x] Figure out how the fuck to support multiple facets in this piece of shit fucking API spec. Seriously, amateur bullshit.

File diff suppressed because it is too large Load Diff

View File

@ -30,33 +30,49 @@ var ParseFunctionsMap = map[string]interface{}{
type FunctionParamInfo struct { type FunctionParamInfo struct {
FunctionName string FunctionName string
Params []string Params []FunctionParam
}
type FunctionParam struct {
Name string
Type string
} }
const tmplFuncParams = ` const tmplFuncParams = `
// List of functions in eiaapi package // List of functions in eiaapi package
// with slice of arg types as strings // with slice of arg types as strings
var FunctionParams = map[string][]string{
type FunctionParam struct {
Name string
Type string
}
var FunctionParams = map[string][]FunctionParam{
{{- range .}} {{- range .}}
"{{ .FunctionName }}": { "{{ .FunctionName }}": {
{{- range .Params }} {{- range .Params }}
{{ . | printf "\"%s\"" }}, {
Name: {{ .Name | printf "\"%s\"" }},
Type: {{ .Type | printf "\"%s\"" }},
},
{{- end }} {{- end }}
}, },
{{- end}} {{- end}}
} }
func GetFuncParamType(funcName string, idx int) string { func GetFuncParamType(funcName string, idx int) *FunctionParam {
var param FunctionParam
funcParams, exists := FunctionParams[funcName] funcParams, exists := FunctionParams[funcName]
if !exists { if !exists {
return "" return nil
} }
if idx < len(funcParams) { if idx < len(funcParams) {
return funcParams[idx] param = funcParams[idx]
} }
return "" return &param
} }
` `
@ -88,7 +104,7 @@ func main() {
// Load up params for all functions // Load up params for all functions
if funcDecl.Name.IsExported() { if funcDecl.Name.IsExported() {
paramTypes := make([]string, 0) paramTypes := make([]FunctionParam, 0)
if funcDecl.Type.Params != nil { if funcDecl.Type.Params != nil {
for _, param := range funcDecl.Type.Params.List { for _, param := range funcDecl.Type.Params.List {
// Convert the type expression to a string representation // Convert the type expression to a string representation
@ -98,8 +114,11 @@ func main() {
continue continue
} }
// Append the type once for each name in the parameter // Append the type once for each name in the parameter
for range param.Names { for _, name := range param.Names {
paramTypes = append(paramTypes, typeStr) paramTypes = append(paramTypes, FunctionParam{
Name: name.Name,
Type: typeStr,
})
} }
} }
} }

View File

@ -12,11 +12,15 @@ import (
func newLoggingMiddleware(logger *zerolog.Logger, level zerolog.Level) eiaapi.RequestEditorFn { func newLoggingMiddleware(logger *zerolog.Logger, level zerolog.Level) eiaapi.RequestEditorFn {
return func(_ context.Context, req *http.Request) error { return func(_ context.Context, req *http.Request) error {
// Don't log api_key
params := req.URL.Query()
delete(params, "api_key")
logger.WithLevel(level). logger.WithLevel(level).
Str("method", req.Method). Str("method", req.Method).
Str("host", req.URL.Host). Str("host", req.URL.Host).
Str("path", req.URL.Path). Str("path", req.URL.Path).
Str("query", req.Form.Encode()). Str("query", params.Encode()).
Time("timestamp", time.Now()). Time("timestamp", time.Now()).
Send() Send()
return nil return nil

View File

@ -2,6 +2,7 @@ package eia
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -19,20 +20,25 @@ import (
type MethodSubs struct { type MethodSubs struct {
TypedParams map[reflect.Type]reflect.Value // Replace field of specific type with value, must be ptr to type TypedParams map[reflect.Type]reflect.Value // Replace field of specific type with value, must be ptr to type
StrTypedParams map[string]reflect.Value // Parameter types by string name from eiaapi mappings StrTypedParams map[string]reflect.Value // Parameter types by string name from eiaapi mappings
StrNamedParams map[string]reflect.Value // Parameter names by string name from eiaapi mappings
RequestEditorFns []eiaapi.RequestEditorFn // Optional request editor functions RequestEditorFns []eiaapi.RequestEditorFn // Optional request editor functions
} }
var yearStringVal = reflect.ValueOf(eiaapi.Route1(
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year()),
))
// By default replace all routes with a string year // By default replace all routes with a string year
// To be more specific, set NameParams, which will overwrite // To be more specific, set NameParams, which will overwrite
// NameContainsParams // NameContainsParams
var defaultMethodSubs = MethodSubs{ var defaultMethodSubs = MethodSubs{
StrNamedParams: map[string]reflect.Value{
"route1": yearStringVal,
"route2": yearStringVal,
},
StrTypedParams: map[string]reflect.Value{ StrTypedParams: map[string]reflect.Value{
"Route1": reflect.ValueOf(eiaapi.Route1( "Route1": yearStringVal,
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year()), "Route2": yearStringVal,
)),
"Route2": reflect.ValueOf(eiaapi.Route1(
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year()),
)),
}, },
} }
@ -53,27 +59,37 @@ func (client *Client) GetRoute(ctx context.Context, route string, subs *MethodSu
} }
args := prepMethodArgs(method, route, subs) args := prepMethodArgs(method, route, subs)
results := method.Call(args) results := method.Call(args)
resp, err := getResponse(results) resp, err := getResponse(results)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result, err := parseResponse(parser, resp) result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
BodyOnly: true,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
desc, ok := result.Interface().(*eiaapi.FinalRouteResponseContainer) // The api doesn't actually return what the swagger spec
if !ok { // tells you it is going to return, so we need to do this
return nil, errors.New("indescribable route returned") // manually for routes
frr := new(eiaapi.FinalRouteResponse)
if err = json.Unmarshal(
result.Interface().([]uint8), frr,
); err != nil {
return nil, err
} }
if desc.Response == nil { if frr.Response == nil {
return nil, errors.New("no route response received") return nil, errors.New("no route response received")
} }
return desc.Response.Response, nil return frr.Response, nil
} }
// Given an API route and a facet ID, retrieve information about the facet // Given an API route and a facet ID, retrieve information about the facet
@ -105,7 +121,11 @@ func (client *Client) GetFacet(ctx context.Context, route string, facet string,
return nil, err return nil, err
} }
result, err := parseResponse(parser, resp) result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
JsonOnly: true,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,7 +174,11 @@ func (client *Client) GetFacets(ctx context.Context, route string, subs *MethodS
return nil, err return nil, err
} }
result, err := parseResponse(parser, resp) result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
JsonOnly: true,
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -229,10 +253,17 @@ func prepMethodArgs(method reflect.Value, name string, subs *MethodSubs) []refle
break break
} }
// Perform type lookups by string match // Perform type lookups by string match on either the name of the
if paramType := eiaapi.GetFuncParamType(name, i); paramType != "" { // parameter, or the type of the parameter, as a string value
if paramType := eiaapi.GetFuncParamType(name, i); paramType != nil {
for name, val := range subs.StrTypedParams { for name, val := range subs.StrTypedParams {
if paramType == name { if paramType.Type == name {
args = append(args, val)
goto next
}
}
for name, val := range subs.StrNamedParams {
if paramType.Name == name {
args = append(args, val) args = append(args, val)
goto next goto next
} }
@ -263,10 +294,21 @@ func prepMethodArgs(method reflect.Value, name string, subs *MethodSubs) []refle
return args return args
} }
func parseResponse(parser reflect.Value, resp *http.Response) (reflect.Value, error) { // If jsonOnly is set, only the parsed value will be returned in a field called JSON200
// If there are unmarshaling issues due to code generated from the crappy swagger spec,
// you may have to manually unmarshal the "Body" field into the correct type. Setting jsonOnly
// to false will return the entire thing rather than just the JSON200 field.
type ParseOpts struct {
Parser reflect.Value
Resp *http.Response
JsonOnly bool // Used when parser can correctly unmarshal into type
BodyOnly bool // Used when API response does not contain openapi spec suggested type
}
func ParseResponse(opts *ParseOpts) (reflect.Value, error) {
var result reflect.Value var result reflect.Value
results := parser.Call([]reflect.Value{reflect.ValueOf(resp)}) results := opts.Parser.Call([]reflect.Value{reflect.ValueOf(opts.Resp)})
if len(results) != 2 { if len(results) != 2 {
return result, errors.New("unexpected response while parsing response") return result, errors.New("unexpected response while parsing response")
} }
@ -283,9 +325,20 @@ func parseResponse(parser reflect.Value, resp *http.Response) (reflect.Value, er
return result, fmt.Errorf("unexpected parse result kind %s", result.Kind().String()) return result, fmt.Errorf("unexpected parse result kind %s", result.Kind().String())
} }
field := result.FieldByName("JSON200") if !opts.JsonOnly && !opts.BodyOnly {
return result, nil
}
var field reflect.Value
if opts.JsonOnly {
field = result.FieldByName("JSON200")
} else if opts.BodyOnly {
field = result.FieldByName("Body")
}
if !field.IsValid() { if !field.IsValid() {
return result, errors.New("invalid facet data field in response") return result, errors.New("invalid response container")
} }
return field, nil return field, nil