Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions transport/http/client/graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,17 @@ import (
"net/http"
"net/url"
"os"
"regexp"
"strings"

"github.com/luraproject/lura/v2/config"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

// urlKeysPattern matches URL path parameters in the form {paramName}.
var urlKeysPattern = regexp.MustCompile(`\{([\w\-.:/]+)\}`)

// Namespace is the key for the backend's extra config
const Namespace = "github.com/devopsfaith/krakend/transport/http/client/graphql"

Expand Down Expand Up @@ -92,20 +96,28 @@ func GetOptions(cfg config.ExtraConfig) (*Options, error) {

// New resturns a new Extractor, ready to be use on a middleware
func New(opt Options) *Extractor {
var replacements [][2]string
// templates holds [varKey, templateString] pairs where {param} has been
// pre-converted to {{.Param}} at setup time, mirroring the approach used
// in config.go for url_pattern interpolation.
var templates [][2]string

title := cases.Title(language.Und)
for k, v := range opt.Variables {
val, ok := v.(string)
if !ok {
continue
}
if val[0] == '{' && val[len(val)-1] == '}' {
replacements = append(replacements, [2]string{k, title.String(val[1:2]) + val[2:len(val)-1]})
// Convert all {param} occurrences to {{.Param}} once at setup time.
tmpl := urlKeysPattern.ReplaceAllStringFunc(val, func(match string) string {
param := match[1 : len(match)-1]
return "{{." + title.String(param[:1]) + param[1:] + "}}"
})
if tmpl != val {
templates = append(templates, [2]string{k, tmpl})
}
}

if len(replacements) == 0 {
if len(templates) == 0 {
b, _ := json.Marshal(opt.GraphQLRequest)

return &Extractor{
Expand All @@ -128,8 +140,13 @@ func New(opt Options) *Extractor {
for k, v := range opt.Variables {
val.Variables[k] = v
}
for _, vs := range replacements {
val.Variables[vs[0]] = params[vs[1]]
// Per-request: plain strings.ReplaceAll, no regex — same as proxy.GeneratePath.
for _, tmpl := range templates {
buff := tmpl[1]
for k, v := range params {
buff = strings.ReplaceAll(buff, "{{."+k+"}}", v)
}
val.Variables[tmpl[0]] = buff
}
return &val, nil
}
Expand Down
60 changes: 60 additions & 0 deletions transport/http/client/graphql/graphql_benchmark_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0

package graphql

import (
"testing"

"github.com/luraproject/lura/v2/config"
)

func BenchmarkExtractor_BodyFromParams(b *testing.B) {
params := map[string]string{
"Owner": "krakend",
"Repo": "lura",
}

for _, tc := range []struct {
name string
variables map[string]interface{}
}{
{
name: "no_params",
variables: map[string]interface{}{"static": "no-params-here"},
},
{
name: "single_param",
variables: map[string]interface{}{"owner": "{owner}"},
},
{
name: "compound_params",
variables: map[string]interface{}{
"query": "repo:{owner}/{repo} category:Announcements",
},
},
{
name: "mixed",
variables: map[string]interface{}{
"single": "{owner}",
"compound": "repo:{owner}/{repo} category:Announcements",
"static": "no-params-here",
},
},
} {
cfg, _ := GetOptions(config.ExtraConfig{
Namespace: map[string]interface{}{
"type": OperationQuery,
"query": "{ search(query: $query) { nodes { id } } }",
"variables": tc.variables,
},
})
extractor := New(*cfg)

b.Run(tc.name, func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
extractor.BodyFromParams(params)
}
})
}
}
30 changes: 30 additions & 0 deletions transport/http/client/graphql/graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,36 @@ func ExampleExtractor_fromFile() {

}

func ExampleExtractor_multipleParamsInVariable() {
cfg, err := GetOptions(config.ExtraConfig{
Namespace: map[string]interface{}{
"type": OperationQuery,
"query": "{\n search(query: $query) {\n nodes { id }\n }\n}\n",
"variables": map[string]interface{}{
"query": "repo:{owner}/{repo} category:Announcements",
},
},
})
if err != nil {
fmt.Println(err)
return
}
extractor := New(*cfg)

body, err := extractor.BodyFromParams(map[string]string{
"Owner": "krakend",
"Repo": "lura",
})
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))

// output:
// {"query":"{\n search(query: $query) {\n nodes { id }\n }\n}\n","variables":{"query":"repo:krakend/lura category:Announcements"}}
}

func ExampleExtractor_noReplacement() {
cfg, err := GetOptions(config.ExtraConfig{
Namespace: map[string]interface{}{
Expand Down
Loading