Repository: jensneuse/graphql-go-tools
Pull request #212 (2020-05-14 06:54:57)

Update graphql contract


Patch:
diff --git a/pkg/execution/datasource/datasource.go b/pkg/execution/datasource/datasource.go
index d08c465b..8e37cbd0 100644
--- a/pkg/execution/datasource/datasource.go
+++ b/pkg/execution/datasource/datasource.go
@@ -4,14 +4,35 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"io"
+	"net/http"
+	"strings"
+	"time"
+
 	"github.com/jensneuse/abstractlogger"
+
 	"github.com/jensneuse/graphql-go-tools/pkg/ast"
 	"github.com/jensneuse/graphql-go-tools/pkg/astparser"
 	"github.com/jensneuse/graphql-go-tools/pkg/asttransform"
 	"github.com/jensneuse/graphql-go-tools/pkg/astvisitor"
-	"io"
 )
 
+var defaultHttpClient *http.Client
+
+func DefaultHttpClient() *http.Client {
+	if defaultHttpClient == nil {
+		defaultHttpClient = &http.Client{
+			Timeout: time.Second * 10,
+			Transport: &http.Transport{
+				MaxIdleConnsPerHost: 1024,
+				TLSHandshakeTimeout: 0 * time.Second,
+			},
+		}
+	}
+
+	return defaultHttpClient
+}
+
 type ResolverArgs interface {
 	ByKey(key []byte) []byte
 	Dump() []string
@@ -131,7 +152,7 @@ type MappingConfiguration struct {
 
 func (p *PlannerConfiguration) DataSourcePlannerFactoryForTypeField(typeName, fieldName string) PlannerFactory {
 	for i := range p.TypeFieldConfigurations {
-		if p.TypeFieldConfigurations[i].TypeName == typeName && p.TypeFieldConfigurations[i].FieldName == fieldName {
+		if strings.EqualFold(p.TypeFieldConfigurations[i].TypeName, typeName) && strings.EqualFold(p.TypeFieldConfigurations[i].FieldName, fieldName) {
 			return p.TypeFieldConfigurations[i].DataSourcePlannerFactory
 		}
 	}
@@ -140,7 +161,7 @@ func (p *PlannerConfiguration) DataSourcePlannerFactoryForTypeField(typeName, fi
 
 func (p *PlannerConfiguration) MappingForTypeField(typeName, fieldName string) *MappingConfiguration {
 	for i := range p.TypeFieldConfigurations {
-		if p.TypeFieldConfigurations[i].TypeName == typeName && p.TypeFieldConfigurations[i].FieldName == fieldName {
+		if strings.EqualFold(p.TypeFieldConfigurations[i].TypeName, typeName) && strings.EqualFold(p.TypeFieldConfigurations[i].FieldName, fieldName) {
 			return p.TypeFieldConfigurations[i].Mapping
 		}
 	}
diff --git a/pkg/execution/datasource/datasource_graphql.go b/pkg/execution/datasource/datasource_graphql.go
index ca9e2f82..4fc9389c 100644
--- a/pkg/execution/datasource/datasource_graphql.go
+++ b/pkg/execution/datasource/datasource_graphql.go
@@ -4,18 +4,19 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"strings"
+
 	"github.com/buger/jsonparser"
 	log "github.com/jensneuse/abstractlogger"
+
 	"github.com/jensneuse/graphql-go-tools/internal/pkg/unsafebytes"
 	"github.com/jensneuse/graphql-go-tools/pkg/ast"
 	"github.com/jensneuse/graphql-go-tools/pkg/astimport"
 	"github.com/jensneuse/graphql-go-tools/pkg/astprinter"
 	"github.com/jensneuse/graphql-go-tools/pkg/lexer/literal"
-	"io"
-	"io/ioutil"
-	"net/http"
-	"strings"
-	"time"
 )
 
 type GraphqlRequest struct {
@@ -40,13 +41,24 @@ type GraphQLDataSourcePlanner struct {
 	nodes                   []ast.Node
 	resolveDocument         *ast.Document
 	dataSourceConfiguration GraphQLDataSourceConfig
+	client                  *http.Client
+}
+
+type GraphQLDataSourcePlannerFactoryFactory struct {
+	Client *http.Client
 }
 
-type GraphQLDataSourcePlannerFactoryFactory struct{}
+func (g *GraphQLDataSourcePlannerFactoryFactory) httpClient() *http.Client {
+	if g.Client != nil {
+		return g.Client
+	}
+	return DefaultHttpClient()
+}
 
 func (g GraphQLDataSourcePlannerFactoryFactory) Initialize(base BasePlanner, configReader io.Reader) (PlannerFactory, error) {
 	factory := &GraphQLDataSourcePlannerFactory{
-		base: base,
+		base:   base,
+		client: g.httpClient(),
 	}
 	err := json.NewDecoder(configReader).Decode(&factory.config)
 	return factory, err
@@ -55,6 +67,7 @@ func (g GraphQLDataSourcePlannerFactoryFactory) Initialize(base BasePlanner, con
 type GraphQLDataSourcePlannerFactory struct {
 	base   BasePlanner
 	config GraphQLDataSourceConfig
+	client *http.Client
 }
 
 func (g *GraphQLDataSourcePlannerFactory) DataSourcePlanner() Planner {
@@ -63,6 +76,7 @@ func (g *GraphQLDataSourcePlannerFactory) DataSourcePlanner() Planner {
 		importer:                &astimport.Importer{},
 		dataSourceConfiguration: g.config,
 		resolveDocument:         &ast.Document{},
+		client:                  g.client,
 	}
 }
 
@@ -265,12 +279,14 @@ func (g *GraphQLDataSourcePlanner) Plan(args []Argument) (DataSource, []Argument
 		}
 	}
 	return &GraphQLDataSource{
-		Log: g.Log,
+		Log:    g.Log,
+		Client: g.client,
 	}, g.Args
 }
 
 type GraphQLDataSource struct {
-	Log log.Logger
+	Log    log.Logger
+	Client *http.Client
 }
 
 func (g *GraphQLDataSource) Resolve(ctx context.Context, args ResolverArgs, out io.Writer) (n int, err error) {
@@ -332,14 +348,6 @@ func (g *GraphQLDataSource) Resolve(ctx context.Context, args ResolverArgs, out
 		log.ByteString("data", gqlRequestData),
 	)
 
-	client := http.Client{
-		Timeout: time.Second * 10,
-		Transport: &http.Transport{
-			MaxIdleConnsPerHost: 1024,
-			TLSHandshakeTimeout: 0 * time.Second,
-		},
-	}
-
 	request, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(gqlRequestData))
 	if err != nil {
 		g.Log.Error("GraphQLDataSource.http.NewRequest",
@@ -351,7 +359,7 @@ func (g *GraphQLDataSource) Resolve(ctx context.Context, args ResolverArgs, out
 	request.Header.Add("Content-Type", "application/json")
 	request.Header.Add("Accept", "application/json")
 
-	res, err := client.Do(request)
+	res, err := g.Client.Do(request)
 	if err != nil {
 		g.Log.Error("GraphQLDataSource.Client.Do",
 			log.Error(err),
diff --git a/pkg/execution/datasource/datasource_http_json.go b/pkg/execution/datasource/datasource_http_json.go
index d03d163a..4c417a73 100644
--- a/pkg/execution/datasource/datasource_http_json.go
+++ b/pkg/execution/datasource/datasource_http_json.go
@@ -10,7 +10,6 @@ import (
 	"net/http"
 	"strconv"
 	"strings"
-	"time"
 
 	"github.com/buger/jsonparser"
 	log "github.com/jensneuse/abstractlogger"
@@ -43,16 +42,6 @@ type HttpJsonDataSourceConfig struct {
 	StatusCodeTypeNameMappings []StatusCodeTypeNameMapping
 }
 
-func DefaultHttpClient() *http.Client {
-	return &http.Client{
-		Timeout: time.Second * 10,
-		Transport: &http.Transport{
-			MaxIdleConnsPerHost: 1024,
-			TLSHandshakeTimeout: 0 * time.Second,
-		},
-	}
-}
-
 type StatusCodeTypeNameMapping struct {
 	StatusCode int
 	TypeName   string
@@ -64,12 +53,12 @@ type HttpJsonDataSourceConfigHeader struct {
 }
 
 type HttpJsonDataSourcePlannerFactoryFactory struct {
-	client *http.Client
+	Client *http.Client
 }
 
 func (h *HttpJsonDataSourcePlannerFactoryFactory) httpClient() *http.Client {
-	if h.client != nil {
-		return h.client
+	if h.Client != nil {
+		return h.Client
 	}
 	return DefaultHttpClient()
 }
diff --git a/pkg/execution/handler.go b/pkg/execution/handler.go
index ba3a36ee..2fdde3df 100644
--- a/pkg/execution/handler.go
+++ b/pkg/execution/handler.go
@@ -45,7 +45,7 @@ func (h *Handler) Handle(requestData, extraVariables []byte) (executor *Executor
 		return
 	}
 
-	variables, extraArguments := h.VariablesFromJson(graphqlRequest.Variables, extraVariables)
+	variables, extraArguments := VariablesFromJson(graphqlRequest.Variables, extraVariables)
 
 	planner := NewPlanner(h.base)
 	if report.HasErrors() {
@@ -90,7 +90,7 @@ func (h *Handler) Handle(requestData, extraVariables []byte) (executor *Executor
 	return executor, plan, ctx, err
 }
 
-func (h *Handler) VariablesFromJson(requestVariables, extraVariables []byte) (variables Variables, extraArguments []datasource.Argument) {
+func VariablesFromJson(requestVariables, extraVariables []byte) (variables Variables, extraArguments []datasource.Argument) {
 	variables = map[uint64][]byte{}
 	_ = jsonparser.ObjectEach(requestVariables, func(key []byte, value []byte, dataType jsonparser.ValueType, offset int) error {
 		variables[xxhash.Sum64(key)] = value
diff --git a/pkg/graphql/execution_engine.go b/pkg/graphql/execution_engine.go
new file mode 100644
index 00000000..031574bc
--- /dev/null
+++ b/pkg/graphql/execution_engine.go
@@ -0,0 +1,140 @@
+package graphql
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io"
+	"io/ioutil"
+	"net/http"
+
+	"github.com/jensneuse/abstractlogger"
+
+	"github.com/jensneuse/graphql-go-tools/pkg/execution"
+	"github.com/jensneuse/graphql-go-tools/pkg/execution/datasource"
+	"github.com/jensneuse/graphql-go-tools/pkg/operationreport"
+)
+
+type DataSourceHttpJsonOptions struct {
+	HttpClient *http.Client
+}
+
+type DataSourceGraphqlOptions struct {
+	HttpClient *http.Client
+}
+
+type ExecutionOptions struct {
+	ExtraArguments json.RawMessage
+}
+
+type ExecutionEngine struct {
+	logger      abstractlogger.Logger
+	basePlanner *datasource.BasePlanner
+	executor    *execution.Executor
+	schema      *Schema
+}
+
+func NewExecutionEngine(logger abstractlogger.Logger, schema *Schema, plannerConfig datasource.PlannerConfiguration) (*ExecutionEngine, error) {
+	executor := execution.NewExecutor(nil)
+	basePlanner, err := datasource.NewBaseDataSourcePlanner(schema.rawInput, plannerConfig, logger)
+	if err != nil {
+		return nil, err
+	}
+
+	return &ExecutionEngine{
+		logger:      logger,
+		basePlanner: basePlanner,
+		executor:    executor,
+		schema:      schema,
+	}, nil
+}
+
+func (e *ExecutionEngine) AddHttpJsonDataSource(name string) error {
+	return e.AddHttpJsonDataSourceWithOptions(name, DataSourceHttpJsonOptions{})
+}
+
+func (e *ExecutionEngine) AddHttpJsonDataSourceWithOptions(name string, options DataSourceHttpJsonOptions) error {
+	httpJsonFactoryFactory := &datasource.HttpJsonDataSourcePlannerFactoryFactory{}
+
+	if options.HttpClient != nil {
+		httpJsonFactoryFactory.Client = options.HttpClient
+	}
+
+	return e.AddDataSource(name, httpJsonFactoryFactory)
+}
+
+func (e *ExecutionEngine) AddGraphqlDataSource(name string) error {
+	return e.AddGraphqlDataSourceWithOptions(name, DataSourceGraphqlOptions{})
+}
+
+func (e *ExecutionEngine) AddGraphqlDataSourceWithOptions(name string, options DataSourceGraphqlOptions) error {
+	graphqlFactoryFactory := &datasource.GraphQLDataSourcePlannerFactoryFactory{}
+
+	if options.HttpClient != nil {
+		graphqlFactoryFactory.Client = options.HttpClient
+	}
+
+	return e.AddDataSource(name, graphqlFactoryFactory)
+}
+
+func (e *ExecutionEngine) AddDataSource(name string, plannerFactoryFactory datasource.PlannerFactoryFactory) error {
+	return e.basePlanner.RegisterDataSourcePlannerFactory(name, plannerFactoryFactory)
+}
+
+func (e *ExecutionEngine) ExecuteWithWriter(ctx context.Context, operation *Request, writer io.Writer, options ExecutionOptions) error {
+	var report operationreport.Report
+	if !operation.IsNormalized() {
+		normalizationResult, err := operation.Normalize(e.schema)
+		if err != nil {
+			return err
+		}
+
+		if !normalizationResult.Successful {
+			return normalizationResult.Errors
+		}
+	}
+
+	planner := execution.NewPlanner(e.basePlanner)
+	plan := planner.Plan(&operation.document, e.basePlanner.Definition, &report)
+	if report.HasErrors() {
+		return report
+	}
+
+	variables, extraArguments := execution.VariablesFromJson(operation.Variables, options.ExtraArguments)
+	executionContext := execution.Context{
+		Context:        ctx,
+		Variables:      variables,
+		ExtraArguments: extraArguments,
+	}
+
+	return e.executor.Execute(executionContext, plan, writer)
+}
+
+func (e *ExecutionEngine) Execute(ctx context.Context, operation *Request, options ExecutionOptions) (*ExecutionResult, error) {
+	var buf bytes.Buffer
+	err := e.ExecuteWithWriter(ctx, operation, &buf, options)
+	return &ExecutionResult{&buf}, err
+}
+
+type ExecutionResult struct {
+	buf *bytes.Buffer
+}
+
+func (r *ExecutionResult) Buffer() *bytes.Buffer {
+	return r.buf
+}
+
+func (r *ExecutionResult) GetAsHTTPResponse() (res *http.Response) {
+	if r.buf == nil {
+		return
+	}
+
+	res = &http.Response{}
+	res.Body = ioutil.NopCloser(r.buf)
+	res.Header = make(http.Header)
+	res.StatusCode = 200
+
+	res.Header.Set("Content-Type", "application/json")
+
+	return
+}


Test patch:
diff --git a/pkg/execution/execution_test.go b/pkg/execution/execution_test.go
index 9f7fbf25..9d15f0e0 100644
--- a/pkg/execution/execution_test.go
+++ b/pkg/execution/execution_test.go
@@ -134,7 +134,8 @@ func TestExecution(t *testing.T) {
 										},
 									},
 									DataSource: &datasource.GraphQLDataSource{
-										Log: log.NoopLogger,
+										Log:    log.NoopLogger,
+										Client: datasource.DefaultHttpClient(),
 									},
 								},
 								BufferName: "user",
@@ -248,7 +249,7 @@ func TestExecution(t *testing.T) {
 													},
 												},
 												DataSource: &datasource.HttpJsonDataSource{
-													Log: log.NoopLogger,
+													Log:    log.NoopLogger,
 													Client: datasource.DefaultHttpClient(),
 												},
 											},
@@ -277,7 +278,8 @@ func TestExecution(t *testing.T) {
 													},
 												},
 												DataSource: &datasource.GraphQLDataSource{
-													Log: log.NoopLogger,
+													Log:    log.NoopLogger,
+													Client: datasource.DefaultHttpClient(),
 												},
 											},
 											BufferName: "pets",
@@ -346,7 +348,7 @@ func TestExecution(t *testing.T) {
 															},
 														},
 														DataSource: &datasource.HttpJsonDataSource{
-															Log: log.NoopLogger,
+															Log:    log.NoopLogger,
 															Client: datasource.DefaultHttpClient(),
 														},
 													},
@@ -1333,7 +1335,7 @@ func TestExecutor_ObjectVariables(t *testing.T) {
 											},
 										},
 										DataSource: &datasource.HttpJsonDataSource{
-											Log: log.NoopLogger,
+											Log:    log.NoopLogger,
 											Client: datasource.DefaultHttpClient(),
 										},
 									},
@@ -1461,7 +1463,7 @@ func TestExecutor_NestedObjectVariables(t *testing.T) {
 								},
 							},
 							DataSource: &datasource.HttpJsonDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
 								Client: datasource.DefaultHttpClient(),
 							},
 						},
@@ -1498,7 +1500,7 @@ func TestExecutor_NestedObjectVariables(t *testing.T) {
 														},
 													},
 													DataSource: &datasource.HttpJsonDataSource{
-														Log: log.NoopLogger,
+														Log:    log.NoopLogger,
 														Client: datasource.DefaultHttpClient(),
 													},
 												},
@@ -1676,7 +1678,8 @@ func TestExecutor_GraphqlDataSourceWithParams(t *testing.T) {
 								},
 							},
 							DataSource: &datasource.GraphQLDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
+								Client: datasource.DefaultHttpClient(),
 							},
 						},
 						BufferName: "countries",
@@ -2078,7 +2081,7 @@ func TestExecutor_HTTPJSONDataSourceWithBody(t *testing.T) {
 						BufferName: "withBody",
 						Source: &DataSourceInvocation{
 							DataSource: &datasource.HttpJsonDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
 								Client: datasource.DefaultHttpClient(),
 							},
 							Args: []datasource.Argument{
@@ -2498,7 +2501,7 @@ func TestExecutor_HTTPJSONDataSourceWithBodyComplexPlayload(t *testing.T) {
 						BufferName: "withBody",
 						Source: &DataSourceInvocation{
 							DataSource: &datasource.HttpJsonDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
 								Client: datasource.DefaultHttpClient(),
 							},
 							Args: []datasource.Argument{
@@ -2601,7 +2604,7 @@ func TestExecutor_HTTPJSONDataSourceWithHeaders(t *testing.T) {
 						BufferName: "withHeaders",
 						Source: &DataSourceInvocation{
 							DataSource: &datasource.HttpJsonDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
 								Client: datasource.DefaultHttpClient(),
 							},
 							Args: []datasource.Argument{
@@ -2707,7 +2710,7 @@ func TestExecutor_HTTPJSONDataSourceWithPathSelector(t *testing.T) {
 						BufferName: "friends",
 						Source: &DataSourceInvocation{
 							DataSource: &datasource.HttpJsonDataSource{
-								Log: log.NoopLogger,
+								Log:    log.NoopLogger,
 								Client: datasource.DefaultHttpClient(),
 							},
 							Args: []datasource.Argument{
diff --git a/pkg/execution/handler_test.go b/pkg/execution/handler_test.go
index c55979f7..bf1cd464 100644
--- a/pkg/execution/handler_test.go
+++ b/pkg/execution/handler_test.go
@@ -2,28 +2,21 @@ package execution
 
 import (
 	"bytes"
+	"testing"
+
 	"github.com/cespare/xxhash"
-	log "github.com/jensneuse/abstractlogger"
+
 	"github.com/jensneuse/graphql-go-tools/pkg/execution/datasource"
-	"testing"
 )
 
 func TestHandler_VariablesFromRequest(t *testing.T) {
-
-	base, err := datasource.NewBaseDataSourcePlanner(nil, datasource.PlannerConfiguration{}, log.NoopLogger)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	handler := NewHandler(base, nil)
-
 	request := GraphqlRequest{
 		Variables: []byte(`{"foo":"bar"}`),
 	}
 
 	extra := []byte(`{"request":{"headers":{"Authorization":"Bearer foo123"}}}`)
 
-	variables, extraArguments := handler.VariablesFromJson(request.Variables, extra)
+	variables, extraArguments := VariablesFromJson(request.Variables, extra)
 
 	for key, value := range map[string]string{
 		"foo":     "bar",
diff --git a/pkg/execution/planning_test.go b/pkg/execution/planning_test.go
index aa36452a..d00f12ea 100644
--- a/pkg/execution/planning_test.go
+++ b/pkg/execution/planning_test.go
@@ -147,7 +147,8 @@ func TestPlanner_Plan(t *testing.T) {
 									},
 								},
 								DataSource: &datasource.GraphQLDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
+									Client: datasource.DefaultHttpClient(),
 								},
 							},
 							BufferName: "country",
@@ -264,7 +265,8 @@ func TestPlanner_Plan(t *testing.T) {
 									},
 								},
 								DataSource: &datasource.GraphQLDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
+									Client: datasource.DefaultHttpClient(),
 								},
 							},
 							BufferName: "likePost",
@@ -457,7 +459,7 @@ func TestPlanner_Plan(t *testing.T) {
 											},
 										},
 										DataSource: &datasource.HttpJsonDataSource{
-											Log: log.NoopLogger,
+											Log:    log.NoopLogger,
 											Client: datasource.DefaultHttpClient(),
 										},
 									},
@@ -544,7 +546,7 @@ func TestPlanner_Plan(t *testing.T) {
 												},
 											},
 											DataSource: &datasource.HttpJsonDataSource{
-												Log: log.NoopLogger,
+												Log:    log.NoopLogger,
 												Client: datasource.DefaultHttpClient(),
 											},
 										},
@@ -630,7 +632,7 @@ func TestPlanner_Plan(t *testing.T) {
 							BufferName: "withBody",
 							Source: &DataSourceInvocation{
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 								Args: []datasource.Argument{
@@ -706,7 +708,7 @@ func TestPlanner_Plan(t *testing.T) {
 							BufferName: "withPath",
 							Source: &DataSourceInvocation{
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 								Args: []datasource.Argument{
@@ -781,7 +783,7 @@ func TestPlanner_Plan(t *testing.T) {
 							BufferName: "listItems",
 							Source: &DataSourceInvocation{
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 								Args: []datasource.Argument{
@@ -869,7 +871,7 @@ func TestPlanner_Plan(t *testing.T) {
 							BufferName: "listWithPath",
 							Source: &DataSourceInvocation{
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 								Args: []datasource.Argument{
@@ -970,7 +972,7 @@ func TestPlanner_Plan(t *testing.T) {
 							BufferName: "withHeaders",
 							Source: &DataSourceInvocation{
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 								Args: []datasource.Argument{
@@ -1324,7 +1326,8 @@ func TestPlanner_Plan(t *testing.T) {
 									},
 								},
 								DataSource: &datasource.GraphQLDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
+									Client: datasource.DefaultHttpClient(),
 								},
 							},
 							BufferName: "user",
@@ -1441,7 +1444,7 @@ func TestPlanner_Plan(t *testing.T) {
 									},
 								},
 								DataSource: &datasource.HttpJsonDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
 									Client: datasource.DefaultHttpClient(),
 								},
 							},
@@ -1495,6 +1498,7 @@ func TestPlanner_Plan(t *testing.T) {
 			},
 		},
 	))
+
 	t.Run("graphql resolver with nested rest resolver", run(withBaseSchema(complexSchema), `
 			query UserQuery($id: String!) {
 				user(id: $id) {
@@ -1572,7 +1576,8 @@ func TestPlanner_Plan(t *testing.T) {
 									},
 								},
 								DataSource: &datasource.GraphQLDataSource{
-									Log: log.NoopLogger,
+									Log:    log.NoopLogger,
+									Client: datasource.DefaultHttpClient(),
 								},
 							},
 							BufferName: "user",
@@ -1608,7 +1613,7 @@ func TestPlanner_Plan(t *testing.T) {
 												},
 											},
 											DataSource: &datasource.HttpJsonDataSource{
-												Log: log.NoopLogger,
+												Log:    log.NoopLogger,
 												Client: datasource.DefaultHttpClient(),
 											},
 										},
diff --git a/pkg/graphql/execution_engine_test.go b/pkg/graphql/execution_engine_test.go
new file mode 100644
index 00000000..c61d437d
--- /dev/null
+++ b/pkg/graphql/execution_engine_test.go
@@ -0,0 +1,152 @@
+package graphql
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"io/ioutil"
+	"net/http"
+	"testing"
+
+	"github.com/jensneuse/abstractlogger"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	"github.com/jensneuse/graphql-go-tools/pkg/execution/datasource"
+	"github.com/jensneuse/graphql-go-tools/pkg/starwars"
+)
+
+type testRoundTripper func(req *http.Request) *http.Response
+
+func (t testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
+	return t(req), nil
+}
+
+func TestExecutionEngine_ExecuteWithOptions(t *testing.T) {
+	schema := starwarsSchema(t)
+	extraVariables := map[string]string{
+		"request": `{"Authorization": "Bearer ey123"}`,
+	}
+	extraVariablesBytes, err := json.Marshal(extraVariables)
+	require.NoError(t, err)
+
+	t.Run("execute with custom roundtripper for simple hero query on HttpJsonDatasource", func(t *testing.T) {
+		query := starwars.LoadQuery(t, starwars.FileSimpleHeroQuery, nil)
+		request := Request{}
+		err := UnmarshalRequest(bytes.NewBuffer(query), &request)
+		require.NoError(t, err)
+
+		plannerConfig := datasource.PlannerConfiguration{
+			TypeFieldConfigurations: []datasource.TypeFieldConfiguration{
+				{
+					TypeName:  "Query",
+					FieldName: "hero",
+					Mapping: &datasource.MappingConfiguration{
+						Disabled: false,
+						Path:     "hero",
+					},
+					DataSource: datasource.SourceConfig{
+						Name: "HttpJsonDataSource",
+						Config: func() []byte {
+							data, _ := json.Marshal(datasource.HttpJsonDataSourceConfig{
+								Host: "example.com",
+								URL:  "/",
+								Method: func() *string {
+									method := "GET"
+									return &method
+								}(),
+								DefaultTypeName: func() *string {
+									typeName := "Hero"
+									return &typeName
+								}(),
+							})
+							return data
+						}(),
+					},
+				},
+			},
+		}
+
+		roundTripper := testRoundTripper(func(req *http.Request) *http.Response {
+			assert.Equal(t, "example.com", req.URL.Host)
+			assert.Equal(t, "/", req.URL.Path)
+
+			body := bytes.NewBuffer([]byte(`{"hero": {"name": "Luke Skywalker"}}`))
+			return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(body)}
+		})
+
+		httpJsonOptions := DataSourceHttpJsonOptions{
+			HttpClient: &http.Client{
+				Transport: roundTripper,
+			},
+		}
+
+		engine, err := NewExecutionEngine(abstractlogger.NoopLogger, schema, plannerConfig)
+		assert.NoError(t, err)
+
+		err = engine.AddHttpJsonDataSourceWithOptions("HttpJsonDataSource", httpJsonOptions)
+		assert.NoError(t, err)
+
+		executionRes, err := engine.Execute(context.Background(), &request, ExecutionOptions{ExtraArguments: extraVariablesBytes})
+		assert.NoError(t, err)
+		assert.Equal(t, `{"data":{"hero":{"name":"Luke Skywalker"}}}`, executionRes.Buffer().String())
+	})
+
+	t.Run("execute with custom roundtripper for simple hero query on GraphqlDataSource", func(t *testing.T) {
+		query := starwars.LoadQuery(t, starwars.FileSimpleHeroQuery, nil)
+		request := Request{}
+		err := UnmarshalRequest(bytes.NewBuffer(query), &request)
+		require.NoError(t, err)
+
+		plannerConfig := datasource.PlannerConfiguration{
+			TypeFieldConfigurations: []datasource.TypeFieldConfiguration{
+				{
+					TypeName:  "query",
+					FieldName: "hero",
+					Mapping: &datasource.MappingConfiguration{
+						Disabled: false,
+						Path:     "hero",
+					},
+					DataSource: datasource.SourceConfig{
+						Name: "GraphqlDataSource",
+						Config: func() []byte {
+							data, _ := json.Marshal(datasource.GraphQLDataSourceConfig{
+								Host: "example.com",
+								URL:  "/",
+								Method: func() *string {
+									method := "GET"
+									return &method
+								}(),
+							})
+							return data
+						}(),
+					},
+				},
+			},
+		}
+
+		roundTripper := testRoundTripper(func(req *http.Request) *http.Response {
+			assert.Equal(t, "example.com", req.URL.Host)
+			assert.Equal(t, "/", req.URL.Path)
+
+			body := bytes.NewBuffer([]byte(`{"data":{"hero":{"name":"Luke Skywalker"}}}`))
+			return &http.Response{StatusCode: 200, Body: ioutil.NopCloser(body)}
+		})
+
+		graphqlOptions := DataSourceGraphqlOptions{
+			HttpClient: &http.Client{
+				Transport: roundTripper,
+			},
+		}
+
+		engine, err := NewExecutionEngine(abstractlogger.NoopLogger, schema, plannerConfig)
+		assert.NoError(t, err)
+
+		err = engine.AddGraphqlDataSourceWithOptions("GraphqlDataSource", graphqlOptions)
+		assert.NoError(t, err)
+
+		executionRes, err := engine.Execute(context.Background(), &request, ExecutionOptions{ExtraArguments: extraVariablesBytes})
+		assert.NoError(t, err)
+		assert.Equal(t, `{"data":{"hero":{"name":"Luke Skywalker"}}}`, executionRes.Buffer().String())
+	})
+}