Skip to content

Commit

Permalink
Improve content type parser and separate oneOf to multiple operations (
Browse files Browse the repository at this point in the history
…#49)

* OAS: Improve the evaluation of unknown content types.
* OAS: If the request body type is a oneOf list the API will be duplicated to multiple operations.
  • Loading branch information
hgiasac authored Dec 27, 2024
1 parent 4818ef4 commit 2b77299
Show file tree
Hide file tree
Showing 45 changed files with 3,517 additions and 1,905 deletions.
2 changes: 0 additions & 2 deletions connector/internal/argument/preset.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package argument
import (
"errors"
"fmt"
"log"
"strconv"
"strings"

Expand Down Expand Up @@ -124,7 +123,6 @@ func (ap ArgumentPreset) evalNestedField(segments []*spec.Segment, argument any,
return argument, nil
}

log.Println(selector, argumentSlice)
step := selector.Step()
if step < 1 {
step = 1
Expand Down
8 changes: 3 additions & 5 deletions connector/internal/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import (
"github.com/hasura/ndc-sdk-go/utils"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
"golang.org/x/sync/errgroup"
)
Expand Down Expand Up @@ -303,10 +302,8 @@ func (client *HTTPClient) doRequest(ctx context.Context, request *RetryableReque
if retryCount > 0 {
span.SetAttributes(attribute.Int("http.request.resend_count", retryCount))
}
setHeaderAttributes(span, "http.request.header.", request.Headers)

client.manager.propagator.Inject(ctx, propagation.HeaderCarrier(request.Headers))
resp, cancel, err := client.manager.ExecuteRequest(ctx, request, namespace)
resp, cancel, err := client.manager.ExecuteRequest(ctx, span, request, namespace)
if err != nil {
span.SetStatus(codes.Error, "error happened when executing the request")
span.RecordError(err)
Expand Down Expand Up @@ -552,6 +549,7 @@ func parseContentType(input string) string {
return ""
}
parts := strings.Split(input, ";")
contentTypeParts := strings.Split(parts[0], ",")

return strings.TrimSpace(parts[0])
return strings.TrimSpace(contentTypeParts[0])
}
3 changes: 2 additions & 1 deletion connector/internal/contenttype/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"

rest "github.com/hasura/ndc-http/ndc-http-schema/schema"
restUtils "github.com/hasura/ndc-http/ndc-http-schema/utils"
"github.com/hasura/ndc-sdk-go/schema"
"github.com/hasura/ndc-sdk-go/utils"
)
Expand All @@ -27,7 +28,7 @@ func NewJSONDecoder(httpSchema *rest.NDCHttpSchema) *JSONDecoder {

// Decode unmarshals json and evaluate the schema type.
func (c *JSONDecoder) Decode(r io.Reader, resultType schema.Type) (any, error) {
underlyingType, _, err := UnwrapNullableType(resultType)
underlyingType, _, err := restUtils.UnwrapNullableType(resultType)
if err != nil {
return nil, err
}
Expand Down
19 changes: 0 additions & 19 deletions connector/internal/contenttype/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"reflect"
"strconv"
"strings"

"github.com/hasura/ndc-sdk-go/schema"
)

var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
Expand Down Expand Up @@ -51,20 +49,3 @@ func StringifySimpleScalar(val reflect.Value, kind reflect.Kind) (string, error)
return string(j), nil
}
}

// UnwrapNullableType unwraps the underlying type of the nullable type
func UnwrapNullableType(input schema.Type) (schema.TypeEncoder, bool, error) {
switch ty := input.Interface().(type) {
case *schema.NullableType:
childType, _, err := UnwrapNullableType(ty.UnderlyingType)
if err != nil {
return nil, false, err
}

return childType, true, nil
case *schema.NamedType, *schema.ArrayType:
return ty, false, nil
default:
return nil, false, fmt.Errorf("invalid type %v", input)
}
}
2 changes: 1 addition & 1 deletion connector/internal/request_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (c *RequestBuilder) getRequestUploadBody(rawRequest *rest.Request, bodyInfo
return rawRequest.RequestBody
}

bi, ok, err := contenttype.UnwrapNullableType(bodyInfo.Type)
bi, ok, err := restUtils.UnwrapNullableType(bodyInfo.Type)
if err != nil || !ok {
return nil
}
Expand Down
7 changes: 6 additions & 1 deletion connector/internal/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/hasura/ndc-sdk-go/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/trace"
)

// UpstreamManager represents a manager for an upstream.
Expand Down Expand Up @@ -133,7 +134,7 @@ func (um *UpstreamManager) CreateHTTPClient(requests *RequestBuilderResults) *HT
}

// ExecuteRequest executes a request to the upstream server.
func (um *UpstreamManager) ExecuteRequest(ctx context.Context, request *RetryableRequest, namespace string) (*http.Response, context.CancelFunc, error) {
func (um *UpstreamManager) ExecuteRequest(ctx context.Context, span trace.Span, request *RetryableRequest, namespace string) (*http.Response, context.CancelFunc, error) {
req, cancel, err := request.CreateRequest(ctx)
if err != nil {
return nil, nil, err
Expand All @@ -146,8 +147,12 @@ func (um *UpstreamManager) ExecuteRequest(ctx context.Context, request *Retryabl
return nil, nil, err
}

setHeaderAttributes(span, "http.request.header.", req.Header)

req.Header.Set(acceptEncodingHeader, um.compressors.AcceptEncoding())
req.Header.Set("User-Agent", "ndc-http/"+version.BuildVersion)
um.propagator.Inject(ctx, propagation.HeaderCarrier(req.Header))

resp, err := httpClient.Do(req)
if err != nil {
cancel()
Expand Down
4 changes: 3 additions & 1 deletion connector/mutation.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ func (c *HTTPConnector) execMutationOperation(parentCtx context.Context, state *
var err error
if operation.Name == internal.ProcedureSendHTTPRequest {
requests, err = internal.NewRawRequestBuilder(operation, c.config.ForwardHeaders).Build()
requests.Operation = &c.procSendHttpRequest
if err == nil {
requests.Operation = &c.procSendHttpRequest
}
} else {
requests, err = c.explainProcedure(&operation)
}
Expand Down
10 changes: 9 additions & 1 deletion docs/argument_presets.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,21 @@ You can use argument presets to set default values to request arguments. Argumen
"value": 1
},
"targets": ["addPet"]
},
{
"path": "body.categories[*].id",
"value": {
"type": "literal",
"value": 1
},
"targets": []
}
]
}
}
```

The target argument field is removed from the `arguments` schema if the selector is the root field. If the path selects the nested field the target field becomes nullable.
The target argument field is removed from the `arguments` schema if the selector is the root field. If the path selects the nested field the target field becomes nullable. Support object properties and array selectors.

## Configuration options

Expand Down
1 change: 0 additions & 1 deletion ndc-http-schema/command/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ func CommandConvertToNDCSchema(args *configuration.ConvertCommandArguments, logg
slog.Any("patch_before", config.PatchBefore),
slog.Any("patch_after", config.PatchAfter),
slog.Any("allowed_content_types", config.AllowedContentTypes),
slog.Bool("strict", config.Strict),
slog.Bool("pure", config.Pure),
slog.Bool("no_deprecation", config.NoDeprecation),
)
Expand Down
5 changes: 1 addition & 4 deletions ndc-http-schema/configuration/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ func ConvertToNDCSchema(config *ConvertConfig, logger *slog.Logger) (*schema.NDC
TrimPrefix: config.TrimPrefix,
EnvPrefix: config.EnvPrefix,
AllowedContentTypes: config.AllowedContentTypes,
Strict: config.Strict,
NoDeprecation: config.NoDeprecation,
Logger: logger,
}
Expand Down Expand Up @@ -79,9 +78,7 @@ func ResolveConvertConfigArguments(config *ConvertConfig, configDir string, args
if args.Pure {
config.Pure = args.Pure
}
if args.Strict {
config.Strict = args.Strict
}

if args.NoDeprecation {
config.NoDeprecation = args.NoDeprecation
}
Expand Down
10 changes: 4 additions & 6 deletions ndc-http-schema/configuration/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ var fieldNameRegex = regexp.MustCompile(`^[a-zA-Z_]\w+$`)
type Configuration struct {
Output string `json:"output,omitempty" yaml:"output,omitempty"`
// Require strict validation
Strict bool `json:"strict" yaml:"strict"`
ForwardHeaders ForwardHeadersSettings `json:"forwardHeaders" yaml:"forwardHeaders"`
Concurrency ConcurrencySettings `json:"concurrency" yaml:"concurrency"`
Files []ConfigItem `json:"files" yaml:"files"`
Strict bool `json:"strict" yaml:"strict"`
ForwardHeaders ForwardHeadersSettings `json:"forwardHeaders,omitempty" yaml:"forwardHeaders,omitempty"`
Concurrency ConcurrencySettings `json:"concurrency,omitempty" yaml:"concurrency,omitempty"`
Files []ConfigItem `json:"files" yaml:"files"`
}

// ConcurrencySettings represent settings for concurrent webhook executions to remote servers.
Expand Down Expand Up @@ -212,8 +212,6 @@ type ConvertConfig struct {
EnvPrefix string `json:"envPrefix,omitempty" yaml:"envPrefix"`
// Return the pure NDC schema only
Pure bool `json:"pure,omitempty" yaml:"pure"`
// Require strict validation
Strict bool `json:"strict,omitempty" yaml:"strict"`
// Ignore deprecated fields.
NoDeprecation bool `json:"noDeprecation,omitempty" yaml:"noDeprecation"`
// Patch files to be applied into the input file before converting
Expand Down
6 changes: 0 additions & 6 deletions ndc-http-schema/jsonschema/configuration.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@
"type": "boolean",
"description": "Return the pure NDC schema only"
},
"strict": {
"type": "boolean",
"description": "Require strict validation"
},
"noDeprecation": {
"type": "boolean",
"description": "Ignore deprecated fields."
Expand Down Expand Up @@ -138,8 +134,6 @@
"type": "object",
"required": [
"strict",
"forwardHeaders",
"concurrency",
"files"
],
"description": "Configuration contains required settings for the connector."
Expand Down
4 changes: 0 additions & 4 deletions ndc-http-schema/jsonschema/convert-config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@
"type": "boolean",
"description": "Return the pure NDC schema only"
},
"strict": {
"type": "boolean",
"description": "Require strict validation"
},
"noDeprecation": {
"type": "boolean",
"description": "Ignore deprecated fields."
Expand Down
13 changes: 7 additions & 6 deletions ndc-http-schema/jsonschema/ndc-http-schema.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@
"literal"
]
},
"value": true
"value": {
"description": "The literal value"
}
},
"type": "object",
"required": [
Expand All @@ -89,7 +91,8 @@
]
},
"name": {
"type": "string"
"type": "string",
"description": "Environment variable name"
}
},
"type": "object",
Expand All @@ -107,7 +110,8 @@
]
},
"name": {
"type": "string"
"type": "string",
"description": "Header name, require enable headers forwarding"
}
},
"type": "object",
Expand Down Expand Up @@ -969,9 +973,6 @@
},
"additionalProperties": false,
"type": "object",
"required": [
"type"
],
"description": "TypeSchema represents a serializable object of OpenAPI schema that is used for validation"
},
"XMLSchema": {
Expand Down
20 changes: 4 additions & 16 deletions ndc-http-schema/openapi/internal/oas2.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,37 +169,25 @@ func (oc *OAS2Builder) pathToNDCOperations(pathItem orderedmap.Pair[string, *v2.
oc.schema.Functions[funcName] = *funcGet
}

procPost, procPostName, err := newOAS2OperationBuilder(oc, pathKey, "post").BuildProcedure(pathValue.Post, pathValue.Parameters)
err = newOAS2OperationBuilder(oc, pathKey, "post").BuildProcedure(pathValue.Post, pathValue.Parameters)
if err != nil {
return err
}
if procPost != nil {
oc.schema.Procedures[procPostName] = *procPost
}

procPut, procPutName, err := newOAS2OperationBuilder(oc, pathKey, "put").BuildProcedure(pathValue.Put, pathValue.Parameters)
err = newOAS2OperationBuilder(oc, pathKey, "put").BuildProcedure(pathValue.Put, pathValue.Parameters)
if err != nil {
return err
}
if procPut != nil {
oc.schema.Procedures[procPutName] = *procPut
}

procPatch, procPatchName, err := newOAS2OperationBuilder(oc, pathKey, "patch").BuildProcedure(pathValue.Patch, pathValue.Parameters)
err = newOAS2OperationBuilder(oc, pathKey, "patch").BuildProcedure(pathValue.Patch, pathValue.Parameters)
if err != nil {
return err
}
if procPatch != nil {
oc.schema.Procedures[procPatchName] = *procPatch
}

procDelete, procDeleteName, err := newOAS2OperationBuilder(oc, pathKey, "delete").BuildProcedure(pathValue.Delete, pathValue.Parameters)
err = newOAS2OperationBuilder(oc, pathKey, "delete").BuildProcedure(pathValue.Delete, pathValue.Parameters)
if err != nil {
return err
}
if procDelete != nil {
oc.schema.Procedures[procDeleteName] = *procDelete
}

return nil
}
Expand Down
Loading

0 comments on commit 2b77299

Please sign in to comment.