Skip to content

Commit

Permalink
feat: support array selectors for preset arguments (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac authored Dec 24, 2024
1 parent e2a2f68 commit 4818ef4
Show file tree
Hide file tree
Showing 38 changed files with 7,579 additions and 3,077 deletions.
48 changes: 41 additions & 7 deletions connector/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1160,7 +1160,7 @@ func (mtls *mockTLSServer) Count() int {
return mtls.counter
}

func (mts *mockTLSServer) createMockTLSServer(t *testing.T, dir string) *httptest.Server {
func (mts *mockTLSServer) createMockTLSServer(t *testing.T, dir string, insecure bool) *httptest.Server {
t.Helper()
mux := http.NewServeMux()

Expand Down Expand Up @@ -1195,7 +1195,11 @@ func (mts *mockTLSServer) createMockTLSServer(t *testing.T, dir string) *httptes
tlsConfig := &tls.Config{
ClientCAs: caCertPool,
Certificates: []tls.Certificate{cert},
ClientAuth: tls.RequestClientCert,
ClientAuth: tls.RequireAndVerifyClientCert,
}

if insecure {
tlsConfig.ClientAuth = tls.NoClientCert
}

server := httptest.NewUnstartedServer(mux)
Expand All @@ -1207,11 +1211,11 @@ func (mts *mockTLSServer) createMockTLSServer(t *testing.T, dir string) *httptes

func TestConnectorTLS(t *testing.T) {
mockServer := &mockTLSServer{}
server := mockServer.createMockTLSServer(t, "testdata/tls/certs")
server := mockServer.createMockTLSServer(t, "testdata/tls/certs", false)
defer server.Close()

mockServer1 := &mockTLSServer{}
server1 := mockServer1.createMockTLSServer(t, "testdata/tls/certs_s1")
server1 := mockServer1.createMockTLSServer(t, "testdata/tls/certs_s1", false)
defer server1.Close()

t.Setenv("PET_STORE_URL", server.URL)
Expand Down Expand Up @@ -1305,14 +1309,14 @@ func TestConnectorTLS(t *testing.T) {
})
}()

time.Sleep(1)
time.Sleep(time.Second)
assert.Equal(t, 1, mockServer.Count())
assert.Equal(t, 1, mockServer1.Count())
}

func TestConnectorTLSInsecure(t *testing.T) {
mockServer := &mockTLSServer{}
server := mockServer.createMockTLSServer(t, "testdata/tls/certs")
server := mockServer.createMockTLSServer(t, "testdata/tls/certs", true)
defer server.Close()

t.Setenv("PET_STORE_URL", server.URL)
Expand Down Expand Up @@ -1384,6 +1388,22 @@ func TestConnectorArgumentPresets(t *testing.T) {
assert.DeepEqual(t, map[string]any{
"id": float64(1),
"name": "Dog",
"categories": []any{
map[string]any{
"id": float64(1),
"name": "mammal",
"addresses": []any{
map[string]any{
"id": float64(1),
"name": string("Street 0"),
},
map[string]any{
"id": float64(2),
"name": "Street 1",
},
},
},
},
}, body)

writeResponse(w, []byte(`[{"id": 1, "name": "Dog"}]`))
Expand Down Expand Up @@ -1469,7 +1489,21 @@ func TestConnectorArgumentPresets(t *testing.T) {
{
"type": "procedure",
"name": "addPet",
"arguments": {}
"arguments": {
"body": {
"categories": [{
"name": "mammal",
"addresses": [
{
"id": 1
},
{
"id": 2
}
]
}]
}
}
}
],
"collection_relationships": {}
Expand Down
80 changes: 71 additions & 9 deletions connector/internal/argument/preset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package argument

import (
"errors"
"fmt"
"log"
"strconv"
"strings"

"github.com/hasura/ndc-http/ndc-http-schema/configuration"
rest "github.com/hasura/ndc-http/ndc-http-schema/schema"
Expand Down Expand Up @@ -54,23 +58,24 @@ func (ap ArgumentPreset) Evaluate(operationName string, arguments map[string]any
return nil, err
}

selectorStr := string(rootSelector)
if len(segments) == 1 {
arguments[string(rootSelector)] = value
arguments[selectorStr] = value

return arguments, nil
}

nestedValue, err := ap.evalNestedField(segments[1:], arguments[string(rootSelector)], value)
nestedValue, err := ap.evalNestedField(segments[1:], arguments[string(rootSelector)], value, []string{selectorStr})
if err != nil {
return nil, err
}

arguments[string(rootSelector)] = nestedValue
arguments[selectorStr] = nestedValue

return arguments, nil
}

func (ap ArgumentPreset) evalNestedField(segments []*spec.Segment, argument any, value any) (any, error) {
func (ap ArgumentPreset) evalNestedField(segments []*spec.Segment, argument any, value any, fieldPaths []string) (any, error) {
segmentsLen := len(segments)
if segmentsLen == 0 || len(segments[0].Selectors()) == 0 {
return value, nil
Expand All @@ -83,22 +88,79 @@ func (ap ArgumentPreset) evalNestedField(segments []*spec.Segment, argument any,
argumentMap = make(map[string]any)
}

selectorStr := string(selector)
if segmentsLen == 1 {
argumentMap[string(selector)] = value
argumentMap[selectorStr] = value

return argumentMap, nil
}

nestedValue, err := ap.evalNestedField(segments[1:], argumentMap[string(selector)], value)
nestedValue, err := ap.evalNestedField(segments[1:], argumentMap[selectorStr], value, append(fieldPaths, selectorStr))
if err != nil {
return nil, err
return nil, fmt.Errorf("%s: %w", strings.Join(fieldPaths, "."), err)
}

argumentMap[string(selector)] = nestedValue
argumentMap[selectorStr] = nestedValue

return argumentMap, nil
case spec.WildcardSelector:
argumentSlice, sok := argument.([]any)
if !sok {
return argument, nil
}

for i, arg := range argumentSlice {
var err error
argumentSlice[i], err = ap.evalNestedField(segments[1:], arg, value, append(fieldPaths, strconv.Itoa(i)))
if err != nil {
return nil, err
}
}

return argumentSlice, nil
case spec.SliceSelector:
argumentSlice, sok := argument.([]any)
if !sok {
return argument, nil
}

log.Println(selector, argumentSlice)
step := selector.Step()
if step < 1 {
step = 1
}

end := selector.End()
if end >= len(argumentSlice) {
end = len(argumentSlice) - 1
}

for i := selector.Start(); i <= end; i += step {
var err error
argumentSlice[i], err = ap.evalNestedField(segments[1:], argumentSlice[i], value, append(fieldPaths, strconv.Itoa(i)))
if err != nil {
return nil, err
}
}

return argumentSlice, nil
case spec.Index:
index := int(selector)
argumentSlice, sok := argument.([]any)
if !sok || len(argumentSlice) <= index {
return argument, nil
}

newValue, err := ap.evalNestedField(segments[1:], argumentSlice[index], value, append(fieldPaths, strconv.Itoa(index)))
if err != nil {
return nil, err
}

argumentSlice[index] = newValue

return argumentSlice, nil
default:
return nil, errors.New("unsupported jsonpath spec: " + selector.String())
return argument, nil
}
}

Expand Down
103 changes: 103 additions & 0 deletions connector/testdata/presets/petstore.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,38 @@
"value": 1
},
"targets": ["addPet"]
},
{
"path": "body.id",
"value": {
"type": "literal",
"value": 1
},
"targets": ["addPet"]
},
{
"path": "body.categories[*].id",
"value": {
"type": "literal",
"value": 1
},
"targets": []
},
{
"path": "body.categories[*].addresses[0].name",
"value": {
"type": "literal",
"value": "Street 0"
},
"targets": []
},
{
"path": "body.categories[*].addresses[1:3].name",
"value": {
"type": "literal",
"value": "Street 1"
},
"targets": []
}
]
},
Expand Down Expand Up @@ -147,6 +179,70 @@
"name": "String",
"type": "named"
}
},
"categories": {
"type": {
"type": "array",
"element_type": {
"name": "Category",
"type": "named"
}
}
}
}
},
"Category": {
"fields": {
"id": {
"type": {
"name": "Int64",
"type": "named"
},
"http": {
"type": ["integer"],
"format": "int64"
}
},
"name": {
"type": {
"name": "String",
"type": "named"
},
"http": {
"type": ["string"]
}
},
"addresses": {
"type": {
"type": "array",
"element_type": {
"name": "Address",
"type": "named"
}
}
}
}
},
"Address": {
"fields": {
"id": {
"type": {
"name": "Int64",
"type": "named"
},
"http": {
"type": ["integer"],
"format": "int64"
}
},
"name": {
"type": {
"name": "String",
"type": "named"
},
"http": {
"type": ["string"]
}
}
}
}
Expand All @@ -159,6 +255,13 @@
"type": "int32"
}
},
"Int64": {
"aggregate_functions": {},
"comparison_operators": {},
"representation": {
"type": "int64"
}
},
"String": {
"aggregate_functions": {},
"comparison_operators": {},
Expand Down
Loading

0 comments on commit 4818ef4

Please sign in to comment.