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

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

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