Skip to content

Commit

Permalink
make explode nullable
Browse files Browse the repository at this point in the history
  • Loading branch information
hgiasac committed Mar 31, 2024
1 parent 56bf897 commit 0ed7bcb
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 90 deletions.
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ go 1.21
toolchain go1.22.0

require (
github.com/hasura/ndc-rest-schema v0.0.0-20240328031759-67ecd08d8e10
github.com/hasura/ndc-sdk-go v1.0.0
github.com/hasura/ndc-rest-schema v0.0.0-20240331093809-e65320e1560c
github.com/hasura/ndc-sdk-go v1.0.1-0.20240331071727-9c3db01b3219
github.com/lmittmann/tint v1.0.4
github.com/stretchr/testify v1.9.0
gopkg.in/yaml.v3 v3.0.1
Expand Down
12 changes: 12 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,20 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0Q
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
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-rest-schema v0.0.0-20240330102919-f53c332d4407 h1:aEG4I9CVvmJ5D8r/YGTaEOKi3eWkEV67chloFZbqIoQ=
github.com/hasura/ndc-rest-schema v0.0.0-20240330102919-f53c332d4407/go.mod h1:nK17RPx2CZd92WqdZJrsNLzMKIWlCwssr3Wqvq7iM6w=
github.com/hasura/ndc-rest-schema v0.0.0-20240330131103-89a52e92c18b h1:jnZD5Y4dUcPSoJ3L+dqiiawqYxIf+ugqhDqeV1m8rFQ=
github.com/hasura/ndc-rest-schema v0.0.0-20240330131103-89a52e92c18b/go.mod h1:nK17RPx2CZd92WqdZJrsNLzMKIWlCwssr3Wqvq7iM6w=
github.com/hasura/ndc-rest-schema v0.0.0-20240331080554-1c8c25fd38d4 h1:02UAvIWj3+DX00Wau9qGptQspqZlQjNCp3hIWsbzKsk=
github.com/hasura/ndc-rest-schema v0.0.0-20240331080554-1c8c25fd38d4/go.mod h1:jcUMvsv1DjSONUk713W+jDNRTE0UQDVqHg6bRHk+Dcc=
github.com/hasura/ndc-rest-schema v0.0.0-20240331090952-d46c51a7ff3b h1:xO4ZLoyr0s35cdBMEPCm1EKd3Cd4LJ0Nsi6XqhsS3fM=
github.com/hasura/ndc-rest-schema v0.0.0-20240331090952-d46c51a7ff3b/go.mod h1:jcUMvsv1DjSONUk713W+jDNRTE0UQDVqHg6bRHk+Dcc=
github.com/hasura/ndc-rest-schema v0.0.0-20240331093809-e65320e1560c h1:N98Gvv2AuvhbQiypqnsybGdc0zDucV9yzWtwDLK5NrQ=
github.com/hasura/ndc-rest-schema v0.0.0-20240331093809-e65320e1560c/go.mod h1:jcUMvsv1DjSONUk713W+jDNRTE0UQDVqHg6bRHk+Dcc=
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/hasura/ndc-sdk-go v1.0.1-0.20240331071727-9c3db01b3219 h1:1HniD4n41sKrKaQIBGO71tivLM3m435FCIJNUKJTX2k=
github.com/hasura/ndc-sdk-go v1.0.1-0.20240331071727-9c3db01b3219/go.mod h1:fNEh1RbxSrZNDl9ejNsKEXuMPMHhmMk0gB4XAd1SAus=
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=
Expand Down
99 changes: 96 additions & 3 deletions rest/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
"io"
"log/slog"
"net/http"
"net/url"
"reflect"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -102,9 +105,6 @@ func createRequest(ctx context.Context, rawRequest *rest.Request, headers http.H
for key, header := range headers {
request.Header[key] = header
}
if request.Header.Get("accept") == "" {
request.Header.Set("Accept", contentTypeJSON)
}

return request, nil
}
Expand Down Expand Up @@ -161,6 +161,99 @@ func evalHTTPResponse(resp *http.Response, selection schema.NestedField) (any, e
}
}

func evalURLAndHeaderParameters(request *rest.Request, argumentsSchema map[string]schema.ArgumentInfo, arguments map[string]any) (string, http.Header, error) {
endpoint, err := url.Parse(request.URL)
if err != nil {
return "", nil, err
}
headers := http.Header{}
for k, h := range request.Headers {
headers.Add(k, h)
}

for _, param := range request.Parameters {
argSchema, schemaOk := argumentsSchema[param.Name]
value, ok := arguments[param.Name]

if !schemaOk || !ok || utils.IsNil(value) {
if param.Required {
return "", nil, fmt.Errorf("parameter %s is required", param.Name)
}
} else if err := evalURLAndHeaderParameterBySchema(endpoint, &headers, &param, argSchema.Type, value); err != nil {
return "", nil, err
}
}
return endpoint.String(), headers, nil
}

// the query parameters serialization follows [OAS 3.1 spec]
//
// [OAS 3.1 spec]: https://swagger.io/docs/specification/serialization/
func evalURLAndHeaderParameterBySchema(endpoint *url.URL, header *http.Header, param *rest.RequestParameter, argumentType schema.Type, value any) error {
if utils.IsNil(value) {
return nil
}

var values []string
switch arg := argumentType.Interface().(type) {
case *schema.NamedType:
switch arg.Name {
case "Boolean":
values = []string{fmt.Sprintf("%t", value)}
case "Int", "Float", "String":
values = []string{fmt.Sprint(value)}
default:
b, err := json.Marshal(value)
if err != nil {
return err
}
values = []string{string(b)}
}
case *schema.NullableType:
return evalURLAndHeaderParameterBySchema(endpoint, header, param, arg.UnderlyingType, value)
case *schema.ArrayType:
if !slices.Contains([]rest.ParameterLocation{rest.InHeader, rest.InQuery}, param.In) {
return fmt.Errorf("cannot evaluate array parameter to %s", param.In)
}
if !utils.IsNil(value) {
arrayValue, ok := value.([]any)
if !ok {
return fmt.Errorf("cannot evaluate array parameter, expected array, got: %+v", reflect.TypeOf(value).Name())
}
for _, item := range arrayValue {
values = append(values, fmt.Sprint(item))
}
}
}

// following the OAS spec to serialize parameters
// https://swagger.io/docs/specification/serialization/
switch param.In {
case rest.InHeader:
header.Set(param.Name, strings.Join(values, ","))
case rest.InQuery:
q := endpoint.Query()
if param.Explode {
for _, v := range values {
q.Add(param.Name, v)
}
} else {
switch param.Style {
case rest.EncodingStyleSpaceDelimited:
q.Add(param.Name, strings.Join(values, "%20"))
case rest.EncodingStylePipeDelimited:
q.Add(param.Name, strings.Join(values, "|"))
default:
q.Add(param.Name, strings.Join(values, ","))
}
}
endpoint.RawQuery = q.Encode()
case rest.InPath:
endpoint.Path = strings.ReplaceAll(endpoint.Path, fmt.Sprintf("{%s}", param.Name), strings.Join(values, ","))
}
return nil
}

func parseContentType(input string) string {
if input == "" {
return ""
Expand Down
14 changes: 11 additions & 3 deletions rest/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package rest
import (
"fmt"
"net/url"
"slices"
"strings"

rest "github.com/hasura/ndc-rest-schema/schema"
Expand Down Expand Up @@ -106,9 +107,16 @@ func (rm RESTMetadata) applySecurity(req *rest.Request) (*rest.Request, error) {
return req, nil
}

security := req.Security.First()
securityScheme, ok := rm.settings.SecuritySchemes[security.Name()]
if !ok {
var securityScheme *rest.SecurityScheme
for _, security := range req.Security {
sc, ok := rm.settings.SecuritySchemes[security.Name()]
if !ok || (slices.Contains([]rest.SecuritySchemeType{rest.HTTPAuthScheme, rest.APIKeyScheme}, sc.Type) && sc.Value == "") {
continue
}
securityScheme = &sc
}

if securityScheme == nil {
return req, nil
}

Expand Down
80 changes: 0 additions & 80 deletions rest/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ package rest

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"slices"
"strings"

rest "github.com/hasura/ndc-rest-schema/schema"
"github.com/hasura/ndc-sdk-go/schema"
"github.com/hasura/ndc-sdk-go/utils"
)
Expand Down Expand Up @@ -71,76 +64,3 @@ func (c *RESTConnector) execQuery(ctx context.Context, request *schema.QueryRequ

return c.client.Send(ctx, function.Request, headers, nil, queryFields)
}

func evalURLAndHeaderParameters(request *rest.Request, argumentsSchema map[string]schema.ArgumentInfo, arguments map[string]any) (string, http.Header, error) {
endpoint, err := url.Parse(request.URL)
if err != nil {
return "", nil, err
}
headers := http.Header{}
for k, h := range request.Headers {
headers.Add(k, h)
}

for _, param := range request.Parameters {
argSchema, schemaOk := argumentsSchema[param.Name]
value, ok := arguments[param.Name]

if !schemaOk || !ok || utils.IsNil(value) {
if param.Required {
return "", nil, fmt.Errorf("parameter %s is required", param.Name)
}
} else if err := evalURLAndHeaderParameterBySchema(endpoint, &headers, &param, argSchema.Type, value); err != nil {
return "", nil, err
}
}
return endpoint.String(), headers, nil
}

func evalURLAndHeaderParameterBySchema(endpoint *url.URL, header *http.Header, param *rest.RequestParameter, argumentType schema.Type, value any) error {
if utils.IsNil(value) {
return nil
}

var valueStr string
switch arg := argumentType.Interface().(type) {
case *schema.NamedType:
switch arg.Name {
case "Boolean":
valueStr = fmt.Sprintf("%t", value)
case "Int", "Float", "String":
valueStr = fmt.Sprint(value)
default:
b, err := json.Marshal(value)
if err != nil {
return err
}
valueStr = string(b)
}
case *schema.NullableType:
return evalURLAndHeaderParameterBySchema(endpoint, header, param, arg.UnderlyingType, value)
case *schema.ArrayType:
if !slices.Contains([]rest.ParameterLocation{rest.InHeader, rest.InQuery}, param.In) {
return fmt.Errorf("cannot evaluate array parameter to %s", param.In)
}

// TODO: evaluate array with reflection
b, err := json.Marshal(value)
if err != nil {
return err
}
valueStr = string(b)
}

switch param.In {
case rest.InHeader:
header.Set(param.Name, valueStr)
case rest.InQuery:
q := endpoint.Query()
q.Add(param.Name, valueStr)
endpoint.RawQuery = q.Encode()
case rest.InPath:
endpoint.Path = strings.ReplaceAll(endpoint.Path, fmt.Sprintf("{%s}", param.Name), valueStr)
}
return nil
}
4 changes: 2 additions & 2 deletions rest/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (*
}

return applyEnvVariablesToSchema(&result, logger), nil
case rest.OpenAPIv2Spec:
case rest.OpenAPIv2Spec, rest.OAS2Spec:
result, errs := openapi.OpenAPIv2ToNDCSchema(rawBytes, &openapi.ConvertOptions{
MethodAlias: conf.MethodAlias,
TrimPrefix: conf.TrimPrefix,
Expand All @@ -93,7 +93,7 @@ func buildSchemaFile(configDir string, conf *SchemaFile, logger *slog.Logger) (*
return applyEnvVariablesToSchema(result, logger), nil
}
return nil, errors.Join(errs...)
case rest.OpenAPIv3Spec:
case rest.OpenAPIv3Spec, rest.OAS3Spec:
result, errs := openapi.OpenAPIv3ToNDCSchema(rawBytes, &openapi.ConvertOptions{
MethodAlias: conf.MethodAlias,
TrimPrefix: conf.TrimPrefix,
Expand Down
5 changes: 5 additions & 0 deletions rest/testdata/stripe/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
files:
- path: https://raw.githubusercontent.com/stripe/openapi/v890/openapi/spec3.json
spec: oas3
trimPrefix: /v1
envPrefix: STRIPE

0 comments on commit 0ed7bcb

Please sign in to comment.