diff --git a/agent/pkg/oas/tree.go b/agent/pkg/oas/tree.go index e9b73d3f4..272872119 100644 --- a/agent/pkg/oas/tree.go +++ b/agent/pkg/oas/tree.go @@ -217,7 +217,46 @@ func (n *Node) searchInConstants(pathChunk string) *Node { } func (n *Node) compact() { - // TODO + // TODO: introduce and leverage "dirty" flag? + var param *Node + // find the param + for _, subnode := range n.children { + if subnode.constant != nil { + continue + } + + param = subnode + } + + if param != nil { + // take its regex + pRegex := param.pathParam.Schema.Pattern + if pRegex != nil { + newChildren := make([]*Node, 0) + + // compact the constants via regex + for _, subnode := range n.children { + if subnode.constant != nil { + if pRegex.Match([]byte(*subnode.constant)) { + param.merge(subnode) + continue + } + } + newChildren = append(newChildren, subnode) + } + + if len(n.children) != len(newChildren) { + logger.Log.Debugf("Shrinking children from %d to %d", len(n.children), len(newChildren)) + n.children = newChildren + n.compact() + } + } + } + + // recurse into next tree level + for _, subnode := range n.children { + subnode.compact() + } } func (n *Node) listPaths() *openapi.Paths { @@ -306,3 +345,30 @@ func (n *Node) countParentParams() int { } return res } + +func (n *Node) merge(other *Node) { + if n.constant == nil && other.constant == nil { + // make sure the params will match by name later in merge + other.pathParam.Name = n.pathParam.Name + } + + if n.pathObj != nil && other.pathObj != nil { + mergePathObj(n.pathObj, other.pathObj) + } + + // TODO: if n is param and other is constant, could have added constant as an example + +outer: + for _, oChild := range other.children { + for _, nChild := range n.children { + matchedConst := oChild.constant != nil && oChild.constant == nChild.constant + matchedParam := oChild.constant == nil && nChild.constant == nil + if matchedConst || matchedParam { + // TODO: if both are params, could have merged their examples + nChild.merge(oChild) + continue outer + } + } + n.children = append(n.children, oChild) + } +} diff --git a/agent/pkg/oas/utils.go b/agent/pkg/oas/utils.go index 59fb0849b..4205c29ef 100644 --- a/agent/pkg/oas/utils.go +++ b/agent/pkg/oas/utils.go @@ -474,3 +474,82 @@ func intersectSliceWithMap(required []string, names map[string]struct{}) []strin } return required } + +func mergePathObj(po *openapi.PathObj, other *openapi.PathObj) { + // merge parameters + mergeParamLists(&po.Parameters, &other.Parameters) + + // merge ops + mergeOps(&po.Get, &other.Get) + mergeOps(&po.Put, &other.Put) + mergeOps(&po.Options, &other.Options) + mergeOps(&po.Patch, &other.Patch) + mergeOps(&po.Delete, &other.Delete) + mergeOps(&po.Head, &other.Head) + mergeOps(&po.Trace, &other.Trace) + mergeOps(&po.Post, &other.Post) +} + +func mergeParamLists(params **openapi.ParameterList, other **openapi.ParameterList) { + if *other == nil { + return + } + + if *params == nil { + *params = new(openapi.ParameterList) + } + +outer: + for _, o := range **other { + oParam, err := o.ResolveParameter(paramResolver) + if err != nil { + logger.Log.Warningf("Failed to resolve reference: %s", err) + continue + } + + for _, p := range **params { + param, err := p.ResolveParameter(paramResolver) + if err != nil { + logger.Log.Warningf("Failed to resolve reference: %s", err) + continue + } + + if param.In == oParam.In && param.Name == oParam.Name { + // TODO: merge examples? transfer schema pattern? + continue outer + } + } + + **params = append(**params, oParam) + } +} + +func mergeOps(op **openapi.Operation, other **openapi.Operation) { + if *other == nil { + return + } + + if *op == nil { + *op = *other + } else { + // merge parameters + mergeParamLists(&(*op).Parameters, &(*other).Parameters) + + // merge responses + mergeOpResponses(&(*op).Responses, &(*other).Responses) + + // merge request body + mergeOpReqBodies(&(*op).RequestBody, &(*other).RequestBody) + + // historical OpIDs + // merge kpis + } +} + +func mergeOpReqBodies(r *openapi.RequestBody, r2 *openapi.RequestBody) { + +} + +func mergeOpResponses(r *openapi.Responses, o *openapi.Responses) { + +} diff --git a/agent/pkg/oas/utils_test.go b/agent/pkg/oas/utils_test.go index ae4b21ef3..4813926de 100644 --- a/agent/pkg/oas/utils_test.go +++ b/agent/pkg/oas/utils_test.go @@ -1,6 +1,8 @@ package oas import ( + "github.com/chanced/openapi" + "reflect" "testing" ) @@ -57,3 +59,28 @@ func TestStrRunes(t *testing.T) { t.Logf("Failed") } } + +func TestOpMerging(t *testing.T) { + testCases := []struct { + op1 *openapi.Operation + op2 *openapi.Operation + res *openapi.Operation + }{ + {nil, nil, nil}, + {&openapi.Operation{}, nil, &openapi.Operation{}}, + {nil, &openapi.Operation{}, &openapi.Operation{}}, + { + &openapi.Operation{OperationID: "op1"}, + &openapi.Operation{OperationID: "op2"}, + &openapi.Operation{OperationID: "op1"}, + }, + // has historicIds + } + for _, tc := range testCases { + mergeOps(&tc.op1, &tc.op2) + + if !reflect.DeepEqual(tc.op1, tc.res) { + t.Errorf("Does not match expected") + } + } +}