fix lying swagger spec response type

This commit is contained in:
2024-11-27 13:52:15 -05:00
parent 521e376455
commit 8ba2caa16d
5 changed files with 8714 additions and 2186 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
## 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] Figure out how the fuck to support multiple facets in this piece of shit fucking API spec. Seriously, amateur bullshit.
+8605 -2153
View File
File diff suppressed because it is too large Load Diff
+29 -10
View File
@@ -30,33 +30,49 @@ var ParseFunctionsMap = map[string]interface{}{
type FunctionParamInfo struct {
FunctionName string
Params []string
Params []FunctionParam
}
type FunctionParam struct {
Name string
Type string
}
const tmplFuncParams = `
// List of functions in eiaapi package
// 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 .}}
"{{ .FunctionName }}": {
{{- range .Params }}
{{ . | printf "\"%s\"" }},
{
Name: {{ .Name | printf "\"%s\"" }},
Type: {{ .Type | printf "\"%s\"" }},
},
{{- end }}
},
{{- end}}
}
func GetFuncParamType(funcName string, idx int) string {
func GetFuncParamType(funcName string, idx int) *FunctionParam {
var param FunctionParam
funcParams, exists := FunctionParams[funcName]
if !exists {
return ""
return nil
}
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
if funcDecl.Name.IsExported() {
paramTypes := make([]string, 0)
paramTypes := make([]FunctionParam, 0)
if funcDecl.Type.Params != nil {
for _, param := range funcDecl.Type.Params.List {
// Convert the type expression to a string representation
@@ -98,8 +114,11 @@ func main() {
continue
}
// Append the type once for each name in the parameter
for range param.Names {
paramTypes = append(paramTypes, typeStr)
for _, name := range param.Names {
paramTypes = append(paramTypes, FunctionParam{
Name: name.Name,
Type: typeStr,
})
}
}
}
+5 -1
View File
@@ -12,11 +12,15 @@ import (
func newLoggingMiddleware(logger *zerolog.Logger, level zerolog.Level) eiaapi.RequestEditorFn {
return func(_ context.Context, req *http.Request) error {
// Don't log api_key
params := req.URL.Query()
delete(params, "api_key")
logger.WithLevel(level).
Str("method", req.Method).
Str("host", req.URL.Host).
Str("path", req.URL.Path).
Str("query", req.Form.Encode()).
Str("query", params.Encode()).
Time("timestamp", time.Now()).
Send()
return nil
+74 -21
View File
@@ -2,6 +2,7 @@ package eia
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -19,20 +20,25 @@ import (
type MethodSubs struct {
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
StrNamedParams map[string]reflect.Value // Parameter names by string name from eiaapi mappings
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
// To be more specific, set NameParams, which will overwrite
// NameContainsParams
var defaultMethodSubs = MethodSubs{
StrNamedParams: map[string]reflect.Value{
"route1": yearStringVal,
"route2": yearStringVal,
},
StrTypedParams: map[string]reflect.Value{
"Route1": reflect.ValueOf(eiaapi.Route1(
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year()),
)),
"Route2": reflect.ValueOf(eiaapi.Route1(
strconv.Itoa(time.Now().AddDate(-1, 0, 0).Year()),
)),
"Route1": yearStringVal,
"Route2": yearStringVal,
},
}
@@ -53,27 +59,37 @@ func (client *Client) GetRoute(ctx context.Context, route string, subs *MethodSu
}
args := prepMethodArgs(method, route, subs)
results := method.Call(args)
resp, err := getResponse(results)
if err != nil {
return nil, err
}
result, err := parseResponse(parser, resp)
result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
BodyOnly: true,
})
if err != nil {
return nil, err
}
desc, ok := result.Interface().(*eiaapi.FinalRouteResponseContainer)
if !ok {
return nil, errors.New("indescribable route returned")
// The api doesn't actually return what the swagger spec
// tells you it is going to return, so we need to do this
// 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 desc.Response.Response, nil
return frr.Response, nil
}
// 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
}
result, err := parseResponse(parser, resp)
result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
JsonOnly: true,
})
if err != nil {
return nil, err
}
@@ -154,7 +174,11 @@ func (client *Client) GetFacets(ctx context.Context, route string, subs *MethodS
return nil, err
}
result, err := parseResponse(parser, resp)
result, err := ParseResponse(&ParseOpts{
Parser: parser,
Resp: resp,
JsonOnly: true,
})
if err != nil {
return nil, err
}
@@ -229,10 +253,17 @@ func prepMethodArgs(method reflect.Value, name string, subs *MethodSubs) []refle
break
}
// Perform type lookups by string match
if paramType := eiaapi.GetFuncParamType(name, i); paramType != "" {
// Perform type lookups by string match on either the name of the
// 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 {
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)
goto next
}
@@ -263,10 +294,21 @@ func prepMethodArgs(method reflect.Value, name string, subs *MethodSubs) []refle
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
results := parser.Call([]reflect.Value{reflect.ValueOf(resp)})
results := opts.Parser.Call([]reflect.Value{reflect.ValueOf(opts.Resp)})
if len(results) != 2 {
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())
}
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() {
return result, errors.New("invalid facet data field in response")
return result, errors.New("invalid response container")
}
return field, nil