File size: 4,006 Bytes
1f10f31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package oasmiddleware

import (
	"errors"
	"fmt"
	"strings"

	"github.com/getkin/kin-openapi/openapi3"
	"github.com/getkin/kin-openapi/openapi3filter"

	"github.com/openmeterio/openmeter/api/v3/apierrors"
)

var oasRuleToAip = map[string]string{
	"minLength": "min_length",
	"maxLength": "max_length",
	"minItems":  "min_items",
	"maxItems":  "max_items",
}

func ToAipError(me openapi3.MultiError) []apierrors.InvalidParameter {
	return aipMapper(me, nil)
}

func aipMapper(me openapi3.MultiError, parent *apierrors.InvalidParameter) []apierrors.InvalidParameter {
	var ipErrs []apierrors.InvalidParameter
	for _, err := range me {
		var i *apierrors.InvalidParameter
		if parent != nil {
			i = parent
		} else {
			i = &apierrors.InvalidParameter{}
		}
		switch err := err.(type) {
		case *openapi3.SchemaError:
			i.Reason = err.Reason
			ipErrs = append(ipErrs, invalidParamFromSchemaError(err, i))
		case *openapi3filter.RequestError:
			if err.Parameter != nil {
				if err.Parameter.Name != "" {
					i.Field = err.Parameter.Name
				}
				if err.Parameter.In != "" {
					i.Source = apierrors.ToInvalid(err.Parameter.In)
				}
				if err.Parameter.Required {
					i.Rule = "required"
				}
			}
			i.Reason = err.Reason
			if err.Reason == "" || err.RequestBody != nil {
				i.Reason = err.Error()
			}

			if err, ok := err.Err.(openapi3.MultiError); ok {
				ipErrs = append(ipErrs, aipMapper(err, i)...)
				continue
			}

			if err, ok := err.Err.(*openapi3.SchemaError); ok {
				i.Choices = make([]string, 0)
				if err.SchemaField == "enum" {
					i.Rule = "enum"
					for _, v := range err.Schema.Enum {
						i.Choices = append(i.Choices, fmt.Sprintf("%v", v))
					}
					i.Reason = fmt.Sprintf("must be one of: [%s]", strings.Join(i.Choices, ","))
				} else if err.SchemaField == "oneOf" {
					ipErrs = append(ipErrs, collectFromSchemaError(err)...)
					continue
				}
			}
			ipErrs = append(ipErrs, *i)
		}
	}
	return ipErrs
}

// collectFromSchemaError looks at schemaErr.Origin. If there are deeper
// child errors (via unwrapOriginError), it returns those. Otherwise, it
// returns a single InvalidParameter built from schemaErr itself.
func collectFromSchemaError(se *openapi3.SchemaError) []apierrors.InvalidParameter {
	childParams := unwrapOriginError(se)
	if len(childParams) == 0 {
		return []apierrors.InvalidParameter{
			invalidParamFromSchemaError(se, nil),
		}
	}
	return childParams
}

// unwrapOriginError traverses schemaErr.Origin (which may be a wrapped multiErrorForOneOf)
// and returns a flat slice of InvalidParameter entries for each underlying *SchemaError.
func unwrapOriginError(schemaErr *openapi3.SchemaError) []apierrors.InvalidParameter {
	if schemaErr == nil || schemaErr.Origin == nil {
		return nil
	}

	// 1) First, try to pull out a MultiError (or multiErrorForOneOf) from the wrapper chain.
	var me openapi3.MultiError
	if errors.As(schemaErr.Origin, &me) {
		var result []apierrors.InvalidParameter
		for _, subErr := range me {
			var subSE *openapi3.SchemaError
			if errors.As(subErr, &subSE) {
				result = append(result, collectFromSchemaError(subSE)...)
			}
		}
		return result
	}

	// 2) If there are no multi-errors and Origin wraps another *SchemaError somewhere in its chain, dive into that.
	var innerSE *openapi3.SchemaError
	if errors.As(schemaErr.Origin, &innerSE) {
		return collectFromSchemaError(innerSE)
	}

	// 3) If we reach here, Origin was neither a nested *SchemaError nor a MultiError.
	return nil
}

func invalidParamFromSchemaError(
	schemaErr *openapi3.SchemaError,
	parent *apierrors.InvalidParameter,
) apierrors.InvalidParameter {
	var ip *apierrors.InvalidParameter
	if parent != nil {
		ip = parent
	} else {
		ip = &apierrors.InvalidParameter{
			Reason: schemaErr.Reason,
		}
	}
	if rule, ok := oasRuleToAip[schemaErr.SchemaField]; ok {
		ip.Rule = rule
	} else {
		ip.Rule = schemaErr.SchemaField
	}
	if path := schemaErr.JSONPointer(); len(path) > 0 {
		ip.Field = strings.Join(path, ".")
	}
	return *ip
}