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
|
## 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
@ -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 ¶m
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user