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 ## 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.
+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 { 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,
})
} }
} }
} }
+5 -1
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
+74 -21
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