diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..4672f19 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.hasura-connector/ +*.hml +.github/ +assets/ \ No newline at end of file diff --git a/.hasura-connector/Dockerfile b/.hasura-connector/Dockerfile index 70e3ef6..ab4dc96 100644 --- a/.hasura-connector/Dockerfile +++ b/.hasura-connector/Dockerfile @@ -1,17 +1,3 @@ -# build context at repo root: docker build -f Dockerfile . -FROM golang:1.22 AS builder +FROM ghcr.io/hasura/ndc-rest:${VERSION} -WORKDIR /app -COPY . . -RUN CGO_ENABLED=0 go build -v -o ndc-cli . - -# stage 2: production image -FROM gcr.io/distroless/base-debian12:nonroot - -# Copy the binary to the production image from the builder stage. -COPY --from=builder /app/ndc-cli /ndc-cli - -ENV HASURA_CONFIGURATION_DIRECTORY /config - -# Run the web service on container startup. -CMD ["/ndc-cli", "serve"] \ No newline at end of file +COPY ./config /config diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f66fcf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +# build context at repo root: docker build -f Dockerfile . +FROM golang:1.22 AS builder + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -v -o ndc-cli . + +# stage 2: production image +FROM gcr.io/distroless/static-debian12:nonroot + +# Copy the binary to the production image from the builder stage. +COPY --from=builder /app/ndc-cli /ndc-cli + +ENV HASURA_CONFIGURATION_DIRECTORY /config + +# Run the web service on container startup. +CMD ["/ndc-cli", "serve"] \ No newline at end of file diff --git a/go.mod b/go.mod index 512f6d7..a798641 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,10 @@ go 1.21 toolchain go1.22.0 require ( - github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217 - github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3 + github.com/hasura/ndc-rest-schema v0.0.0-20240328031759-67ecd08d8e10 + github.com/hasura/ndc-sdk-go v1.0.0 github.com/lmittmann/tint v1.0.4 + github.com/stretchr/testify v1.9.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -16,8 +17,9 @@ require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -26,9 +28,10 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pb33f/libopenapi v0.15.14 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v1.19.0 // indirect github.com/prometheus/client_model v0.6.0 // indirect - github.com/prometheus/common v0.50.0 // indirect + github.com/prometheus/common v0.51.1 // indirect github.com/prometheus/procfs v0.13.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect @@ -44,13 +47,13 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.24.0 // indirect go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.1.0 // indirect - golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 // indirect + golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect golang.org/x/net v0.22.0 // indirect golang.org/x/sync v0.6.0 // indirect golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa // indirect google.golang.org/grpc v1.62.1 // indirect google.golang.org/protobuf v1.33.0 // indirect ) diff --git a/go.sum b/go.sum index d04e8de..ad19859 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -54,12 +54,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= -github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855 h1:d3bq7jjsK/aPcFih6B+h1uM3CkNY24F8Eo5JXx8QBo8= -github.com/hasura/ndc-rest-schema v0.0.0-20240320034216-4c2976a29855/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg= -github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217 h1:HhfRPEHzEpBO2j55oHV7r+uBQcg2KMgWlmEtp3TvWO4= -github.com/hasura/ndc-rest-schema v0.0.0-20240321015327-71425bf24217/go.mod h1:R1xvYOx/TqgHEiP5Nm8qTRwFfJmxmV1y1De4b1i1CFg= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3 h1:A1N3ilX1EIxjTA2qaHPXFnIECpYjKhIFmtva8qJrpHk= -github.com/hasura/ndc-sdk-go v0.1.1-0.20240317172640-9c7a7adc1cd3/go.mod h1:EeM3hKbhCfBjDDva8mP4D2KeptTqAaxNqNw8rFQAnMs= +github.com/hasura/ndc-rest-schema v0.0.0-20240328031759-67ecd08d8e10 h1:7xo9cOlToNgDYy1mx9JsQIB0zHdLeTM4rz/l3n61JXk= +github.com/hasura/ndc-rest-schema v0.0.0-20240328031759-67ecd08d8e10/go.mod h1:nK17RPx2CZd92WqdZJrsNLzMKIWlCwssr3Wqvq7iM6w= +github.com/hasura/ndc-sdk-go v1.0.0 h1:vLbv1k1UIkIXUUrNzi3B4QvAqT1/q2xxlrzff8+Z6+E= +github.com/hasura/ndc-sdk-go v1.0.0/go.mod h1:EeM3hKbhCfBjDDva8mP4D2KeptTqAaxNqNw8rFQAnMs= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= @@ -99,8 +97,8 @@ github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7km github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8= -github.com/prometheus/common v0.50.0 h1:YSZE6aa9+luNa2da6/Tik0q0A5AbR+U003TItK57CPQ= -github.com/prometheus/common v0.50.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ= +github.com/prometheus/common v0.51.1 h1:eIjN50Bwglz6a/c3hAgSMcofL3nD+nFQkV6Dd4DsQCw= +github.com/prometheus/common v0.51.1/go.mod h1:lrWtQx+iDfn2mbH5GUzlH9TSHyfZpHkSiG1W7y3sF2Q= github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o= github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= @@ -113,6 +111,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= @@ -147,8 +147,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc= -golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw= +golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -195,10 +195,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c h1:kaI7oewGK5YnVwj+Y+EJBO/YN1ht8iTL9XkFHtVZLsc= -google.golang.org/genproto/googleapis/api v0.0.0-20240314234333-6e1732d8331c/go.mod h1:VQW3tUculP/D4B+xVCo+VgSq8As6wA9ZjHl//pmk+6s= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa h1:Jt1XW5PaLXF1/ePZrznsh/aAUvI7Adfc3LY1dAKlzRs= +google.golang.org/genproto/googleapis/api v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:K4kfzHtI0kqWA79gecJarFtDn/Mls+GxQcg3Zox91Ac= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa h1:RBgMaUMP+6soRkik4VoN8ojR2nex2TqZwjSSogic+eo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240325203815-454cdb8f5daa/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/rest/connector.go b/rest/connector.go index c50ffff..321e25d 100644 --- a/rest/connector.go +++ b/rest/connector.go @@ -36,7 +36,7 @@ func NewRESTConnector(opts ...Option) *RESTConnector { func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir string) (*Configuration, error) { restCapabilities := schema.CapabilitiesResponse{ - Version: "0.1.0", + Version: "0.1.1", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/rest/connector_test.go b/rest/connector_test.go index a137786..d0ddd40 100644 --- a/rest/connector_test.go +++ b/rest/connector_test.go @@ -10,12 +10,11 @@ import ( "net/http/httptest" "os" "path" - "reflect" - "strings" "testing" "github.com/hasura/ndc-sdk-go/connector" "github.com/hasura/ndc-sdk-go/schema" + "github.com/stretchr/testify/assert" ) func TestRESTConnector(t *testing.T) { @@ -28,7 +27,7 @@ func TestRESTConnector(t *testing.T) { Dir: "testdata/jsonplaceholder", }, { - Name: "jsonplaceholder", + Name: "petstore3", Dir: "testdata/petstore3", }, } @@ -50,9 +49,9 @@ func TestRESTConnector(t *testing.T) { } var capabilities schema.CapabilitiesResponse - assertNoError(t, json.Unmarshal(rawBytes, &capabilities)) + assert.NoError(t, json.Unmarshal(rawBytes, &capabilities)) resp, err := http.Get(fmt.Sprintf("%s/capabilities", testServer.URL)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, resp, http.StatusOK, capabilities) }) @@ -68,9 +67,9 @@ func TestRESTConnector(t *testing.T) { } var expected schema.SchemaResponse - assertNoError(t, json.Unmarshal(rawBytes, &expected)) + assert.NoError(t, json.Unmarshal(rawBytes, &expected)) resp, err := http.Get(fmt.Sprintf("%s/schema", testServer.URL)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, resp, http.StatusOK, expected) }) @@ -83,7 +82,7 @@ func TestRESTConnector(t *testing.T) { func TestRESTConnector_configurationFailure(t *testing.T) { c := NewRESTConnector() _, err := c.ParseConfiguration(context.Background(), "") - assertError(t, err, "the config.{json,yaml,yml} file does not exist at") + assert.NoError(t, err, "the config.{json,yaml,yml} file does not exist at") } func TestRESTConnector_authentication(t *testing.T) { @@ -98,7 +97,7 @@ func TestRESTConnector_authentication(t *testing.T) { connServer, err := connector.NewServer(NewRESTConnector(), &connector.ServerOptions{ Configuration: "testdata/auth", }, connector.WithoutRecovery()) - assertNoError(t, err) + assert.NoError(t, err) testServer := connServer.BuildTestServer() defer testServer.Close() @@ -118,7 +117,7 @@ func TestRESTConnector_authentication(t *testing.T) { }`) res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{ { Rows: []map[string]any{ @@ -141,7 +140,7 @@ func TestRESTConnector_authentication(t *testing.T) { }`) res, err := http.Post(fmt.Sprintf("%s/mutation", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, res, http.StatusOK, schema.MutationResponse{ OperationResults: []schema.MutationOperationResults{ schema.NewProcedureResult(map[string]any{}).Encode(), @@ -165,7 +164,7 @@ func TestRESTConnector_authentication(t *testing.T) { }`) res, err := http.Post(fmt.Sprintf("%s/query", testServer.URL), "application/json", bytes.NewBuffer(reqBody)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, res, http.StatusOK, schema.QueryResponse{ { Rows: []map[string]any{ @@ -232,14 +231,14 @@ func assertNdcOperations(t *testing.T, dir string, targetURL string) { } t.Run(entry.Name(), func(t *testing.T) { requestBytes, err := os.ReadFile(path.Join(dir, entry.Name(), "request.json")) - assertNoError(t, err) + assert.NoError(t, err) expectedBytes, err := os.ReadFile(path.Join(dir, entry.Name(), "expected.json")) - assertNoError(t, err) + assert.NoError(t, err) var expected any - assertNoError(t, json.Unmarshal(expectedBytes, &expected)) + assert.NoError(t, json.Unmarshal(expectedBytes, &expected)) resp, err := http.Post(targetURL, "application/json", bytes.NewBuffer(requestBytes)) - assertNoError(t, err) + assert.NoError(t, err) assertHTTPResponse(t, resp, http.StatusOK, expected) }) } @@ -258,41 +257,6 @@ func test_createServer(t *testing.T, dir string) *connector.Server[Configuration return server } -func assertNoError(t *testing.T, err error) { - if err != nil { - t.Errorf("expected no error, got: %s", err) - t.FailNow() - } -} - -func assertError(t *testing.T, err error, message string) { - if err == nil { - t.Error("expected error, got nil") - t.FailNow() - } else if !strings.Contains(err.Error(), message) { - t.Errorf("expected error with content: %s, got: %s", err.Error(), message) - t.FailNow() - } -} - -func assertDeepEqual(t *testing.T, expected any, reality any, msgs ...string) { - if reflect.DeepEqual(expected, reality) { - return - } - - expectedJson, _ := json.Marshal(expected) - realityJson, _ := json.Marshal(reality) - - var expected1, reality1 any - assertNoError(t, json.Unmarshal(expectedJson, &expected1)) - assertNoError(t, json.Unmarshal(realityJson, &reality1)) - - if !reflect.DeepEqual(expected1, reality1) { - t.Errorf("%s: not equal.\nexpected: %s\ngot : %s", strings.Join(msgs, " "), string(expectedJson), string(realityJson)) - t.FailNow() - } -} - func assertHTTPResponse[B any](t *testing.T, res *http.Response, statusCode int, expectedBody B) { bodyBytes, err := io.ReadAll(res.Body) if err != nil { @@ -311,5 +275,5 @@ func assertHTTPResponse[B any](t *testing.T, res *http.Response, statusCode int, t.FailNow() } - assertDeepEqual(t, body, expectedBody) + assert.Equal(t, expectedBody, body) } diff --git a/rest/schema.go b/rest/schema.go index e1b0e7a..7b19a4f 100644 --- a/rest/schema.go +++ b/rest/schema.go @@ -22,30 +22,13 @@ func (c *RESTConnector) GetSchema(ctx context.Context, configuration *Configurat return c.schema, nil } -func getEnvVariables() map[string]string { - results := make(map[string]string) - for _, env := range os.Environ() { - if env == "" { - continue - } - keyValues := strings.Split(env, "=") - if len(keyValues) < 2 { - continue - } - value := strings.Trim(strings.Join(keyValues[1:], "="), `"`) - results[keyValues[0]] = value - } - return results -} - // build NDC REST schema from file list func buildSchemaFiles(configDir string, files []SchemaFile, logger *slog.Logger) ([]ndcRestSchemaWithName, map[string][]string) { - envVars := getEnvVariables() schemas := make([]ndcRestSchemaWithName, len(files)) errors := make(map[string][]string) for i, file := range files { var errs []string - schemaOutput, err := buildSchemaFile(configDir, &file, envVars, logger) + schemaOutput, err := buildSchemaFile(configDir, &file, logger) if err != nil { errs = append(errs, err.Error()) } @@ -63,7 +46,7 @@ func buildSchemaFiles(configDir string, files []SchemaFile, logger *slog.Logger) return schemas, errors } -func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]string, logger *slog.Logger) (*rest.NDCRestSchema, error) { +func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (*rest.NDCRestSchema, error) { if conf.Path == "" { return nil, errors.New("file path is empty") } @@ -77,12 +60,6 @@ func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]stri return nil, err } - // replace environment variables - rawString := string(rawBytes) - for key, val := range envVars { - rawString = strings.ReplaceAll(rawString, fmt.Sprintf("{{%s}}", key), val) - } - switch conf.Spec { case rest.NDCSpec: var result rest.NDCRestSchema @@ -92,18 +69,18 @@ func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]stri } switch fileFormat { case rest.SchemaFileJSON: - if err := json.Unmarshal([]byte(rawString), &result); err != nil { + if err := json.Unmarshal(rawBytes, &result); err != nil { return nil, err } - return &result, nil case rest.SchemaFileYAML: - if err := yaml.Unmarshal([]byte(rawString), &result); err != nil { + if err := yaml.Unmarshal(rawBytes, &result); err != nil { return nil, err } - return &result, nil default: return nil, fmt.Errorf("invalid file format: %s", fileFormat) } + + return applyEnvVariablesToSchema(&result, logger), nil case rest.OpenAPIv2Spec: result, errs := openapi.OpenAPIv2ToNDCSchema(rawBytes, &openapi.ConvertOptions{ MethodAlias: conf.MethodAlias, @@ -113,7 +90,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]stri if len(errs) > 0 { logger.Warn("some errors happened when parsing OpenAPI", slog.Any("errors", errs)) } - return result, nil + return applyEnvVariablesToSchema(result, logger), nil } return nil, errors.Join(errs...) case rest.OpenAPIv3Spec: @@ -125,7 +102,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, envVars map[string]stri if len(errs) > 0 { logger.Warn("some errors happened when parsing OpenAPI", slog.Any("errors", errs)) } - return result, nil + return applyEnvVariablesToSchema(result, logger), nil } return nil, errors.Join(errs...) default: @@ -245,3 +222,63 @@ func validateRequestSchema(req *rest.Request, defaultMethod string, defTimeout u func printSchemaValidationError(logger *slog.Logger, errors map[string][]string) { logger.Error("errors happen when validating NDC REST schemas", slog.Any("errors", errors)) } + +func replaceEnvTemplate(input string, logger *slog.Logger) string { + envTemplates := rest.FindAllEnvTemplates(input) + if len(envTemplates) == 0 { + return input + } + + for _, env := range envTemplates { + value, ok := os.LookupEnv(env.Name) + if !ok { + if env.DefaultValue == nil { + logger.Warn(fmt.Sprintf("%s env does not exist", env.Name)) + continue + } + value = *env.DefaultValue + } + input = strings.ReplaceAll(input, env.String(), value) + } + + return input +} + +func replaceEnvTemplateInHeader(input map[string]string, logger *slog.Logger) map[string]string { + result := map[string]string{} + for k, v := range input { + result[replaceEnvTemplate(k, logger)] = replaceEnvTemplate(v, logger) + } + return result +} + +func applyEnvVariablesToSchema(input *rest.NDCRestSchema, logger *slog.Logger) *rest.NDCRestSchema { + + if input.Settings != nil { + input.Settings.Headers = replaceEnvTemplateInHeader(input.Settings.Headers, logger) + for i, server := range input.Settings.Servers { + server.URL = replaceEnvTemplate(server.URL, logger) + input.Settings.Servers[i] = server + } + for key, ss := range input.Settings.SecuritySchemes { + ss.Value = replaceEnvTemplate(ss.Value, logger) + input.Settings.SecuritySchemes[key] = ss + } + } + + for _, fn := range input.Functions { + if fn.Request == nil { + continue + } + fn.Request.Headers = replaceEnvTemplateInHeader(fn.Request.Headers, logger) + } + + for _, proc := range input.Procedures { + if proc.Request == nil { + continue + } + proc.Request.Headers = replaceEnvTemplateInHeader(proc.Request.Headers, logger) + } + + return input +} diff --git a/rest/testdata/jsonplaceholder/snapshots/capabilities b/rest/testdata/jsonplaceholder/snapshots/capabilities index 70961cd..fea4e46 100644 --- a/rest/testdata/jsonplaceholder/snapshots/capabilities +++ b/rest/testdata/jsonplaceholder/snapshots/capabilities @@ -1,9 +1,9 @@ { - "version": "0.1.0", + "version": "0.1.1", "capabilities": { "query": { "variables": {} }, "mutation": {} } -} \ No newline at end of file +} diff --git a/rest/testdata/jsonplaceholder/snapshots/schema b/rest/testdata/jsonplaceholder/snapshots/schema index f94fd62..9ae7bcd 100644 --- a/rest/testdata/jsonplaceholder/snapshots/schema +++ b/rest/testdata/jsonplaceholder/snapshots/schema @@ -2,9 +2,19 @@ "scalar_types": { "Boolean": { "aggregate_functions": {}, - "comparison_operators": {} + "comparison_operators": {}, + "representation": { + "type": "boolean" + } }, "Int": { + "aggregate_functions": {}, + "comparison_operators": {}, + "representation": { + "type": "integer" + } + }, + "JSON": { "aggregate_functions": {}, "comparison_operators": {} }, @@ -14,7 +24,10 @@ }, "String": { "aggregate_functions": {}, - "comparison_operators": {} + "comparison_operators": {}, + "representation": { + "type": "string" + } } }, "object_types": { @@ -836,4 +849,4 @@ } } ] -} \ No newline at end of file +}