mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-21 05:53:29 +00:00
* Make `logger` a separate module such that don't depend on `shared` module as a whole for logging * Update `Dockerfile`
491 lines
11 KiB
Go
491 lines
11 KiB
Go
package oas
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/up9inc/mizu/agent/pkg/har"
|
|
|
|
"github.com/chanced/openapi"
|
|
"github.com/up9inc/mizu/logger"
|
|
)
|
|
|
|
func exampleResolver(ref string) (*openapi.ExampleObj, error) {
|
|
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
|
}
|
|
|
|
func responseResolver(ref string) (*openapi.ResponseObj, error) {
|
|
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
|
}
|
|
|
|
func reqBodyResolver(ref string) (*openapi.RequestBodyObj, error) {
|
|
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
|
}
|
|
|
|
func paramResolver(ref string) (*openapi.ParameterObj, error) {
|
|
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
|
}
|
|
|
|
func headerResolver(ref string) (*openapi.HeaderObj, error) {
|
|
return nil, errors.New("JSON references are not supported at the moment: " + ref)
|
|
}
|
|
|
|
func initParams(obj **openapi.ParameterList) {
|
|
if *obj == nil {
|
|
var params openapi.ParameterList = make([]openapi.Parameter, 0)
|
|
*obj = ¶ms
|
|
}
|
|
}
|
|
|
|
func initHeaders(respObj *openapi.ResponseObj) {
|
|
if respObj.Headers == nil {
|
|
var created openapi.Headers = map[string]openapi.Header{}
|
|
respObj.Headers = created
|
|
}
|
|
}
|
|
|
|
func createSimpleParam(name string, in openapi.In, ptype openapi.SchemaType) *openapi.ParameterObj {
|
|
if name == "" {
|
|
panic("Cannot create parameter with empty name")
|
|
}
|
|
required := true // FFS! https://stackoverflow.com/questions/32364027/reference-a-boolean-for-assignment-in-a-struct/32364093
|
|
schema := new(openapi.SchemaObj)
|
|
schema.Type = openapi.Types{ptype}
|
|
|
|
style := openapi.StyleSimple
|
|
if in == openapi.InQuery {
|
|
style = openapi.StyleForm
|
|
}
|
|
|
|
newParam := openapi.ParameterObj{
|
|
Name: name,
|
|
In: in,
|
|
Style: string(style),
|
|
Examples: map[string]openapi.Example{},
|
|
Schema: schema,
|
|
Required: &required,
|
|
}
|
|
return &newParam
|
|
}
|
|
|
|
func findParamByName(params *openapi.ParameterList, in openapi.In, name string) (idx int, pathParam *openapi.ParameterObj) {
|
|
caseInsensitive := in == openapi.InHeader
|
|
for i, param := range *params {
|
|
idx = i
|
|
paramObj, err := param.ResolveParameter(paramResolver)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to resolve reference: %s", err)
|
|
continue
|
|
}
|
|
|
|
if paramObj.In != in {
|
|
continue
|
|
}
|
|
|
|
if paramObj.Name == name || (caseInsensitive && strings.EqualFold(paramObj.Name, name)) {
|
|
pathParam = paramObj
|
|
break
|
|
}
|
|
}
|
|
|
|
return idx, pathParam
|
|
}
|
|
|
|
func findHeaderByName(headers *openapi.Headers, name string) *openapi.HeaderObj {
|
|
for hname, param := range *headers {
|
|
hdrObj, err := param.ResolveHeader(headerResolver)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to resolve reference: %s", err)
|
|
continue
|
|
}
|
|
|
|
if strings.EqualFold(hname, name) {
|
|
return hdrObj
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type nvParams struct {
|
|
In openapi.In
|
|
Pairs []har.NVP
|
|
IsIgnored func(name string) bool
|
|
GeneralizeName func(name string) string
|
|
}
|
|
|
|
func handleNameVals(gw nvParams, params **openapi.ParameterList, checkIgnore bool, sampleId string) {
|
|
visited := map[string]*openapi.ParameterObj{}
|
|
for _, pair := range gw.Pairs {
|
|
if (checkIgnore && gw.IsIgnored(pair.Name)) || pair.Name == "" {
|
|
continue
|
|
}
|
|
|
|
nameGeneral := gw.GeneralizeName(pair.Name)
|
|
|
|
initParams(params)
|
|
_, param := findParamByName(*params, gw.In, pair.Name)
|
|
if param == nil {
|
|
param = createSimpleParam(nameGeneral, gw.In, openapi.TypeString)
|
|
appended := append(**params, param)
|
|
*params = &appended
|
|
}
|
|
exmp := ¶m.Examples
|
|
err := fillParamExample(&exmp, pair.Value)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
|
}
|
|
visited[nameGeneral] = param
|
|
|
|
setSampleID(¶m.Extensions, sampleId)
|
|
}
|
|
|
|
// maintain "required" flag
|
|
if *params != nil {
|
|
for _, param := range **params {
|
|
paramObj, err := param.ResolveParameter(paramResolver)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to resolve param: %s", err)
|
|
continue
|
|
}
|
|
if paramObj.In != gw.In {
|
|
continue
|
|
}
|
|
|
|
_, ok := visited[strings.ToLower(paramObj.Name)]
|
|
if !ok {
|
|
flag := false
|
|
paramObj.Required = &flag
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func createHeader(ptype openapi.SchemaType) *openapi.HeaderObj {
|
|
required := true // FFS! https://stackoverflow.com/questions/32364027/reference-a-boolean-for-assignment-in-a-struct/32364093
|
|
schema := new(openapi.SchemaObj)
|
|
schema.Type = make(openapi.Types, 0)
|
|
schema.Type = append(schema.Type, ptype)
|
|
|
|
style := openapi.StyleSimple
|
|
newParam := openapi.HeaderObj{
|
|
Style: string(style),
|
|
Examples: map[string]openapi.Example{},
|
|
Schema: schema,
|
|
Required: &required,
|
|
}
|
|
return &newParam
|
|
}
|
|
|
|
func fillParamExample(param **openapi.Examples, exampleValue string) error {
|
|
if **param == nil {
|
|
**param = map[string]openapi.Example{}
|
|
}
|
|
|
|
cnt := 0
|
|
for _, example := range **param {
|
|
cnt++
|
|
exampleObj, err := example.ResolveExample(exampleResolver)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var value string
|
|
err = json.Unmarshal(exampleObj.Value, &value)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed decoding parameter example into string: %s", err)
|
|
continue
|
|
}
|
|
|
|
if value == exampleValue || cnt >= 5 { // 5 examples is enough
|
|
return nil
|
|
}
|
|
}
|
|
|
|
valMsg, err := json.Marshal(exampleValue)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
themap := **param
|
|
themap["example #"+strconv.Itoa(cnt)] = &openapi.ExampleObj{Value: valMsg}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TODO: somehow generalize the two example setting functions, plus add body example handling
|
|
|
|
func addSchemaExample(existing *openapi.SchemaObj, bodyStr string) {
|
|
if len(existing.Examples) < 5 {
|
|
found := false
|
|
for _, eVal := range existing.Examples {
|
|
existingExample := ""
|
|
err := json.Unmarshal(eVal, &existingExample)
|
|
if err != nil {
|
|
logger.Log.Debugf("Failed to unmarshal example: %v", eVal)
|
|
continue
|
|
}
|
|
|
|
if existingExample == bodyStr {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
example, err := json.Marshal(bodyStr)
|
|
if err != nil {
|
|
logger.Log.Debugf("Failed to marshal example: %v", bodyStr)
|
|
return
|
|
}
|
|
existing.Examples = append(existing.Examples, example)
|
|
}
|
|
}
|
|
}
|
|
|
|
func longestCommonXfix(strs [][]string, pre bool) []string { // https://github.com/jpillora/longestcommon
|
|
empty := make([]string, 0)
|
|
//short-circuit empty list
|
|
if len(strs) == 0 {
|
|
return empty
|
|
}
|
|
xfix := strs[0]
|
|
//short-circuit single-element list
|
|
if len(strs) == 1 {
|
|
return xfix
|
|
}
|
|
//compare first to rest
|
|
for _, str := range strs[1:] {
|
|
xfixl := len(xfix)
|
|
strl := len(str)
|
|
//short-circuit empty strings
|
|
if xfixl == 0 || strl == 0 {
|
|
return empty
|
|
}
|
|
//maximum possible length
|
|
maxl := xfixl
|
|
if strl < maxl {
|
|
maxl = strl
|
|
}
|
|
//compare letters
|
|
if pre {
|
|
//prefix, iterate left to right
|
|
for i := 0; i < maxl; i++ {
|
|
if xfix[i] != str[i] {
|
|
xfix = xfix[:i]
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
//suffix, iternate right to left
|
|
for i := 0; i < maxl; i++ {
|
|
xi := xfixl - i - 1
|
|
si := strl - i - 1
|
|
if xfix[xi] != str[si] {
|
|
xfix = xfix[xi+1:]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return xfix
|
|
}
|
|
|
|
func longestCommonXfixStr(strs []string, pre bool) string { // https://github.com/jpillora/longestcommon
|
|
//short-circuit empty list
|
|
if len(strs) == 0 {
|
|
return ""
|
|
}
|
|
xfix := strs[0]
|
|
//short-circuit single-element list
|
|
if len(strs) == 1 {
|
|
return xfix
|
|
}
|
|
//compare first to rest
|
|
for _, str := range strs[1:] {
|
|
xfixl := len(xfix)
|
|
strl := len(str)
|
|
//short-circuit empty strings
|
|
if xfixl == 0 || strl == 0 {
|
|
return ""
|
|
}
|
|
//maximum possible length
|
|
maxl := xfixl
|
|
if strl < maxl {
|
|
maxl = strl
|
|
}
|
|
//compare letters
|
|
if pre {
|
|
//prefix, iterate left to right
|
|
for i := 0; i < maxl; i++ {
|
|
if xfix[i] != str[i] {
|
|
xfix = xfix[:i]
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
//suffix, iternate right to left
|
|
for i := 0; i < maxl; i++ {
|
|
xi := xfixl - i - 1
|
|
si := strl - i - 1
|
|
if xfix[xi] != str[si] {
|
|
xfix = xfix[xi+1:]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return xfix
|
|
}
|
|
|
|
func getSimilarPrefix(strs []string) string {
|
|
chunked := make([][]string, 0)
|
|
for _, item := range strs {
|
|
chunked = append(chunked, strings.Split(item, "/"))
|
|
}
|
|
|
|
cmn := longestCommonXfix(chunked, true)
|
|
res := make([]string, 0)
|
|
for _, chunk := range cmn {
|
|
if chunk != "api" && !IsVersionString(chunk) && !strings.HasPrefix(chunk, "{") {
|
|
res = append(res, chunk)
|
|
}
|
|
}
|
|
return strings.Join(res[1:], ".")
|
|
}
|
|
|
|
// returns all non-nil ops in PathObj
|
|
func getOps(pathObj *openapi.PathObj) []*openapi.Operation {
|
|
ops := []**openapi.Operation{&pathObj.Get, &pathObj.Patch, &pathObj.Put, &pathObj.Options, &pathObj.Post, &pathObj.Trace, &pathObj.Head, &pathObj.Delete}
|
|
res := make([]*openapi.Operation, 0)
|
|
for _, opp := range ops {
|
|
if *opp == nil {
|
|
continue
|
|
}
|
|
res = append(res, *opp)
|
|
}
|
|
return res
|
|
}
|
|
|
|
// parses JSON into any possible value
|
|
func anyJSON(text string) (anyVal interface{}, isJSON bool) {
|
|
isJSON = true
|
|
asMap := map[string]interface{}{}
|
|
err := json.Unmarshal([]byte(text), &asMap)
|
|
if err == nil && asMap != nil {
|
|
return asMap, isJSON
|
|
}
|
|
|
|
asArray := make([]interface{}, 0)
|
|
err = json.Unmarshal([]byte(text), &asArray)
|
|
if err == nil && asArray != nil {
|
|
return asArray, isJSON
|
|
}
|
|
|
|
asString := ""
|
|
sPtr := &asString
|
|
err = json.Unmarshal([]byte(text), &sPtr)
|
|
if err == nil && sPtr != nil {
|
|
return asString, isJSON
|
|
}
|
|
|
|
asInt := 0
|
|
intPtr := &asInt
|
|
err = json.Unmarshal([]byte(text), &intPtr)
|
|
if err == nil && intPtr != nil {
|
|
return asInt, isJSON
|
|
}
|
|
|
|
asFloat := 0.0
|
|
floatPtr := &asFloat
|
|
err = json.Unmarshal([]byte(text), &floatPtr)
|
|
if err == nil && floatPtr != nil {
|
|
return asFloat, isJSON
|
|
}
|
|
|
|
asBool := false
|
|
boolPtr := &asBool
|
|
err = json.Unmarshal([]byte(text), &boolPtr)
|
|
if err == nil && boolPtr != nil {
|
|
return asBool, isJSON
|
|
}
|
|
|
|
if text == "null" {
|
|
return nil, isJSON
|
|
}
|
|
|
|
return nil, false
|
|
}
|
|
|
|
func cleanStr(str string, criterion func(r rune) bool) string {
|
|
s := []byte(str)
|
|
j := 0
|
|
for _, b := range s {
|
|
if criterion(rune(b)) {
|
|
s[j] = b
|
|
j++
|
|
}
|
|
}
|
|
return string(s[:j])
|
|
}
|
|
|
|
/*
|
|
func isAlpha(s string) bool {
|
|
for _, r := range s {
|
|
if isAlphaRune(r) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
*/
|
|
|
|
func isAlphaRune(r rune) bool {
|
|
return !((r < 'a' || r > 'z') && (r < 'A' || r > 'Z'))
|
|
}
|
|
|
|
func isAlNumRune(b rune) bool {
|
|
return isAlphaRune(b) || ('0' <= b && b <= '9')
|
|
}
|
|
|
|
func deleteFromSlice(s []string, val string) []string {
|
|
temp := s[:0]
|
|
for _, x := range s {
|
|
if x != val {
|
|
temp = append(temp, x)
|
|
}
|
|
}
|
|
return temp
|
|
}
|
|
|
|
func sliceContains(s []string, e string) bool {
|
|
for _, a := range s {
|
|
if a == e {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func intersectSliceWithMap(required []string, names map[string]struct{}) []string {
|
|
for name := range names {
|
|
if !sliceContains(required, name) {
|
|
required = deleteFromSlice(required, name)
|
|
}
|
|
}
|
|
return required
|
|
}
|
|
|
|
func setSampleID(extensions *openapi.Extensions, id string) {
|
|
if id != "" {
|
|
if *extensions == nil {
|
|
*extensions = openapi.Extensions{}
|
|
}
|
|
err := (extensions).SetExtension(SampleId, id)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to set sample ID: %s", err)
|
|
}
|
|
}
|
|
}
|