fix lying swagger spec response type
This commit is contained in:
parent
521e376455
commit
8ba2caa16d
2
TODO.md
2
TODO.md
@ -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.
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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 ¶m
|
||||
}
|
||||
`
|
||||
|
||||
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user