diff --git a/connector/connector.go b/connector/connector.go index 4fc110c..ee759fe 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -4,18 +4,17 @@ import ( "context" "encoding/json" "fmt" - "os" "github.com/hasura/ndc-rest/connector/internal" "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-sdk-go/connector" "github.com/hasura/ndc-sdk-go/schema" - "gopkg.in/yaml.v3" ) // RESTConnector implements the SDK interface of NDC specification type RESTConnector struct { + config *configuration.Configuration metadata internal.MetadataCollection capabilities *schema.RawCapabilitiesResponse rawSchema *schema.RawSchemaResponse @@ -53,7 +52,7 @@ func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir } c.capabilities = schema.NewRawCapabilitiesResponseUnsafe(rawCapabilities) - config, err := parseConfiguration(configurationDir) + config, err := configuration.ReadConfigurationFile(configurationDir) if err != nil { return nil, err } @@ -66,7 +65,7 @@ func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir var errs map[string][]string if schemas == nil { - schemas, errs = configuration.BuildSchemaFiles(config, configurationDir, logger) + schemas, errs = configuration.BuildSchemaFromConfig(config, configurationDir, logger) if len(errs) > 0 { printSchemaValidationError(logger, errs) return nil, errBuildSchemaFailed @@ -77,6 +76,8 @@ func (c *RESTConnector) ParseConfiguration(ctx context.Context, configurationDir return nil, errInvalidSchema } + c.config = config + return config, nil } @@ -106,41 +107,3 @@ func (c *RESTConnector) HealthCheck(ctx context.Context, configuration *configur func (c *RESTConnector) GetCapabilities(configuration *configuration.Configuration) schema.CapabilitiesResponseMarshaler { return c.capabilities } - -func parseConfiguration(configurationDir string) (*configuration.Configuration, error) { - var config configuration.Configuration - jsonBytes, err := os.ReadFile(configurationDir + "/config.json") - if err == nil { - if err = json.Unmarshal(jsonBytes, &config); err != nil { - return nil, err - } - return &config, nil - } - - if !os.IsNotExist(err) { - return nil, err - } - - // try to read and parse yaml file - yamlBytes, err := os.ReadFile(configurationDir + "/config.yaml") - if err != nil { - if !os.IsNotExist(err) { - return nil, err - } - yamlBytes, err = os.ReadFile(configurationDir + "/config.yml") - } - - if err != nil { - if os.IsNotExist(err) { - return nil, fmt.Errorf("the config.{json,yaml,yml} file does not exist at %s", configurationDir) - } else { - return nil, err - } - } - - if err = yaml.Unmarshal(yamlBytes, &config); err != nil { - return nil, err - } - - return &config, nil -} diff --git a/connector/connector_test.go b/connector/connector_test.go index 9c6b6e5..e23aa7f 100644 --- a/connector/connector_test.go +++ b/connector/connector_test.go @@ -309,37 +309,37 @@ func TestRESTConnector_distribution(t *testing.T) { }, connector.WithoutRecovery()) assert.NilError(t, err) - timeout, err := rc.metadata[0].Settings.Servers[0].Timeout.Value() - assert.NilError(t, err) - assert.Equal(t, int64(30), *timeout) + // timeout, err := rc.metadata[0].Settings.Servers[0].Timeout.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(30), *timeout) - retryTimes, err := rc.metadata[0].Settings.Servers[0].Retry.Times.Value() - assert.NilError(t, err) - assert.Equal(t, int64(2), *retryTimes) + // retryTimes, err := rc.metadata[0].Settings.Servers[0].Retry.Times.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(2), *retryTimes) - retryDelay, err := rc.metadata[0].Settings.Servers[0].Retry.Delay.Value() - assert.NilError(t, err) - assert.Equal(t, int64(1000), *retryDelay) + // retryDelay, err := rc.metadata[0].Settings.Servers[0].Retry.Delay.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(1000), *retryDelay) - retryStatus, err := rc.metadata[0].Settings.Servers[0].Retry.HTTPStatus.Value() - assert.NilError(t, err) - assert.DeepEqual(t, []int64{429, 500}, retryStatus) + // retryStatus, err := rc.metadata[0].Settings.Servers[0].Retry.HTTPStatus.Value() + // assert.NilError(t, err) + // assert.DeepEqual(t, []int64{429, 500}, retryStatus) - timeout1, err := rc.metadata[0].Settings.Servers[1].Timeout.Value() - assert.NilError(t, err) - assert.Equal(t, int64(10), *timeout1) + // timeout1, err := rc.metadata[0].Settings.Servers[1].Timeout.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(10), *timeout1) - retryTimes1, err := rc.metadata[0].Settings.Servers[1].Retry.Times.Value() - assert.NilError(t, err) - assert.Equal(t, int64(1), *retryTimes1) + // retryTimes1, err := rc.metadata[0].Settings.Servers[1].Retry.Times.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(1), *retryTimes1) - retryDelay1, err := rc.metadata[0].Settings.Servers[1].Retry.Delay.Value() - assert.NilError(t, err) - assert.Equal(t, int64(500), *retryDelay1) + // retryDelay1, err := rc.metadata[0].Settings.Servers[1].Retry.Delay.Value() + // assert.NilError(t, err) + // assert.Equal(t, int64(500), *retryDelay1) - retryStatus1, err := rc.metadata[0].Settings.Servers[1].Retry.HTTPStatus.Value() - assert.NilError(t, err) - assert.DeepEqual(t, []int64{429, 500, 501, 502}, retryStatus1) + // retryStatus1, err := rc.metadata[0].Settings.Servers[1].Retry.HTTPStatus.Value() + // assert.NilError(t, err) + // assert.DeepEqual(t, []int64{429, 500, 501, 502}, retryStatus1) testServer := connServer.BuildTestServer() defer testServer.Close() diff --git a/connector/internal/client.go b/connector/internal/client.go index 4149675..d484ff0 100644 --- a/connector/internal/client.go +++ b/connector/internal/client.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "log/slog" + "math" "net/http" "strings" "sync" @@ -149,8 +150,9 @@ func (client *HTTPClient) sendSingle(ctx context.Context, request *RetryableRequ logger.Debug("sending request to remote server...", logAttrs...) } - times := int(request.Retry.Times) - for i := 0; i <= times; i++ { + times := int(math.Max(float64(request.Runtime.Retry.Times), 1)) + delayMs := int(math.Max(float64(request.Runtime.Retry.Delay), 100)) + for i := range times { req, cancel, reqError := request.CreateRequest(ctx) if reqError != nil { cancel() @@ -195,7 +197,7 @@ func (client *HTTPClient) sendSingle(ctx context.Context, request *RetryableRequ ) } - time.Sleep(time.Duration(request.Retry.Delay) * time.Millisecond) + time.Sleep(time.Duration(delayMs) * time.Millisecond) } if err != nil { diff --git a/connector/internal/metadata.go b/connector/internal/metadata.go index 259c1eb..73d33eb 100644 --- a/connector/internal/metadata.go +++ b/connector/internal/metadata.go @@ -1,31 +1,32 @@ package internal import ( + "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-sdk-go/schema" ) // MetadataCollection stores list of REST metadata with helper methods -type MetadataCollection []rest.NDCRestSchema +type MetadataCollection []configuration.NDCRestRuntimeSchema // GetFunction gets the NDC function by name -func (rms MetadataCollection) GetFunction(name string) (*rest.OperationInfo, *rest.NDCRestSettings, error) { +func (rms MetadataCollection) GetFunction(name string) (*rest.OperationInfo, configuration.NDCRestRuntimeSchema, error) { for _, rm := range rms { fn := rm.GetFunction(name) if fn != nil { - return fn, rm.Settings, nil + return fn, rm, nil } } - return nil, nil, schema.UnprocessableContentError("unsupported query: "+name, nil) + return nil, configuration.NDCRestRuntimeSchema{}, schema.UnprocessableContentError("unsupported query: "+name, nil) } // GetProcedure gets the NDC procedure by name -func (rms MetadataCollection) GetProcedure(name string) (*rest.OperationInfo, *rest.NDCRestSettings, error) { +func (rms MetadataCollection) GetProcedure(name string) (*rest.OperationInfo, configuration.NDCRestRuntimeSchema, error) { for _, rm := range rms { fn := rm.GetProcedure(name) if fn != nil { - return fn, rm.Settings, nil + return fn, rm, nil } } - return nil, nil, schema.UnprocessableContentError("unsupported mutation: "+name, nil) + return nil, configuration.NDCRestRuntimeSchema{}, schema.UnprocessableContentError("unsupported mutation: "+name, nil) } diff --git a/connector/internal/request.go b/connector/internal/request.go index a53f9d0..5a37958 100644 --- a/connector/internal/request.go +++ b/connector/internal/request.go @@ -24,8 +24,7 @@ type RetryableRequest struct { ContentType string Headers http.Header Body io.ReadSeeker - Timeout uint - Retry *rest.RetryPolicy + Runtime rest.RuntimeSettings } // CreateRequest creates an HTTP request with body copied @@ -37,7 +36,7 @@ func (r *RetryableRequest) CreateRequest(ctx context.Context) (*http.Request, co } } - timeout := r.Timeout + timeout := r.Runtime.Timeout if timeout == 0 { timeout = defaultTimeoutSeconds } @@ -62,9 +61,9 @@ func getHostFromServers(servers []rest.ServerConfig, serverIDs []string) (string if len(serverIDs) > 0 && !slices.Contains(serverIDs, server.ID) { continue } - hostPtr := server.URL.Value() - if hostPtr != nil && *hostPtr != "" { - results = append(results, *hostPtr) + hostPtr := server.GetURL() + if hostPtr != "" { + results = append(results, hostPtr) selectedServerIDs = append(selectedServerIDs, server.ID) } } @@ -119,7 +118,7 @@ func BuildDistributedRequestsWithOptions(request *RetryableRequest, restOptions } req := RetryableRequest{ - URL: fmt.Sprintf("%s%s", host, request.URL), + URL: host + request.URL, ServerID: serverID, RawRequest: request.RawRequest, ContentType: request.ContentType, @@ -177,7 +176,7 @@ func (req *RetryableRequest) applySecurity(serverConfig *rest.ServerConfig, isEx securityScheme = &sc if slices.Contains([]rest.SecuritySchemeType{rest.HTTPAuthScheme, rest.APIKeyScheme}, sc.Type) && - sc.Value != nil && sc.Value.Value() != nil && *sc.Value.Value() != "" { + sc.Value != nil && sc.GetValue() != "" { break } } @@ -200,45 +199,40 @@ func (req *RetryableRequest) applySecurity(serverConfig *rest.ServerConfig, isEx if scheme == "bearer" || scheme == "basic" { scheme = utils.ToPascalCase(securityScheme.Scheme) } - if securityScheme.Value != nil { - v := securityScheme.Value.Value() - if v != nil { - req.Headers.Set(headerName, fmt.Sprintf("%s %s", scheme, eitherMaskSecret(*v, isExplain))) - } + v := securityScheme.GetValue() + if v != "" { + req.Headers.Set(headerName, fmt.Sprintf("%s %s", scheme, eitherMaskSecret(v, isExplain))) } case rest.APIKeyScheme: switch securityScheme.In { case rest.APIKeyInHeader: if securityScheme.Value != nil { - value := securityScheme.Value.Value() - if value != nil { - req.Headers.Set(securityScheme.Name, eitherMaskSecret(*value, isExplain)) + value := securityScheme.GetValue() + if value != "" { + req.Headers.Set(securityScheme.Name, eitherMaskSecret(value, isExplain)) } } case rest.APIKeyInQuery: - value := securityScheme.Value.Value() - if value != nil { + value := securityScheme.GetValue() + if value != "" { endpoint, err := url.Parse(req.URL) if err != nil { return err } q := endpoint.Query() - q.Add(securityScheme.Name, eitherMaskSecret(*value, isExplain)) + q.Add(securityScheme.Name, eitherMaskSecret(value, isExplain)) endpoint.RawQuery = q.Encode() req.URL = endpoint.String() } case rest.APIKeyInCookie: - if securityScheme.Value != nil { - v := securityScheme.Value.Value() - if v != nil { - req.Headers.Set("Cookie", fmt.Sprintf("%s=%s", securityScheme.Name, eitherMaskSecret(*v, isExplain))) - } - } + // Cookie header should be forwarded from Hasura engine default: return fmt.Errorf("unsupported location for apiKey scheme: %s", securityScheme.In) } // TODO: support OAuth and OIDC + // Authentication headers can be forwarded from Hasura engine + case rest.OAuth2Scheme, rest.OpenIDConnectScheme: default: return fmt.Errorf("unsupported security scheme: %s", securityScheme.Type) } @@ -246,9 +240,6 @@ func (req *RetryableRequest) applySecurity(serverConfig *rest.ServerConfig, isEx } func (req *RetryableRequest) applySettings(settings *rest.NDCRestSettings, isExplain bool) error { - if req.Retry == nil { - req.Retry = &rest.RetryPolicy{} - } if settings == nil { return nil } @@ -260,59 +251,19 @@ func (req *RetryableRequest) applySettings(settings *rest.NDCRestSettings, isExp return err } - if req.Timeout <= 0 && serverConfig.Timeout != nil { - timeout, err := serverConfig.Timeout.Value() - if err != nil { - return err - } - if timeout != nil && *timeout > 0 { - req.Timeout = uint(*timeout) - } - } - if req.Timeout == 0 { - req.Timeout = defaultTimeoutSeconds - } - - if serverConfig.Retry != nil { - if req.Retry.Times <= 0 { - times, err := serverConfig.Retry.Times.Value() - if err == nil && times != nil && *times > 0 { - req.Retry.Times = uint(*times) - } - } - if req.Retry.Delay <= 0 { - delay, err := serverConfig.Retry.Delay.Value() - if err == nil && delay != nil && *delay > 0 { - req.Retry.Delay = uint(*delay) - } else { - req.Retry.Delay = defaultRetryDelays - } - } - if len(req.Retry.HTTPStatus) == 0 { - status, err := serverConfig.Retry.HTTPStatus.Value() - if err != nil || len(status) == 0 { - status = defaultRetryHTTPStatus - } - for _, st := range status { - req.Retry.HTTPStatus = append(req.Retry.HTTPStatus, int(st)) - } - } - } - - req.applyDefaultHeaders(serverConfig.Headers) - req.applyDefaultHeaders(settings.Headers) + req.applyDefaultHeaders(serverConfig.GetHeaders()) + req.applyDefaultHeaders(settings.GetHeaders()) return nil } -func (req *RetryableRequest) applyDefaultHeaders(defaultHeaders map[string]rest.EnvString) { +func (req *RetryableRequest) applyDefaultHeaders(defaultHeaders map[string]string) { for k, envValue := range defaultHeaders { if req.Headers.Get(k) != "" { continue } - value := envValue.Value() - if value != nil && *value != "" { - req.Headers.Set(k, *value) + if envValue != "" { + req.Headers.Set(k, envValue) } } } diff --git a/connector/internal/request_builder.go b/connector/internal/request_builder.go index 55e00ae..c22de15 100644 --- a/connector/internal/request_builder.go +++ b/connector/internal/request_builder.go @@ -21,14 +21,16 @@ type RequestBuilder struct { Schema *rest.NDCRestSchema Operation *rest.OperationInfo Arguments map[string]any + Runtime rest.RuntimeSettings } // NewRequestBuilder creates a new RequestBuilder instance -func NewRequestBuilder(restSchema *rest.NDCRestSchema, operation *rest.OperationInfo, arguments map[string]any) *RequestBuilder { +func NewRequestBuilder(restSchema *rest.NDCRestSchema, operation *rest.OperationInfo, arguments map[string]any, runtime rest.RuntimeSettings) *RequestBuilder { return &RequestBuilder{ Schema: restSchema, Operation: operation, Arguments: arguments, + Runtime: runtime, } } @@ -105,8 +107,22 @@ func (c *RequestBuilder) Build() (*RetryableRequest, error) { ContentType: contentType, Headers: headers, Body: buffer, - Timeout: rawRequest.Timeout, - Retry: rawRequest.Retry, + Runtime: c.Runtime, + } + + if rawRequest.RuntimeSettings != nil { + if rawRequest.RuntimeSettings.Timeout > 0 { + request.Runtime.Timeout = rawRequest.RuntimeSettings.Timeout + } + if rawRequest.RuntimeSettings.Retry.Times > 0 { + request.Runtime.Retry.Times = rawRequest.RuntimeSettings.Retry.Times + } + if rawRequest.RuntimeSettings.Retry.Delay > 0 { + request.Runtime.Retry.Delay = rawRequest.RuntimeSettings.Retry.Delay + } + if rawRequest.RuntimeSettings.Retry.HTTPStatus != nil { + request.Runtime.Retry.HTTPStatus = rawRequest.RuntimeSettings.Retry.HTTPStatus + } } return request, nil diff --git a/connector/internal/request_parameter.go b/connector/internal/request_parameter.go index eac2ecc..4e22a20 100644 --- a/connector/internal/request_parameter.go +++ b/connector/internal/request_parameter.go @@ -28,9 +28,12 @@ func (c *RequestBuilder) evalURLAndHeaderParameters() (string, http.Header, erro } headers := http.Header{} for k, h := range c.Operation.Request.Headers { - v := h.Value() - if v != nil && *v != "" { - headers.Add(k, *v) + v, err := h.Get() + if err != nil { + return "", nil, fmt.Errorf("invalid header value, key: %s, %w", k, err) + } + if v != "" { + headers.Add(k, v) } } diff --git a/connector/mutation.go b/connector/mutation.go index 2a76d66..260153d 100644 --- a/connector/mutation.go +++ b/connector/mutation.go @@ -55,7 +55,7 @@ func (c *RESTConnector) MutationExplain(ctx context.Context, configuration *conf } func (c *RESTConnector) explainProcedure(operation *schema.MutationOperation) (*internal.RetryableRequest, *rest.OperationInfo, *internal.RESTOptions, error) { - procedure, settings, err := c.metadata.GetProcedure(operation.Name) + procedure, metadata, err := c.metadata.GetProcedure(operation.Name) if err != nil { return nil, nil, nil, err } @@ -69,20 +69,26 @@ func (c *RESTConnector) explainProcedure(operation *schema.MutationOperation) (* } // 2. build the request - builder := internal.NewRequestBuilder(c.schema, procedure, rawArgs) + builder := internal.NewRequestBuilder(c.schema, procedure, rawArgs, metadata.Runtime) httpRequest, err := builder.Build() if err != nil { return nil, nil, nil, err } - restOptions, err := parseRESTOptionsFromArguments(procedure.Arguments, rawArgs[rest.RESTOptionsArgumentName]) + if err := c.evalForwardedHeaders(httpRequest, rawArgs); err != nil { + return nil, nil, nil, schema.UnprocessableContentError("invalid forwarded headers", map[string]any{ + "cause": err.Error(), + }) + } + + restOptions, err := c.parseRESTOptionsFromArguments(procedure.Arguments, rawArgs) if err != nil { return nil, nil, nil, schema.UnprocessableContentError("invalid rest options", map[string]any{ "cause": err.Error(), }) } - restOptions.Settings = settings + restOptions.Settings = metadata.Settings return httpRequest, procedure, restOptions, nil } diff --git a/connector/query.go b/connector/query.go index 8da1cae..1024a69 100644 --- a/connector/query.go +++ b/connector/query.go @@ -58,7 +58,7 @@ func (c *RESTConnector) QueryExplain(ctx context.Context, configuration *configu } func (c *RESTConnector) explainQuery(request *schema.QueryRequest, variables map[string]any) (*internal.RetryableRequest, *rest.OperationInfo, *internal.RESTOptions, error) { - function, settings, err := c.metadata.GetFunction(request.Collection) + function, metadata, err := c.metadata.GetFunction(request.Collection) if err != nil { return nil, nil, nil, err } @@ -72,19 +72,25 @@ func (c *RESTConnector) explainQuery(request *schema.QueryRequest, variables map } // 2. build the request - req, err := internal.NewRequestBuilder(c.schema, function, rawArgs).Build() + req, err := internal.NewRequestBuilder(c.schema, function, rawArgs, metadata.Runtime).Build() if err != nil { return nil, nil, nil, err } - restOptions, err := parseRESTOptionsFromArguments(function.Arguments, rawArgs[rest.RESTOptionsArgumentName]) + if err := c.evalForwardedHeaders(req, rawArgs); err != nil { + return nil, nil, nil, schema.UnprocessableContentError("invalid forwarded headers", map[string]any{ + "cause": err.Error(), + }) + } + + restOptions, err := c.parseRESTOptionsFromArguments(function.Arguments, rawArgs) if err != nil { return nil, nil, nil, schema.UnprocessableContentError("invalid rest options", map[string]any{ "cause": err.Error(), }) } - restOptions.Settings = settings + restOptions.Settings = metadata.Settings return req, function, restOptions, err } diff --git a/connector/schema.go b/connector/schema.go index bfa181f..7091578 100644 --- a/connector/schema.go +++ b/connector/schema.go @@ -3,8 +3,10 @@ package rest import ( "context" "encoding/json" + "fmt" "log/slog" + "github.com/go-viper/mapstructure/v2" "github.com/hasura/ndc-rest/connector/internal" "github.com/hasura/ndc-rest/ndc-rest-schema/configuration" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" @@ -17,8 +19,8 @@ func (c *RESTConnector) GetSchema(ctx context.Context, configuration *configurat } // ApplyNDCRestSchemas applies slice of raw NDC REST schemas to the connector -func (c *RESTConnector) ApplyNDCRestSchemas(config *configuration.Configuration, schemas []configuration.NDCRestSchemaWithName, logger *slog.Logger) error { - ndcSchema, metadata, errs := configuration.MergeNDCRestSchemas(schemas) +func (c *RESTConnector) ApplyNDCRestSchemas(config *configuration.Configuration, schemas []configuration.NDCRestRuntimeSchema, logger *slog.Logger) error { + ndcSchema, metadata, errs := configuration.MergeNDCRestSchemas(config, schemas) if len(errs) > 0 { printSchemaValidationError(logger, errs) if ndcSchema == nil || config.Strict { @@ -45,16 +47,45 @@ func printSchemaValidationError(logger *slog.Logger, errors map[string][]string) logger.Error("errors happen when validating NDC REST schemas", slog.Any("errors", errors)) } -func parseRESTOptionsFromArguments(arguments map[string]rest.ArgumentInfo, rawRestOptions any) (*internal.RESTOptions, error) { +func (c *RESTConnector) parseRESTOptionsFromArguments(argumentsInfo map[string]rest.ArgumentInfo, rawArgs map[string]any) (*internal.RESTOptions, error) { var result internal.RESTOptions - if err := result.FromValue(rawRestOptions); err != nil { - return nil, err + argInfo, ok := argumentsInfo[rest.RESTOptionsArgumentName] + if !ok { + return &result, nil } - argInfo, ok := arguments[rest.RESTOptionsArgumentName] + rawRestOptions, ok := rawArgs[rest.RESTOptionsArgumentName] if !ok { return &result, nil } + if err := result.FromValue(rawRestOptions); err != nil { + return nil, err + } restOptionsNamedType := schema.GetUnderlyingNamedType(argInfo.Type) result.Distributed = restOptionsNamedType != nil && restOptionsNamedType.Name == rest.RESTDistributedOptionsObjectName + return &result, nil } + +func (c *RESTConnector) evalForwardedHeaders(req *internal.RetryableRequest, rawArgs map[string]any) error { + if !c.config.ForwardHeaders.Enabled { + return nil + } + rawHeaders, ok := rawArgs[c.config.ForwardHeaders.ArgumentName] + if ok { + return nil + } + + var headers map[string]string + if err := mapstructure.Decode(rawHeaders, &headers); err != nil { + return fmt.Errorf("arguments.%s: %w", c.config.ForwardHeaders.ArgumentName, err) + } + + for key, value := range headers { + if req.Headers.Get(key) != "" { + continue + } + req.Headers.Set(key, value) + } + + return nil +} diff --git a/go.mod b/go.mod index 5000582..91b90b8 100644 --- a/go.mod +++ b/go.mod @@ -5,17 +5,17 @@ go 1.23.0 toolchain go1.23.1 require ( + github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/uuid v1.6.0 - github.com/hasura/ndc-rest/ndc-rest-schema v0.2.5 - github.com/hasura/ndc-sdk-go v1.6.2 - go.opentelemetry.io/otel v1.31.0 - go.opentelemetry.io/otel/trace v1.31.0 - gopkg.in/yaml.v3 v3.0.1 + github.com/hasura/ndc-rest/ndc-rest-schema v0.0.0-00010101000000-000000000000 + github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5 + go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/otel/trace v1.32.0 gotest.tools/v3 v3.5.1 ) require ( - github.com/alecthomas/kong v1.2.1 // indirect + github.com/alecthomas/kong v1.4.0 // indirect 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 @@ -25,39 +25,39 @@ require ( github.com/evanphx/json-patch v0.5.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/google/go-cmp v0.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect github.com/klauspost/compress v1.17.11 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pb33f/libopenapi v0.18.3 // indirect + github.com/pb33f/libopenapi v0.18.6 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 // indirect - go.opentelemetry.io/otel/exporters/prometheus v0.53.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/sdk v1.31.0 // indirect - go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 // indirect + go.opentelemetry.io/otel/exporters/prometheus v0.54.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/sdk v1.32.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/net v0.30.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/text v0.19.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect - google.golang.org/grpc v1.67.1 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/hasura/ndc-rest/ndc-rest-schema => ./ndc-rest-schema diff --git a/go.sum b/go.sum index f539ef6..71831fa 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ -github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= -github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= -github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA= +github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -45,6 +45,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -54,10 +56,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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= -github.com/hasura/ndc-sdk-go v1.6.2 h1:xJtFJ0OduqWmL60GhAYn3gzt1TvEwttSKv8u59MJqDQ= -github.com/hasura/ndc-sdk-go v1.6.2/go.mod h1:H7iN3SFXSou2rjBKv9fLumbvDXMDGP0Eg+cXWHpkA3k= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= +github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5 h1:YA3ix2/SMZ+vR/96YXuSPNYHsocsWnY8xCmhJeT3RYs= +github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5/go.mod h1:H7iN3SFXSou2rjBKv9fLumbvDXMDGP0Eg+cXWHpkA3k= 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= @@ -96,8 +98,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/pb33f/libopenapi v0.18.3 h1:j4lm8xMM/GYSj2M8S7qNwZ//rOtEK5ACEiuNC7mTJzE= -github.com/pb33f/libopenapi v0.18.3/go.mod h1:9ap4lXBHgxGyFwxtOfa+B1C3IQ0rvnqteqjJvJ11oiQ= +github.com/pb33f/libopenapi v0.18.6 h1:adxzZUnOBOAuKxFAIrtb1Qt8GA4XnDWUAxEnqiSoTh0= +github.com/pb33f/libopenapi v0.18.6/go.mod h1:qZRs2IHIcs9SjHPmQfSUCyeD3OY9JkLJQOuFxd0bYCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -106,8 +108,8 @@ github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+ github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -125,30 +127,30 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew= github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/contrib/propagators/b3 v1.31.0 h1:PQPXYscmwbCp76QDvO4hMngF2j8Bx/OTV86laEl8uqo= -go.opentelemetry.io/contrib/propagators/b3 v1.31.0/go.mod h1:jbqfV8wDdqSDrAYxVpXQnpM0XFMq2FtDesblJ7blOwQ= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0 h1:FZ6ei8GFW7kyPYdxJaV2rgI6M+4tvZzhYsQ2wgyVC08= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.31.0/go.mod h1:MdEu/mC6j3D+tTEfvI15b5Ci2Fn7NneJ71YMoiS3tpI= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0 h1:ZsXq73BERAiNuuFXYqP4MR5hBrjXfMGSO+Cx7qoOZiM= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.31.0/go.mod h1:hg1zaDMpyZJuUzjFxFsRYBoccE86tM9Uf4IqNMUxvrY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0 h1:FFeLy03iVTXP6ffeN2iXrxfGsZGCjVx0/4KlizjyBwU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.31.0/go.mod h1:TMu73/k1CP8nBUpDLc71Wj/Kf7ZS9FK5b53VapRsP9o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/exporters/prometheus v0.53.0 h1:QXobPHrwiGLM4ufrY3EOmDPJpo2P90UuFau4CDPJA/I= -go.opentelemetry.io/otel/exporters/prometheus v0.53.0/go.mod h1:WOAXGr3D00CfzmFxtTV1eR0GpoHuPEu+HJT8UWW2SIU= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/contrib/propagators/b3 v1.32.0 h1:MazJBz2Zf6HTN/nK/s3Ru1qme+VhWU5hm83QxEP+dvw= +go.opentelemetry.io/contrib/propagators/b3 v1.32.0/go.mod h1:B0s70QHYPrJwPOwD1o3V/R8vETNOG9N3qZf4LDYvA30= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0 h1:t/Qur3vKSkUCcDVaSumWF2PKHt85pc7fRvFuoVT8qFU= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.32.0/go.mod h1:Rl61tySSdcOJWoEgYZVtmnKdA0GeKrSqkHC1t+91CH8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0 h1:cMyu9O88joYEaI47CnQkxO1XZdpoTF9fEnW2duIddhw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.32.0/go.mod h1:6Am3rn7P9TVVeXYG+wtcGE7IE1tsQ+bP3AuWcKt/gOI= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= +go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= +go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -164,8 +166,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -183,16 +185,16 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -200,12 +202,12 @@ 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-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= -google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/ndc-rest-schema/configuration/schema.go b/ndc-rest-schema/configuration/schema.go index da46f12..5622ef4 100644 --- a/ndc-rest-schema/configuration/schema.go +++ b/ndc-rest-schema/configuration/schema.go @@ -15,32 +15,39 @@ import ( "github.com/hasura/ndc-sdk-go/utils" ) -// BuildSchemaFiles build NDC REST schema from file list -func BuildSchemaFiles(config *Configuration, configDir string, logger *slog.Logger) ([]NDCRestSchemaWithName, map[string][]string) { - schemas := make([]NDCRestSchemaWithName, len(config.Files)) +// BuildSchemaFromConfig build NDC REST schema from the configuration +func BuildSchemaFromConfig(config *Configuration, configDir string, logger *slog.Logger) ([]NDCRestRuntimeSchema, map[string][]string) { + schemas := make([]NDCRestRuntimeSchema, len(config.Files)) errors := make(map[string][]string) for i, file := range config.Files { - var errs []string - schemaOutput, err := buildSchemaFile(configDir, &file, logger) + schemaOutput, err := buildSchemaFile(config, configDir, &file, logger) if err != nil { - errs = append(errs, err.Error()) + errors[file.File] = []string{err.Error()} } - if schemaOutput != nil { - schemas[i] = NDCRestSchemaWithName{ - Name: file.File, - NDCRestSchema: schemaOutput, - } + + if schemaOutput == nil { + continue } - if len(errs) > 0 { - errors[file.File] = errs + + ndcSchema := NDCRestRuntimeSchema{ + Name: file.File, + NDCRestSchema: schemaOutput, + } + + runtime, err := file.GetRuntimeSettings() + if err != nil { + errors[file.File] = []string{err.Error()} + } else { + ndcSchema.Runtime = *runtime } + schemas[i] = ndcSchema } return schemas, errors } // ReadSchemaOutputFile reads the schema output file in disk -func ReadSchemaOutputFile(configDir string, filePath string, logger *slog.Logger) ([]NDCRestSchemaWithName, error) { +func ReadSchemaOutputFile(configDir string, filePath string, logger *slog.Logger) ([]NDCRestRuntimeSchema, error) { if filePath == "" { return nil, nil } @@ -55,7 +62,7 @@ func ReadSchemaOutputFile(configDir string, filePath string, logger *slog.Logger return nil, fmt.Errorf("failed to read the file at %s: %w", outputFilePath, err) } - var result []NDCRestSchemaWithName + var result []NDCRestRuntimeSchema if err := json.Unmarshal(rawBytes, &result); err != nil { return nil, fmt.Errorf("failed to unmarshal the schema file at %s: %w", outputFilePath, err) } @@ -64,7 +71,7 @@ func ReadSchemaOutputFile(configDir string, filePath string, logger *slog.Logger } // MergeNDCRestSchemas merge REST schemas into a single schema object -func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, []rest.NDCRestSchema, map[string][]string) { +func MergeNDCRestSchemas(config *Configuration, schemas []NDCRestRuntimeSchema) (*rest.NDCRestSchema, []NDCRestRuntimeSchema, map[string][]string) { ndcSchema := &rest.NDCRestSchema{ ScalarTypes: make(schema.SchemaResponseScalarTypes), ObjectTypes: make(map[string]rest.ObjectType), @@ -72,8 +79,7 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, Procedures: make(map[string]rest.OperationInfo), } - appliedSchemas := make([]rest.NDCRestSchema, len(schemas)) - + appliedSchemas := make([]NDCRestRuntimeSchema, len(schemas)) errors := make(map[string][]string) for i, item := range schemas { @@ -86,52 +92,6 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, settings = &rest.NDCRestSettings{} } else { for i, server := range settings.Servers { - if server.Retry == nil { - if settings.Retry != nil { - server.Retry = settings.Retry - } - } else if settings.Retry != nil { - delay, err := server.Retry.Delay.Value() - if err != nil { - errors[fmt.Sprintf("settings.servers[%d].retry.delay", i)] = []string{err.Error()} - return nil, nil, errors - } - if delay == nil || *delay <= 0 { - server.Retry.Delay = settings.Retry.Delay - } - - times, err := server.Retry.Times.Value() - if err != nil { - errors[fmt.Sprintf("settings.servers[%d].retry.times", i)] = []string{err.Error()} - return nil, nil, errors - } - if times == nil || *times <= 0 { - server.Retry.Times = settings.Retry.Times - } - - status, err := server.Retry.HTTPStatus.Value() - if err != nil { - errors[fmt.Sprintf("settings.servers[%d].retry.httpStatus", i)] = []string{err.Error()} - return nil, nil, errors - } - if len(status) == 0 { - server.Retry.HTTPStatus = settings.Retry.HTTPStatus - } - } - - if server.Timeout == nil { - server.Timeout = settings.Timeout - } else { - timeout, err := server.Timeout.Value() - if err != nil { - errors[fmt.Sprintf("settings.servers[%d].timeout", i)] = []string{err.Error()} - return nil, nil, errors - } - if timeout == nil || *timeout <= 0 { - settings.Timeout = rest.NewEnvIntValue(*timeout) - } - } - if server.Security.IsEmpty() { server.Security = settings.Security } @@ -146,7 +106,7 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, } if server.Headers == nil { - server.Headers = make(map[string]rest.EnvString) + server.Headers = make(map[string]utils.EnvString) } for key, value := range settings.Headers { _, ok := server.Headers[key] @@ -158,10 +118,16 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, } } - meta := rest.NDCRestSchema{ - Settings: settings, - Functions: map[string]rest.OperationInfo{}, - Procedures: map[string]rest.OperationInfo{}, + meta := NDCRestRuntimeSchema{ + Name: item.Name, + Runtime: item.Runtime, + NDCRestSchema: &rest.NDCRestSchema{ + Settings: settings, + Functions: map[string]rest.OperationInfo{}, + Procedures: map[string]rest.OperationInfo{}, + ObjectTypes: item.ObjectTypes, + ScalarTypes: item.ScalarTypes, + }, } var errs []string @@ -189,6 +155,7 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, errs = append(errs, fmt.Sprintf("function %s: %s", fnName, err)) continue } + fn := rest.OperationInfo{ Request: req, Arguments: fnItem.Arguments, @@ -229,26 +196,26 @@ func MergeNDCRestSchemas(schemas []NDCRestSchemaWithName) (*rest.NDCRestSchema, return ndcSchema, appliedSchemas, errors } -func buildSchemaFile(configDir string, conf *ConfigItem, logger *slog.Logger) (*rest.NDCRestSchema, error) { - if conf.ConvertConfig.File == "" { +func buildSchemaFile(config *Configuration, configDir string, configItem *ConfigItem, logger *slog.Logger) (*rest.NDCRestSchema, error) { + if configItem.ConvertConfig.File == "" { return nil, errFilePathRequired } - ResolveConvertConfigArguments(&conf.ConvertConfig, configDir, nil) - ndcSchema, err := ConvertToNDCSchema(&conf.ConvertConfig, logger) + ResolveConvertConfigArguments(&configItem.ConvertConfig, configDir, nil) + ndcSchema, err := ConvertToNDCSchema(&configItem.ConvertConfig, logger) if err != nil { return nil, err } if ndcSchema.Settings == nil || len(ndcSchema.Settings.Servers) == 0 { - return nil, fmt.Errorf("the servers setting of schema %s is empty", conf.ConvertConfig.File) + return nil, fmt.Errorf("the servers setting of schema %s is empty", configItem.ConvertConfig.File) } - buildRESTArguments(ndcSchema, conf) + buildRESTArguments(config, ndcSchema, configItem) return ndcSchema, nil } -func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { +func buildRESTArguments(config *Configuration, restSchema *rest.NDCRestSchema, conf *ConfigItem) { if restSchema.Settings == nil || len(restSchema.Settings.Servers) < 2 { return } @@ -270,19 +237,12 @@ func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { restSchema.ScalarTypes[rest.RESTServerIDScalarName] = *serverScalar restSchema.ObjectTypes[rest.RESTSingleOptionsObjectName] = singleObjectType - restSingleOptionsArgument := rest.ArgumentInfo{ - ArgumentInfo: schema.ArgumentInfo{ - Description: singleObjectType.Description, - Type: schema.NewNullableNamedType(rest.RESTSingleOptionsObjectName).Encode(), - }, - } - for _, fn := range restSchema.Functions { - fn.Arguments[rest.RESTOptionsArgumentName] = restSingleOptionsArgument + applyOperationInfo(config, &fn) } for _, proc := range restSchema.Procedures { - proc.Arguments[rest.RESTOptionsArgumentName] = restSingleOptionsArgument + applyOperationInfo(config, &proc) } if !conf.Distributed { @@ -342,6 +302,13 @@ func buildRESTArguments(restSchema *rest.NDCRestSchema, conf *ConfigItem) { } } +func applyOperationInfo(config *Configuration, info *rest.OperationInfo) { + info.Arguments[rest.RESTOptionsArgumentName] = restSingleOptionsArgument + if config.ForwardHeaders.Enabled { + info.Arguments[config.ForwardHeaders.ArgumentName] = headersArguments + } +} + func cloneDistributedArguments(arguments map[string]rest.ArgumentInfo) map[string]rest.ArgumentInfo { result := map[string]rest.ArgumentInfo{} for k, v := range arguments { @@ -355,6 +322,7 @@ func cloneDistributedArguments(arguments map[string]rest.ArgumentInfo) map[strin Type: schema.NewNullableNamedType(rest.RESTDistributedOptionsObjectName).Encode(), }, } + return result } diff --git a/ndc-rest-schema/configuration/types.go b/ndc-rest-schema/configuration/types.go index 74b427d..57cfbf0 100644 --- a/ndc-rest-schema/configuration/types.go +++ b/ndc-rest-schema/configuration/types.go @@ -1,7 +1,9 @@ package configuration import ( + "encoding/json" "errors" + "fmt" rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" restUtils "github.com/hasura/ndc-rest/ndc-rest-schema/utils" @@ -14,14 +16,6 @@ var ( errHTTPMethodRequired = errors.New("the HTTP method is required") ) -// ConfigItem extends the ConvertConfig with advanced options -type ConfigItem struct { - ConvertConfig `yaml:",inline"` - - // Distributed enables distributed schema - Distributed bool `json:"distributed" yaml:"distributed"` -} - // Configuration contains required settings for the connector. type Configuration struct { Output string `json:"output,omitempty" yaml:"output,omitempty"` @@ -37,6 +31,100 @@ type ForwardHeadersSettings struct { ArgumentName string `json:"argumentName" yaml:"argumentName"` } +// UnmarshalJSON implements json.Unmarshaler. +func (j *ForwardHeadersSettings) UnmarshalJSON(b []byte) error { + type Plain ForwardHeadersSettings + var rawResult Plain + if err := json.Unmarshal(b, &rawResult); err != nil { + return err + } + + if rawResult.Enabled && rawResult.ArgumentName == "" { + return errors.New("forwardHeaders.argumentName must not be empty if enabled") + } + + *j = ForwardHeadersSettings(rawResult) + return nil +} + +// RetryPolicySetting represents retry policy settings +type RetryPolicySetting struct { + // Number of retry times + Times utils.EnvInt `json:"times,omitempty" mapstructure:"times" yaml:"times,omitempty"` + // Delay retry delay in milliseconds + Delay utils.EnvInt `json:"delay,omitempty" mapstructure:"delay" yaml:"delay,omitempty"` + // HTTPStatus retries if the remote service returns one of these http status + HTTPStatus []int `json:"httpStatus,omitempty" mapstructure:"httpStatus" yaml:"httpStatus,omitempty"` +} + +// Validate if the current instance is valid +func (rs RetryPolicySetting) Validate() (*rest.RetryPolicy, error) { + times, err := rs.Times.Get() + if err != nil { + return nil, err + } + if times < 0 { + return nil, errors.New("retry policy times must be positive") + } + + delay, err := rs.Delay.Get() + if err != nil { + return nil, err + } + if delay < 0 { + return nil, errors.New("retry delay must be larger than 0") + } + + for _, status := range rs.HTTPStatus { + if status < 400 || status >= 600 { + return nil, errors.New("retry http status must be in between 400 and 599") + } + } + + return &rest.RetryPolicy{ + Times: uint(times), + Delay: uint(delay), + HTTPStatus: rs.HTTPStatus, + }, nil +} + +// ConfigItem extends the ConvertConfig with advanced options +type ConfigItem struct { + ConvertConfig `yaml:",inline"` + + // Distributed enables distributed schema + Distributed bool `json:"distributed" yaml:"distributed"` + // configure the request timeout in seconds. + Timeout *utils.EnvInt `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` + Retry *RetryPolicySetting `json:"retry,omitempty" mapstructure:"retry" yaml:"retry,omitempty"` +} + +// GetRuntimeSettings validate and get runtime settings +func (ci ConfigItem) GetRuntimeSettings() (*rest.RuntimeSettings, error) { + result := &rest.RuntimeSettings{} + + if ci.Timeout != nil { + timeout, err := ci.Timeout.Get() + if err != nil { + return nil, fmt.Errorf("timeout: %w", err) + } + if timeout < 0 { + return nil, fmt.Errorf("timeout must be positive, got: %d", timeout) + } + result.Timeout = uint(timeout) + } + + if ci.Retry != nil { + retryPolicy, err := ci.Retry.Validate() + if err != nil { + return nil, fmt.Errorf("ConfigItem.retry: %w", err) + } + result.Retry = *retryPolicy + } + + return result, nil +} + // ConvertConfig represents the content of convert config file type ConvertConfig struct { // File path needs to be converted @@ -65,9 +153,10 @@ type ConvertConfig struct { Output string `json:"output,omitempty" yaml:"output,omitempty"` } -// NDCRestSchemaWithName wraps NDCRestSchema with identity name -type NDCRestSchemaWithName struct { - Name string `json:"name" yaml:"name"` +// NDCRestRuntimeSchema wraps NDCRestSchema with runtime settings +type NDCRestRuntimeSchema struct { + Name string `json:"name" yaml:"name"` + Runtime rest.RuntimeSettings `json:"-" yaml:"-"` *rest.NDCRestSchema } @@ -120,3 +209,17 @@ var distributedObjectType rest.ObjectType = rest.ObjectType{ }, }, } + +var headersArguments = rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Description: utils.ToPtr("Headers forwarded from the Hasura engine"), + Type: schema.NewNullableNamedType(string(rest.ScalarJSON)).Encode(), + }, +} + +var restSingleOptionsArgument = rest.ArgumentInfo{ + ArgumentInfo: schema.ArgumentInfo{ + Description: singleObjectType.Description, + Type: schema.NewNullableNamedType(rest.RESTSingleOptionsObjectName).Encode(), + }, +} diff --git a/ndc-rest-schema/configuration/update.go b/ndc-rest-schema/configuration/update.go index f02bc34..d6d0e8f 100644 --- a/ndc-rest-schema/configuration/update.go +++ b/ndc-rest-schema/configuration/update.go @@ -1,8 +1,11 @@ package configuration import ( + "encoding/json" "errors" + "fmt" "log/slog" + "os" "path/filepath" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" @@ -11,17 +14,12 @@ import ( // UpdateRESTConfiguration validates and updates the REST configuration func UpdateRESTConfiguration(configurationDir string, logger *slog.Logger) error { - configFilePath := configurationDir + "/config.yaml" - rawConfig, err := utils.ReadFileFromPath(configFilePath) + config, err := ReadConfigurationFile(configurationDir) if err != nil { return err } - var config Configuration - if err := yaml.Unmarshal(rawConfig, &config); err != nil { - return err - } - schemas, errs := BuildSchemaFiles(&config, configurationDir, logger) + schemas, errs := BuildSchemaFromConfig(config, configurationDir, logger) if len(errs) > 0 { printSchemaValidationError(logger, errs) if config.Strict { @@ -29,10 +27,10 @@ func UpdateRESTConfiguration(configurationDir string, logger *slog.Logger) error } } - validatedResults, _, errs := MergeNDCRestSchemas(schemas) + _, validatedSchemas, errs := MergeNDCRestSchemas(config, schemas) if len(errs) > 0 { printSchemaValidationError(logger, errs) - if validatedResults == nil || config.Strict { + if validatedSchemas == nil || config.Strict { return errors.New("invalid rest schema") } } @@ -48,3 +46,42 @@ func UpdateRESTConfiguration(configurationDir string, logger *slog.Logger) error func printSchemaValidationError(logger *slog.Logger, errors map[string][]string) { logger.Error("errors happen when validating NDC REST schemas", slog.Any("errors", errors)) } + +// ReadConfigurationFile reads and decodes the configuration file from the configuration directory +func ReadConfigurationFile(configurationDir string) (*Configuration, error) { + var config Configuration + jsonBytes, err := os.ReadFile(configurationDir + "/config.json") + if err == nil { + if err = json.Unmarshal(jsonBytes, &config); err != nil { + return nil, err + } + return &config, nil + } + + if !os.IsNotExist(err) { + return nil, err + } + + // try to read and parse yaml file + yamlBytes, err := os.ReadFile(configurationDir + "/config.yaml") + if err != nil { + if !os.IsNotExist(err) { + return nil, err + } + yamlBytes, err = os.ReadFile(configurationDir + "/config.yml") + } + + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("the config.{json,yaml,yml} file does not exist at %s", configurationDir) + } else { + return nil, err + } + } + + if err = yaml.Unmarshal(yamlBytes, &config); err != nil { + return nil, err + } + + return &config, nil +} diff --git a/ndc-rest-schema/go.mod b/ndc-rest-schema/go.mod index bd91975..16b4bf1 100644 --- a/ndc-rest-schema/go.mod +++ b/ndc-rest-schema/go.mod @@ -5,12 +5,13 @@ go 1.23.0 toolchain go1.23.1 require ( - github.com/alecthomas/kong v1.2.1 + github.com/alecthomas/kong v1.4.0 github.com/evanphx/json-patch v0.5.2 - github.com/hasura/ndc-sdk-go v1.6.2 + github.com/google/go-cmp v0.6.0 + github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5 github.com/invopop/jsonschema v0.12.0 github.com/lmittmann/tint v1.0.5 - github.com/pb33f/libopenapi v0.18.3 + github.com/pb33f/libopenapi v0.18.6 github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd gopkg.in/yaml.v3 v3.0.1 gotest.tools/v3 v3.5.1 @@ -21,15 +22,15 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 // indirect github.com/go-viper/mapstructure/v2 v2.2.1 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/sergi/go-diff v1.3.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect golang.org/x/net v0.30.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/sys v0.26.0 // indirect + google.golang.org/protobuf v1.35.1 // indirect ) diff --git a/ndc-rest-schema/go.sum b/ndc-rest-schema/go.sum index b43090b..51ca3fd 100644 --- a/ndc-rest-schema/go.sum +++ b/ndc-rest-schema/go.sum @@ -1,7 +1,7 @@ -github.com/alecthomas/assert/v2 v2.10.0 h1:jjRCHsj6hBJhkmhznrCzoNpbA3zqy0fYiUcYZP/GkPY= -github.com/alecthomas/assert/v2 v2.10.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= -github.com/alecthomas/kong v1.2.1 h1:E8jH4Tsgv6wCRX2nGrdPyHDUCSG83WH2qE4XLACD33Q= -github.com/alecthomas/kong v1.2.1/go.mod h1:rKTSFhbdp3Ryefn8x5MOEprnRFQ7nlmMC01GKhehhBM= +github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= +github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= +github.com/alecthomas/kong v1.4.0 h1:UL7tzGMnnY0YRMMvJyITIRX1EpO6RbBRZDNcCevy3HA= +github.com/alecthomas/kong v1.4.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= @@ -43,8 +43,8 @@ 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hasura/ndc-sdk-go v1.6.2 h1:xJtFJ0OduqWmL60GhAYn3gzt1TvEwttSKv8u59MJqDQ= -github.com/hasura/ndc-sdk-go v1.6.2/go.mod h1:H7iN3SFXSou2rjBKv9fLumbvDXMDGP0Eg+cXWHpkA3k= +github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5 h1:YA3ix2/SMZ+vR/96YXuSPNYHsocsWnY8xCmhJeT3RYs= +github.com/hasura/ndc-sdk-go v1.6.2-0.20241109102535-399b739f7af5/go.mod h1:H7iN3SFXSou2rjBKv9fLumbvDXMDGP0Eg+cXWHpkA3k= 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= @@ -77,16 +77,16 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/pb33f/libopenapi v0.18.3 h1:j4lm8xMM/GYSj2M8S7qNwZ//rOtEK5ACEiuNC7mTJzE= -github.com/pb33f/libopenapi v0.18.3/go.mod h1:9ap4lXBHgxGyFwxtOfa+B1C3IQ0rvnqteqjJvJ11oiQ= +github.com/pb33f/libopenapi v0.18.6 h1:adxzZUnOBOAuKxFAIrtb1Qt8GA4XnDWUAxEnqiSoTh0= +github.com/pb33f/libopenapi v0.18.6/go.mod h1:qZRs2IHIcs9SjHPmQfSUCyeD3OY9JkLJQOuFxd0bYCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= @@ -100,8 +100,8 @@ github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd h1:dLuIF2kX9c+KknGJUdJi1Il1SDiTSK158/BB9kdgAew= github.com/wk8/go-ordered-map/v2 v2.1.9-0.20240815153524-6ea36470d1bd/go.mod h1:DbzwytT4g/odXquuOCqroKvtxxldI4nb3nuesHF/Exo= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= 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= @@ -157,8 +157,8 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/ndc-rest-schema/openapi/internal/oas2.go b/ndc-rest-schema/openapi/internal/oas2.go index 7875769..187cc1e 100644 --- a/ndc-rest-schema/openapi/internal/oas2.go +++ b/ndc-rest-schema/openapi/internal/oas2.go @@ -10,6 +10,7 @@ import ( rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" "github.com/hasura/ndc-sdk-go/schema" + sdkUtils "github.com/hasura/ndc-sdk-go/utils" "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" v2 "github.com/pb33f/libopenapi/datamodel/high/v2" @@ -32,7 +33,6 @@ func NewOAS2Builder(schema *rest.NDCRestSchema, options ConvertOptions) *OAS2Bui ConvertOptions: applyConvertOptions(options), } - setDefaultSettings(builder.schema.Settings, builder.ConvertOptions) return builder } @@ -57,7 +57,7 @@ func (oc *OAS2Builder) BuildDocumentModel(docModel *libopenapi.DocumentModel[v2. envName := utils.StringSliceToConstantCase([]string{oc.EnvPrefix, "SERVER_URL"}) serverURL := fmt.Sprintf("%s://%s%s", scheme, docModel.Model.Host, docModel.Model.BasePath) oc.schema.Settings.Servers = append(oc.schema.Settings.Servers, rest.ServerConfig{ - URL: *rest.NewEnvStringTemplate(rest.NewEnvTemplateWithDefault(envName, serverURL)), + URL: sdkUtils.NewEnvString(envName, serverURL), }) } @@ -109,9 +109,8 @@ func (oc *OAS2Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v2 In: inLocation, Name: security.Name, } - result.Value = rest.NewEnvStringTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key}), - }) + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key})) + result.Value = &valueEnv result.APIKeyAuthConfig = &apiConfig case "basic": result.Type = rest.HTTPAuthScheme @@ -119,9 +118,8 @@ func (oc *OAS2Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v2 Scheme: "Basic", Header: "Authorization", } - result.Value = rest.NewEnvStringTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"}), - }) + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"})) + result.Value = &valueEnv result.HTTPAuthConfig = &httpConfig case "oauth2": var flowType rest.OAuthFlowType diff --git a/ndc-rest-schema/openapi/internal/oas3.go b/ndc-rest-schema/openapi/internal/oas3.go index 5ee044e..533ff31 100644 --- a/ndc-rest-schema/openapi/internal/oas3.go +++ b/ndc-rest-schema/openapi/internal/oas3.go @@ -8,6 +8,7 @@ import ( rest "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "github.com/hasura/ndc-rest/ndc-rest-schema/utils" "github.com/hasura/ndc-sdk-go/schema" + sdkUtils "github.com/hasura/ndc-sdk-go/utils" "github.com/pb33f/libopenapi" "github.com/pb33f/libopenapi/datamodel/high/base" v3 "github.com/pb33f/libopenapi/datamodel/high/v3" @@ -43,7 +44,6 @@ func NewOAS3Builder(schema *rest.NDCRestSchema, options ConvertOptions) *OAS3Bui ConvertOptions: applyConvertOptions(options), } - setDefaultSettings(builder.schema.Settings, builder.ConvertOptions) return builder } @@ -122,7 +122,7 @@ func (oc *OAS3Builder) convertServers(servers []*v3.Server) []rest.ServerConfig conf := rest.ServerConfig{ ID: serverID, - URL: *rest.NewEnvStringTemplate(rest.NewEnvTemplateWithDefault(envName, serverURL)), + URL: sdkUtils.NewEnvString(envName, serverURL), } results = append(results, conf) } @@ -154,14 +154,17 @@ func (oc *OAS3Builder) convertSecuritySchemes(scheme orderedmap.Pair[string, *v3 In: inLocation, Name: security.Name, } - result.Value = rest.NewEnvStringTemplate(rest.NewEnvTemplate(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key}))) + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key})) + result.Value = &valueEnv result.APIKeyAuthConfig = &apiConfig case rest.HTTPAuthScheme: httpConfig := rest.HTTPAuthConfig{ Scheme: security.Scheme, Header: "Authorization", } - result.Value = rest.NewEnvStringTemplate(rest.NewEnvTemplate(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"}))) + + valueEnv := sdkUtils.NewEnvStringVariable(utils.StringSliceToConstantCase([]string{oc.EnvPrefix, key, "TOKEN"})) + result.Value = &valueEnv result.HTTPAuthConfig = &httpConfig case rest.OAuth2Scheme: if security.Flows == nil { diff --git a/ndc-rest-schema/openapi/internal/utils.go b/ndc-rest-schema/openapi/internal/utils.go index f3fd0cd..e34b7c6 100644 --- a/ndc-rest-schema/openapi/internal/utils.go +++ b/ndc-rest-schema/openapi/internal/utils.go @@ -287,23 +287,6 @@ func encodeHeaderArgumentName(name string) string { return "header" + utils.ToPascalCase(name) } -func setDefaultSettings(settings *rest.NDCRestSettings, opts *ConvertOptions) { - settings.Timeout = rest.NewEnvIntTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{opts.EnvPrefix, "TIMEOUT"}), - }) - settings.Retry = &rest.RetryPolicySetting{ - Times: *rest.NewEnvIntTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{opts.EnvPrefix, "RETRY_TIMES"}), - }), - Delay: *rest.NewEnvIntTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{opts.EnvPrefix, "RETRY_DELAY"}), - }), - HTTPStatus: *rest.NewEnvIntsTemplate(rest.EnvTemplate{ - Name: utils.StringSliceToConstantCase([]string{opts.EnvPrefix, "RETRY_HTTP_STATUS"}), - }), - } -} - // evaluate and filter invalid types in allOf, anyOf or oneOf schemas func evalSchemaProxiesSlice(schemaProxies []*base.SchemaProxy, location rest.ParameterLocation) ([]*base.SchemaProxy, *base.Schema, bool) { var results []*base.SchemaProxy diff --git a/ndc-rest-schema/openapi/oas2_test.go b/ndc-rest-schema/openapi/oas2_test.go index 8e19331..d079308 100644 --- a/ndc-rest-schema/openapi/oas2_test.go +++ b/ndc-rest-schema/openapi/oas2_test.go @@ -12,7 +12,6 @@ import ( ) func TestOpenAPIv2ToRESTSchema(t *testing.T) { - testCases := []struct { Name string Source string @@ -100,5 +99,5 @@ func assertConnectorSchema(t *testing.T, schemaPath string, output *rest.NDCRest var expectedSchema schema.SchemaResponse assert.NilError(t, json.Unmarshal(schemaBytes, &expectedSchema)) outputSchema := output.ToSchemaResponse() - assert.DeepEqual(t, expectedSchema, *outputSchema) + assetDeepEqual(t, expectedSchema, *outputSchema) } diff --git a/ndc-rest-schema/openapi/oas3_test.go b/ndc-rest-schema/openapi/oas3_test.go index 2037b19..c8b4c9a 100644 --- a/ndc-rest-schema/openapi/oas3_test.go +++ b/ndc-rest-schema/openapi/oas3_test.go @@ -4,8 +4,11 @@ import ( "encoding/json" "errors" "os" + "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/hasura/ndc-rest/ndc-rest-schema/schema" "gotest.tools/v3/assert" ) @@ -101,12 +104,33 @@ func TestOpenAPIv3ToRESTSchema(t *testing.T) { func assertRESTSchemaEqual(t *testing.T, expected *schema.NDCRestSchema, output *schema.NDCRestSchema) { t.Helper() - assert.DeepEqual(t, expected.Settings, output.Settings) - assert.DeepEqual(t, expected.ScalarTypes, output.ScalarTypes) + assetDeepEqual(t, expected.Settings.Headers, output.Settings.Headers) + assetDeepEqual(t, expected.Settings.Security, output.Settings.Security) + assetDeepEqual(t, expected.Settings.SecuritySchemes, output.Settings.SecuritySchemes) + assetDeepEqual(t, expected.Settings.Version, output.Settings.Version) + for i, server := range expected.Settings.Servers { + sv := output.Settings.Servers[i] + assetDeepEqual(t, server.Headers, sv.Headers) + assetDeepEqual(t, server.ID, sv.ID) + assetDeepEqual(t, server.Security, sv.Security) + assetDeepEqual(t, server.SecuritySchemes, sv.SecuritySchemes) + assetDeepEqual(t, server.URL, sv.URL) + assetDeepEqual(t, server.TLS, sv.TLS) + } + assetDeepEqual(t, expected.ScalarTypes, output.ScalarTypes) objectBs, _ := json.Marshal(output.ObjectTypes) var objectTypes map[string]schema.ObjectType assert.NilError(t, json.Unmarshal(objectBs, &objectTypes)) - assert.DeepEqual(t, expected.ObjectTypes, objectTypes) - assert.DeepEqual(t, expected.Procedures, output.Procedures) - assert.DeepEqual(t, expected.Functions, output.Functions) + assetDeepEqual(t, expected.ObjectTypes, objectTypes) + assetDeepEqual(t, expected.Procedures, output.Procedures) + assetDeepEqual(t, expected.Functions, output.Functions) +} + +func assetDeepEqual(t *testing.T, expected any, reality any) { + t.Helper() + assert.DeepEqual(t, + expected, reality, + cmpopts.IgnoreUnexported(schema.ServerConfig{}, schema.NDCRestSettings{}), + cmp.Exporter(func(t reflect.Type) bool { return true }), + ) } diff --git a/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json b/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json index dd616a5..53ba330 100644 --- a/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json +++ b/ndc-rest-schema/openapi/testdata/jsonplaceholder/expected.json @@ -3,15 +3,12 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://jsonplaceholder.typicode.com}}" + "url": { + "env": "SERVER_URL", + "value": "https://jsonplaceholder.typicode.com" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "version": "1.0.0" }, "functions": { @@ -37,9 +34,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -56,9 +51,7 @@ "name": "userId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -92,9 +85,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -125,9 +116,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -161,9 +150,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -197,9 +184,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -216,9 +201,7 @@ "name": "postId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -252,9 +235,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -288,9 +269,7 @@ "name": "albumId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -307,9 +286,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -343,9 +320,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -379,9 +354,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -398,9 +371,7 @@ "name": "userId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -434,9 +405,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -486,9 +455,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -522,9 +489,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -541,9 +506,7 @@ "name": "userId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -577,9 +540,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -613,9 +574,7 @@ "name": "email", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -632,9 +591,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -662,9 +619,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -677,9 +632,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userId": { @@ -691,9 +644,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -710,9 +661,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "email": { @@ -724,9 +673,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "email" } }, @@ -739,9 +686,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -754,9 +699,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "postId": { @@ -768,9 +711,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -787,9 +728,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -802,9 +741,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -817,9 +754,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "uri" } }, @@ -832,9 +767,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "url": { @@ -846,9 +779,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "uri" } } @@ -865,9 +796,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -879,9 +808,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -894,9 +821,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userId": { @@ -908,9 +833,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -927,9 +850,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "id": { @@ -941,9 +862,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -956,9 +875,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userId": { @@ -970,9 +887,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -989,9 +904,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "company": { @@ -1003,9 +916,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "email": { @@ -1017,9 +928,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "email" } }, @@ -1032,9 +941,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -1047,9 +954,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "phone": { @@ -1061,9 +966,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "username": { @@ -1075,9 +978,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "website": { @@ -1089,9 +990,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1107,9 +1006,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "geo": { @@ -1121,9 +1018,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "street": { @@ -1135,9 +1030,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "suite": { @@ -1149,9 +1042,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "zipcode": { @@ -1163,9 +1054,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1181,9 +1070,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "lng": { @@ -1195,9 +1082,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1213,9 +1098,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "catchPhrase": { @@ -1227,9 +1110,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -1241,9 +1122,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1271,9 +1150,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1304,9 +1181,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1342,9 +1217,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1358,9 +1231,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1393,9 +1264,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1409,9 +1278,7 @@ "name": "id", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } diff --git a/ndc-rest-schema/openapi/testdata/onesignal/expected-patch.json b/ndc-rest-schema/openapi/testdata/onesignal/expected-patch.json index eef43f6..24c68e8 100644 --- a/ndc-rest-schema/openapi/testdata/onesignal/expected-patch.json +++ b/ndc-rest-schema/openapi/testdata/onesignal/expected-patch.json @@ -4,25 +4,26 @@ "servers": [ { "id": "foo", - "url": "{{FOO_SERVER_URL:-https://onesignal.com/api/v1}}" + "url": { + "env": "FOO_SERVER_URL", + "value": "https://onesignal.com/api/v1" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "app_key": { "type": "http", - "value": "{{APP_KEY_TOKEN}}", + "value": { + "env": "APP_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" }, "user_key": { "type": "http", - "value": "{{USER_KEY_TOKEN}}", + "value": { + "env": "USER_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" } diff --git a/ndc-rest-schema/openapi/testdata/onesignal/expected.json b/ndc-rest-schema/openapi/testdata/onesignal/expected.json index ee2476d..bf5ff41 100644 --- a/ndc-rest-schema/openapi/testdata/onesignal/expected.json +++ b/ndc-rest-schema/openapi/testdata/onesignal/expected.json @@ -3,25 +3,26 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://onesignal.com/api/v1}}" + "url": { + "env": "SERVER_URL", + "value": "https://onesignal.com/api/v1" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "app_key": { "type": "http", - "value": "{{APP_KEY_TOKEN}}", + "value": { + "env": "APP_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" }, "user_key": { "type": "http", - "value": "{{USER_KEY_TOKEN}}", + "value": { + "env": "USER_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" } @@ -52,9 +53,7 @@ "name": "app_id", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -67,9 +66,7 @@ "name": "notification_id", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -105,9 +102,7 @@ "name": "app_id", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -124,9 +119,7 @@ "name": "kind", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -143,9 +136,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -162,9 +153,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -189,9 +178,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -219,9 +206,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -233,9 +218,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "recipients": { @@ -247,9 +230,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -266,9 +247,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -281,9 +260,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "failed": { @@ -296,9 +273,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "received": { @@ -311,9 +286,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "successful": { @@ -326,9 +299,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -342,9 +313,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "key": { @@ -357,9 +326,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "relation": { @@ -369,9 +336,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -384,9 +349,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -402,9 +365,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "email": { @@ -417,9 +378,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "events": { @@ -432,9 +391,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -450,9 +407,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "success": { @@ -464,9 +419,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -482,9 +435,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "custom_data": { @@ -496,9 +447,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "data": { @@ -511,9 +460,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "filters": { @@ -528,9 +475,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -542,9 +487,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -556,9 +499,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "send_after": { @@ -570,9 +511,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -585,9 +524,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -603,9 +540,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "notifications": { @@ -620,9 +555,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "offset": { @@ -634,9 +567,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "total_count": { @@ -648,9 +579,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -667,9 +596,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -682,9 +609,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "converted": { @@ -697,9 +622,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -712,9 +635,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "excluded_segments": { @@ -729,13 +650,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -749,9 +666,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "filters": { @@ -766,9 +681,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -780,9 +693,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -794,9 +705,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "include_player_ids": { @@ -811,13 +720,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -833,13 +738,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -855,9 +756,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "platform_delivery_stats": { @@ -870,9 +769,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "queued_at": { @@ -885,9 +782,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -901,9 +796,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "remaining": { @@ -916,9 +809,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "send_after": { @@ -931,9 +822,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -946,9 +835,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "successful": { @@ -961,9 +848,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "target_channel": { @@ -975,9 +860,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "throttle_rate_per_minute": { @@ -990,9 +873,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1005,9 +886,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -1016,9 +895,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -1027,9 +904,7 @@ "type": "named" }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1046,9 +921,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "chrome_web_push": { @@ -1060,9 +933,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "edge_web_push": { @@ -1074,9 +945,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "email": { @@ -1088,9 +957,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "firefox_web_push": { @@ -1102,9 +969,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "ios": { @@ -1116,9 +981,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "safari_web_push": { @@ -1130,9 +993,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "sms": { @@ -1144,9 +1005,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1163,9 +1022,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1195,9 +1052,7 @@ "name": "app_id", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -1210,9 +1065,7 @@ "name": "notification_id", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1296,9 +1149,7 @@ "name": "notification_id", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1368,10 +1219,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sent", - "clicked" - ], + "one_of": ["sent", "clicked"], "type": "enum" } }, @@ -1379,10 +1227,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], + "one_of": ["sum", "count"], "type": "enum" } }, @@ -1390,11 +1235,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], + "one_of": ["push", "email", "sms"], "type": "enum" } }, diff --git a/ndc-rest-schema/openapi/testdata/openai/expected.json b/ndc-rest-schema/openapi/testdata/openai/expected.json index 1e751f7..8c418a6 100644 --- a/ndc-rest-schema/openapi/testdata/openai/expected.json +++ b/ndc-rest-schema/openapi/testdata/openai/expected.json @@ -3,22 +3,24 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://api.openai.com/v1}}" + "url": { + "env": "SERVER_URL", + "value": "https://api.openai.com/v1" + } }, { - "url": "{{SERVER_URL_2:-http://127.0.0.1:11434}}" + "url": { + "env": "SERVER_URL_2", + "value": "http://127.0.0.1:11434" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "ApiKeyAuth": { "type": "http", - "value": "{{API_KEY_AUTH_TOKEN}}", + "value": { + "env": "API_KEY_AUTH_TOKEN" + }, "header": "Authorization", "scheme": "bearer" } @@ -44,9 +46,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -56,9 +56,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "parameters": { @@ -71,9 +69,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -87,9 +83,7 @@ "type": "named" }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -99,9 +93,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -111,9 +103,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -128,9 +118,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -140,9 +128,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -157,9 +143,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "function_call": { @@ -172,9 +156,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "role": { @@ -184,9 +166,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "tool_calls": { @@ -202,9 +182,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -219,9 +197,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -231,9 +207,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -251,9 +225,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } } } @@ -270,13 +242,9 @@ "type": "array" }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -287,9 +255,7 @@ "type": "named" }, "rest": { - "type": [ - "number" - ] + "type": ["number"] } }, "token": { @@ -299,9 +265,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "top_logprobs": { @@ -314,13 +278,9 @@ "type": "array" }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -338,13 +298,9 @@ "type": "array" }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -355,9 +311,7 @@ "type": "named" }, "rest": { - "type": [ - "number" - ] + "type": ["number"] } }, "token": { @@ -367,9 +321,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -382,9 +334,7 @@ "type": "named" }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "type": { @@ -394,9 +344,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -413,9 +361,7 @@ } }, "rest": { - "type": [ - "number" - ], + "type": ["number"], "maximum": 2, "minimum": -2 } @@ -446,9 +392,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "logit_bias": { @@ -461,9 +405,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "logprobs": { @@ -476,9 +418,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "max_tokens": { @@ -491,9 +431,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "messages": { @@ -506,9 +444,7 @@ "type": "array" }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "model": { @@ -518,9 +454,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "n": { @@ -533,9 +467,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "maximum": 128, "minimum": 1 } @@ -550,9 +482,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "presence_penalty": { @@ -565,9 +495,7 @@ } }, "rest": { - "type": [ - "number" - ], + "type": ["number"], "maximum": 2, "minimum": -2 } @@ -582,9 +510,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "seed": { @@ -597,9 +523,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "maximum": 9223372036854776000, "minimum": -9223372036854776000 } @@ -614,9 +538,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "stop": { @@ -642,9 +564,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "stream_options": { @@ -657,9 +577,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "temperature": { @@ -672,9 +590,7 @@ } }, "rest": { - "type": [ - "number" - ], + "type": ["number"], "maximum": 2, "minimum": 0 } @@ -705,9 +621,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "top_logprobs": { @@ -720,9 +634,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "maximum": 20, "minimum": 0 } @@ -737,9 +649,7 @@ } }, "rest": { - "type": [ - "number" - ], + "type": ["number"], "maximum": 1, "minimum": 0 } @@ -754,9 +664,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -774,9 +682,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -794,13 +700,9 @@ "type": "array" }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -811,9 +713,7 @@ "type": "named" }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "id": { @@ -823,9 +723,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "model": { @@ -835,9 +733,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "object": { @@ -847,9 +743,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "service_tier": { @@ -862,9 +756,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "system_fingerprint": { @@ -877,9 +769,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -893,9 +783,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "index": { @@ -905,9 +793,7 @@ "type": "named" }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "logprobs": { @@ -917,9 +803,7 @@ "type": "named" }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "message": { @@ -929,9 +813,7 @@ "type": "named" }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -949,9 +831,7 @@ "type": "array" }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -968,9 +848,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -987,9 +865,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "name": { @@ -999,9 +875,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "parameters": { @@ -1014,9 +888,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1033,9 +905,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "digest": { @@ -1048,9 +918,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "status": { @@ -1063,9 +931,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "total": { @@ -1078,9 +944,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1163,9 +1027,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "function" - ], + "one_of": ["function"], "type": "enum" } }, @@ -1180,9 +1042,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "assistant" - ], + "one_of": ["assistant"], "type": "enum" } }, @@ -1197,9 +1057,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "function" - ], + "one_of": ["function"], "type": "enum" } }, @@ -1207,10 +1065,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "text", - "json_object" - ], + "one_of": ["text", "json_object"], "type": "enum" } }, @@ -1218,10 +1073,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "auto", - "default" - ], + "one_of": ["auto", "default"], "type": "enum" } }, @@ -1243,9 +1095,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "chat.completion" - ], + "one_of": ["chat.completion"], "type": "enum" } }, @@ -1253,10 +1103,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "scale", - "default" - ], + "one_of": ["scale", "default"], "type": "enum" } }, diff --git a/ndc-rest-schema/openapi/testdata/petstore2/expected.json b/ndc-rest-schema/openapi/testdata/petstore2/expected.json index 2d57c01..6ddd20f 100644 --- a/ndc-rest-schema/openapi/testdata/petstore2/expected.json +++ b/ndc-rest-schema/openapi/testdata/petstore2/expected.json @@ -3,25 +3,26 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://petstore.swagger.io/v2}}" + "url": { + "env": "SERVER_URL", + "value": "https://petstore.swagger.io/v2" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "api_key": { "type": "apiKey", - "value": "{{API_KEY}}", + "value": { + "env": "API_KEY" + }, "in": "header", "name": "api_key" }, "basic": { "type": "http", - "value": "{{BASIC_TOKEN}}", + "value": { + "env": "BASIC_TOKEN" + }, "header": "Authorization", "scheme": "Basic" }, @@ -47,10 +48,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -71,9 +69,7 @@ "name": "status", "in": "query", "schema": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -94,10 +90,7 @@ "method": "get", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -118,9 +111,7 @@ "name": "tags", "in": "query", "schema": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -175,9 +166,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "maximum": 10, "minimum": 1 } @@ -215,9 +204,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -264,9 +251,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -300,9 +285,7 @@ "name": "client_name", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -319,9 +302,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -338,9 +319,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -357,9 +336,7 @@ "name": "owner", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -393,9 +370,7 @@ "name": "password", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -409,9 +384,7 @@ "name": "username", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -436,9 +409,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -451,9 +422,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "type": { @@ -465,9 +434,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -483,9 +450,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -498,9 +463,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -517,9 +480,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_name": { @@ -532,9 +493,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_secret": { @@ -547,9 +506,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "client_secret_expires_at": { @@ -562,9 +519,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -578,9 +533,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -596,9 +549,7 @@ } }, "rest": { - "type": [ - "boolean" - ] + "type": ["boolean"] } }, "id": { @@ -610,9 +561,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -625,9 +574,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -640,9 +587,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -655,9 +600,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -671,9 +614,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -689,9 +630,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -703,9 +642,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -715,9 +652,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "photoUrls": { @@ -729,13 +664,9 @@ "type": "array" }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -749,9 +680,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "tags": { @@ -766,9 +695,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } } } @@ -796,9 +723,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -811,9 +736,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -829,9 +752,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -844,9 +765,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -863,9 +782,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "status": { @@ -878,9 +795,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -897,9 +812,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "file": { @@ -912,9 +825,7 @@ } }, "rest": { - "type": [ - "file" - ] + "type": ["file"] } } } @@ -930,9 +841,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "firstName": { @@ -944,9 +853,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -958,9 +865,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -973,9 +878,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "password": { @@ -987,9 +890,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "phone": { @@ -1001,9 +902,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userStatus": { @@ -1016,9 +915,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int32" } }, @@ -1031,9 +928,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1046,10 +941,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -1069,9 +961,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1121,9 +1011,7 @@ "name": "orderId", "in": "path", "schema": { - "type": [ - "integer" - ], + "type": ["integer"], "minimum": 1 } } @@ -1145,10 +1033,7 @@ "method": "delete", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "response": { @@ -1168,9 +1053,7 @@ "name": "api_key", "in": "header", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -1184,9 +1067,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1220,9 +1101,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1257,9 +1136,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1292,9 +1169,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1312,10 +1187,7 @@ "method": "put", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -1335,9 +1207,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1358,10 +1228,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -1381,9 +1248,7 @@ "rest": { "in": "formData", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1397,9 +1262,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1435,9 +1298,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1451,9 +1312,7 @@ "name": "username", "in": "path", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1474,10 +1333,7 @@ "method": "post", "security": [ { - "petstore_auth": [ - "write:pets", - "read:pets" - ] + "petstore_auth": ["write:pets", "read:pets"] } ], "requestBody": { @@ -1497,9 +1353,7 @@ "rest": { "in": "formData", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } }, @@ -1513,9 +1367,7 @@ "name": "petId", "in": "path", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -1568,11 +1420,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "placed", - "approved", - "delivered" - ], + "one_of": ["placed", "approved", "delivered"], "type": "enum" } }, @@ -1580,11 +1428,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "available", - "pending", - "sold" - ], + "one_of": ["available", "pending", "sold"], "type": "enum" } }, diff --git a/ndc-rest-schema/openapi/testdata/petstore3/expected.json b/ndc-rest-schema/openapi/testdata/petstore3/expected.json index 12801ec..db064b3 100644 --- a/ndc-rest-schema/openapi/testdata/petstore3/expected.json +++ b/ndc-rest-schema/openapi/testdata/petstore3/expected.json @@ -3,28 +3,32 @@ "settings": { "servers": [ { - "url": "{{PET_STORE_SERVER_URL:-https://petstore3.swagger.io/api/v3}}" + "url": { + "value": "https://petstore3.swagger.io/api/v3", + "env": "PET_STORE_SERVER_URL" + } }, { - "url": "{{PET_STORE_SERVER_URL_2:-https://petstore3.swagger.io/api/v3.1}}" + "url": { + "value": "https://petstore3.swagger.io/api/v3.1", + "env": "PET_STORE_SERVER_URL_2" + } } ], - "timeout": "{{PET_STORE_TIMEOUT}}", - "retry": { - "times": "{{PET_STORE_RETRY_TIMES}}", - "delay": "{{PET_STORE_RETRY_DELAY}}", - "httpStatus": "{{PET_STORE_RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "api_key": { "type": "apiKey", - "value": "{{PET_STORE_API_KEY}}", + "value": { + "env": "PET_STORE_API_KEY" + }, "in": "header", "name": "api_key" }, "basic": { "type": "http", - "value": "{{PET_STORE_BASIC_TOKEN}}", + "value": { + "env": "PET_STORE_BASIC_TOKEN" + }, "header": "Authorization", "scheme": "basic" }, @@ -275,7 +279,6 @@ } }, "description": "You can list all invoices, or list the invoices for a specific customer. The invoices are returned sorted by creation date, with the most recently created invoices appearing first.", - "name": "GetInvoices", "result_type": { "name": "GetInvoicesResult", "type": "named" @@ -320,7 +323,6 @@ } }, "description": "Finds Pets by status", - "name": "findPetsByStatus", "result_type": { "element_type": { "name": "Pet", @@ -376,7 +378,6 @@ } }, "description": "Finds Pets by tags", - "name": "findPetsByTags", "result_type": { "element_type": { "name": "Pet", @@ -400,7 +401,6 @@ }, "arguments": {}, "description": "Returns pet inventories by status", - "name": "getInventory", "result_type": { "name": "JSON", "type": "named" @@ -434,7 +434,6 @@ } }, "description": "Find purchase order by ID", - "name": "getOrderById", "result_type": { "name": "Order", "type": "named" @@ -479,7 +478,6 @@ } }, "description": "Find pet by ID", - "name": "getPetById", "result_type": { "name": "Pet", "type": "named" @@ -495,7 +493,6 @@ }, "arguments": {}, "description": "Get snake object", - "name": "getSnake", "result_type": { "name": "SnakeObject", "type": "named" @@ -528,7 +525,6 @@ } }, "description": "Get user by user name", - "name": "getUserByName", "result_type": { "name": "User", "type": "named" @@ -583,7 +579,6 @@ } }, "description": "Logs user into the system", - "name": "loginUser", "result_type": { "name": "String", "type": "named" @@ -6231,7 +6226,6 @@ } }, "description": "DELETE /v1/accounts/{account}", - "name": "DeleteAccountsAccount", "result_type": { "name": "TreasuryInboundTransfer", "type": "named" @@ -6346,7 +6340,6 @@ } }, "description": "Creates a Session object.", - "name": "PostCheckoutSessions", "result_type": { "name": "Order", "type": "named" @@ -6358,7 +6351,10 @@ "method": "post", "servers": [ { - "url": "{{PET_STORE_SERVER_URL:-https://files.stripe.com/}}" + "url": { + "value": "https://files.stripe.com/", + "env": "PET_STORE_SERVER_URL" + } } ], "requestBody": { @@ -6424,7 +6420,6 @@ } }, "description": "POST /v1/files", - "name": "PostFiles", "result_type": { "name": "ApiResponse", "type": "named" @@ -6484,7 +6479,6 @@ } }, "description": "Transitions a test mode created InboundTransfer to the failed status. The InboundTransfer must already be in the processing state.", - "name": "PostTestHelpersTreasuryInboundTransfersIdFail", "result_type": { "name": "TreasuryInboundTransfer", "type": "named" @@ -6522,7 +6516,6 @@ } }, "description": "Add a new pet to the store", - "name": "addPet", "result_type": { "name": "Pet", "type": "named" @@ -6538,7 +6531,6 @@ }, "arguments": {}, "description": "Add snake object", - "name": "addSnake", "result_type": { "name": "SnakeObject", "type": "named" @@ -6568,7 +6560,6 @@ } }, "description": "POST /fine_tuning/jobs", - "name": "createFineTuningJob", "result_type": { "name": "CreateFineTuningJobRequest", "type": "named" @@ -6604,7 +6595,6 @@ } }, "description": "Creates list of users with given input array", - "name": "createUsersWithListInput", "result_type": { "name": "User", "type": "named" @@ -6638,7 +6628,6 @@ } }, "description": "Delete purchase order by ID", - "name": "deleteOrder", "result_type": { "type": "nullable", "underlying_type": { @@ -6701,7 +6690,6 @@ } }, "description": "Deletes a pet", - "name": "deletePet", "result_type": { "type": "nullable", "underlying_type": { @@ -6737,7 +6725,6 @@ } }, "description": "Delete user", - "name": "deleteUser", "result_type": { "type": "nullable", "underlying_type": { @@ -6773,7 +6760,6 @@ } }, "description": "Place an order for a pet", - "name": "placeOrder", "result_type": { "name": "Order", "type": "named" @@ -6811,7 +6797,6 @@ } }, "description": "Update an existing pet", - "name": "updatePet", "result_type": { "name": "Pet", "type": "named" @@ -6891,7 +6876,6 @@ } }, "description": "Updates a pet in the store with form data", - "name": "updatePetWithForm", "result_type": { "type": "nullable", "underlying_type": { @@ -6971,7 +6955,6 @@ } }, "description": "uploads an image", - "name": "uploadFile", "result_type": { "name": "ApiResponse", "type": "named" @@ -7039,7 +7022,6 @@ } }, "description": "POST /pet/multipart", - "name": "uploadPetMultipart", "result_type": { "name": "ApiResponse", "type": "named" diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json index 69c1b92..b5af35c 100644 --- a/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_multi_words.json @@ -3,15 +3,12 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://jsonplaceholder.typicode.com}}" + "url": { + "env": "SERVER_URL", + "value": "https://jsonplaceholder.typicode.com" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "version": "1.0.0" }, "functions": { @@ -37,9 +34,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -56,9 +51,7 @@ "name": "userId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -86,9 +79,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -100,9 +91,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -115,9 +104,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userId": { @@ -129,9 +116,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -160,9 +145,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } diff --git a/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json index 576d18f..0247167 100644 --- a/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json +++ b/ndc-rest-schema/openapi/testdata/prefix2/expected_single_word.json @@ -3,15 +3,12 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://jsonplaceholder.typicode.com}}" + "url": { + "env": "SERVER_URL", + "value": "https://jsonplaceholder.typicode.com" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "version": "1.0.0" }, "functions": { @@ -37,9 +34,7 @@ "name": "id", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -56,9 +51,7 @@ "name": "userId", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -86,9 +79,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -100,9 +91,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -115,9 +104,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "userId": { @@ -129,9 +116,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } } @@ -160,9 +145,7 @@ "rest": { "in": "body", "schema": { - "type": [ - "object" - ] + "type": ["object"] } } } diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json index ac0162c..e3476be 100644 --- a/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_multi_words.json @@ -3,25 +3,26 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://onesignal.com/api/v1}}" + "url": { + "env": "SERVER_URL", + "value": "https://onesignal.com/api/v1" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "app_key": { "type": "http", - "value": "{{APP_KEY_TOKEN}}", + "value": { + "env": "APP_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" }, "user_key": { "type": "http", - "value": "{{USER_KEY_TOKEN}}", + "value": { + "env": "USER_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" } @@ -53,9 +54,7 @@ "name": "app_id", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -72,9 +71,7 @@ "name": "kind", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -91,9 +88,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -110,9 +105,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -149,9 +142,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -163,9 +154,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "recipients": { @@ -177,9 +166,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -196,9 +183,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -211,9 +196,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "failed": { @@ -226,9 +209,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "received": { @@ -241,9 +222,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "successful": { @@ -256,9 +235,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -272,9 +249,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "key": { @@ -287,9 +262,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "relation": { @@ -299,9 +272,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -314,9 +285,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -332,9 +301,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "custom_data": { @@ -346,9 +313,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "data": { @@ -361,9 +326,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "filters": { @@ -378,9 +341,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -392,9 +353,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -406,9 +365,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "send_after": { @@ -420,9 +377,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -435,9 +390,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -453,9 +406,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "notifications": { @@ -470,9 +421,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "offset": { @@ -484,9 +433,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "total_count": { @@ -498,9 +445,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -517,9 +462,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -532,9 +475,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "converted": { @@ -547,9 +488,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -562,9 +501,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "excluded_segments": { @@ -579,13 +516,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -599,9 +532,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "filters": { @@ -616,9 +547,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -630,9 +559,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -644,9 +571,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "include_player_ids": { @@ -661,13 +586,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -683,13 +604,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -705,9 +622,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "platform_delivery_stats": { @@ -720,9 +635,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "queued_at": { @@ -735,9 +648,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -751,9 +662,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "remaining": { @@ -766,9 +675,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "send_after": { @@ -781,9 +688,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -796,9 +701,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "successful": { @@ -811,9 +714,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "target_channel": { @@ -825,9 +726,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "throttle_rate_per_minute": { @@ -840,9 +739,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -855,9 +752,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -866,9 +761,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -877,9 +770,7 @@ "type": "named" }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -896,9 +787,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "chrome_web_push": { @@ -910,9 +799,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "edge_web_push": { @@ -924,9 +811,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "email": { @@ -938,9 +823,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "firefox_web_push": { @@ -952,9 +835,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "ios": { @@ -966,9 +847,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "safari_web_push": { @@ -980,9 +859,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "sms": { @@ -994,9 +871,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1013,9 +888,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1108,10 +981,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], + "one_of": ["sum", "count"], "type": "enum" } }, @@ -1119,11 +989,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], + "one_of": ["push", "email", "sms"], "type": "enum" } }, diff --git a/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json index e5965a4..c79deb3 100644 --- a/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json +++ b/ndc-rest-schema/openapi/testdata/prefix3/expected_single_word.json @@ -3,25 +3,26 @@ "settings": { "servers": [ { - "url": "{{SERVER_URL:-https://onesignal.com/api/v1}}" + "url": { + "env": "SERVER_URL", + "value": "https://onesignal.com/api/v1" + } } ], - "timeout": "{{TIMEOUT}}", - "retry": { - "times": "{{RETRY_TIMES}}", - "delay": "{{RETRY_DELAY}}", - "httpStatus": "{{RETRY_HTTP_STATUS}}" - }, "securitySchemes": { "app_key": { "type": "http", - "value": "{{APP_KEY_TOKEN}}", + "value": { + "env": "APP_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" }, "user_key": { "type": "http", - "value": "{{USER_KEY_TOKEN}}", + "value": { + "env": "USER_KEY_TOKEN" + }, "header": "Authorization", "scheme": "bearer" } @@ -53,9 +54,7 @@ "name": "app_id", "in": "query", "schema": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -72,9 +71,7 @@ "name": "kind", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -91,9 +88,7 @@ "name": "limit", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } }, @@ -110,9 +105,7 @@ "name": "offset", "in": "query", "schema": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -149,9 +142,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -163,9 +154,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "recipients": { @@ -177,9 +166,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -196,9 +183,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -211,9 +196,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "failed": { @@ -226,9 +209,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "received": { @@ -241,9 +222,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "successful": { @@ -256,9 +235,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -272,9 +249,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "key": { @@ -287,9 +262,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "relation": { @@ -299,9 +272,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -314,9 +285,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -332,9 +301,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "custom_data": { @@ -346,9 +313,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "data": { @@ -361,9 +326,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "filters": { @@ -378,9 +341,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -392,9 +353,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -406,9 +365,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "send_after": { @@ -420,9 +377,7 @@ } }, "rest": { - "type": [ - "string" - ], + "type": ["string"], "format": "date-time" } }, @@ -435,9 +390,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -453,9 +406,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "notifications": { @@ -470,9 +421,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "offset": { @@ -484,9 +433,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "total_count": { @@ -498,9 +445,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -517,9 +462,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -532,9 +475,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "converted": { @@ -547,9 +488,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "errored": { @@ -562,9 +501,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "excluded_segments": { @@ -579,13 +516,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -599,9 +532,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "filters": { @@ -616,9 +547,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "headings": { @@ -630,9 +559,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "id": { @@ -644,9 +571,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "include_player_ids": { @@ -661,13 +586,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -683,13 +604,9 @@ } }, "rest": { - "type": [ - "array" - ], + "type": ["array"], "items": { - "type": [ - "string" - ] + "type": ["string"] } } }, @@ -705,9 +622,7 @@ } }, "rest": { - "type": [ - "array" - ] + "type": ["array"] } }, "platform_delivery_stats": { @@ -720,9 +635,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "queued_at": { @@ -735,9 +648,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -751,9 +662,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "remaining": { @@ -766,9 +675,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "send_after": { @@ -781,9 +688,7 @@ } }, "rest": { - "type": [ - "integer" - ], + "type": ["integer"], "format": "int64" } }, @@ -796,9 +701,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "successful": { @@ -811,9 +714,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } }, "target_channel": { @@ -825,9 +726,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "throttle_rate_per_minute": { @@ -840,9 +739,7 @@ } }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -855,9 +752,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "id": { @@ -866,9 +761,7 @@ "type": "named" }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } }, "value": { @@ -877,9 +770,7 @@ "type": "named" }, "rest": { - "type": [ - "integer" - ] + "type": ["integer"] } } } @@ -896,9 +787,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "chrome_web_push": { @@ -910,9 +799,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "edge_web_push": { @@ -924,9 +811,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "email": { @@ -938,9 +823,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "firefox_web_push": { @@ -952,9 +835,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "ios": { @@ -966,9 +847,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "safari_web_push": { @@ -980,9 +859,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } }, "sms": { @@ -994,9 +871,7 @@ } }, "rest": { - "type": [ - "object" - ] + "type": ["object"] } } } @@ -1013,9 +888,7 @@ } }, "rest": { - "type": [ - "string" - ] + "type": ["string"] } } } @@ -1108,10 +981,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "sum", - "count" - ], + "one_of": ["sum", "count"], "type": "enum" } }, @@ -1119,11 +989,7 @@ "aggregate_functions": {}, "comparison_operators": {}, "representation": { - "one_of": [ - "push", - "email", - "sms" - ], + "one_of": ["push", "email", "sms"], "type": "enum" } }, diff --git a/ndc-rest-schema/schema/auth.go b/ndc-rest-schema/schema/auth.go index 7f59c04..cc288ef 100644 --- a/ndc-rest-schema/schema/auth.go +++ b/ndc-rest-schema/schema/auth.go @@ -6,6 +6,7 @@ import ( "fmt" "slices" + "github.com/hasura/ndc-sdk-go/utils" "github.com/invopop/jsonschema" orderedmap "github.com/wk8/go-ordered-map/v2" ) @@ -112,11 +113,13 @@ func ParseAPIKeyLocation(value string) (APIKeyLocation, error) { // [OpenAPI 3]: https://swagger.io/docs/specification/authentication type SecurityScheme struct { Type SecuritySchemeType `json:"type" mapstructure:"type" yaml:"type"` - Value *EnvString `json:"value,omitempty" mapstructure:"value" yaml:"value,omitempty"` + Value *utils.EnvString `json:"value,omitempty" mapstructure:"value" yaml:"value,omitempty"` *APIKeyAuthConfig `yaml:",inline"` *HTTPAuthConfig `yaml:",inline"` *OAuth2Config `yaml:",inline"` *OpenIDConfig `yaml:",inline"` + + value *string } // JSONSchema is used to generate a custom jsonschema @@ -213,7 +216,7 @@ func (j *SecurityScheme) UnmarshalJSON(b []byte) error { } // Validate if the current instance is valid -func (ss SecurityScheme) Validate() error { +func (ss *SecurityScheme) Validate() error { if _, err := ParseSecuritySchemeType(string(ss.Type)); err != nil { return err } @@ -239,9 +242,34 @@ func (ss SecurityScheme) Validate() error { } return ss.OpenIDConfig.Validate() } + + if ss.Value != nil { + value, err := ss.Value.Get() + if err != nil { + return fmt.Errorf("SecurityScheme.Value: %w", err) + } + if value != "" { + ss.value = &value + } + } + return nil } +// GetValue get the authentication credential value +func (ss SecurityScheme) GetValue() string { + if ss.value != nil { + return *ss.value + } + + if ss.Value != nil { + value, _ := ss.Value.Get() + return value + } + + return "" +} + // APIKeyAuthConfig contains configurations for [apiKey authentication] // // [apiKey authentication]: https://swagger.io/docs/specification/authentication/api-keys/ diff --git a/ndc-rest-schema/schema/env.go b/ndc-rest-schema/schema/env.go deleted file mode 100644 index 6b8480f..0000000 --- a/ndc-rest-schema/schema/env.go +++ /dev/null @@ -1,882 +0,0 @@ -package schema - -import ( - "encoding/json" - "fmt" - "os" - "regexp" - "slices" - "strconv" - "strings" - - "github.com/invopop/jsonschema" - "gopkg.in/yaml.v3" -) - -var envVariableRegex = regexp.MustCompile(`{{([A-Z0-9_]+)(:-([^}]*))?}}`) - -// EnvTemplate represents an environment variable template -type EnvTemplate struct { - Name string - DefaultValue *string -} - -// NewEnvTemplate creates an EnvTemplate without default value -func NewEnvTemplate(name string) EnvTemplate { - return EnvTemplate{ - Name: name, - } -} - -// NewEnvTemplateWithDefault creates an EnvTemplate with a default value -func NewEnvTemplateWithDefault(name string, defaultValue string) EnvTemplate { - return EnvTemplate{ - Name: name, - DefaultValue: &defaultValue, - } -} - -// IsEmpty checks if env template is empty -func (et EnvTemplate) IsEmpty() bool { - return et.Name == "" -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et EnvTemplate) Value() (string, bool) { - value, ok := os.LookupEnv(et.Name) - if !ok && et.DefaultValue != nil { - return *et.DefaultValue, true - } - return value, ok -} - -// String implements the Stringer interface -func (et EnvTemplate) String() string { - if et.IsEmpty() { - return "" - } - if et.DefaultValue == nil { - return fmt.Sprintf("{{%s}}", et.Name) - } - return fmt.Sprintf("{{%s:-%s}}", et.Name, *et.DefaultValue) -} - -// MarshalJSON implements json.Marshaler. -func (j EnvTemplate) MarshalJSON() ([]byte, error) { - if j.IsEmpty() { - return json.Marshal(nil) - } - return json.Marshal(j.String()) -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvTemplate) UnmarshalJSON(b []byte) error { - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - value := FindEnvTemplate(rawValue) - if value != nil { - *j = *value - } - return nil -} - -// MarshalYAML implements yaml.Marshaler interface -func (j EnvTemplate) MarshalYAML() (any, error) { - if j.IsEmpty() { - return yaml.Marshal(nil) - } - return j.String(), nil -} - -// UnmarshalYAML implements yaml.Unmarshaler interface -func (j *EnvTemplate) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - value := FindEnvTemplate(node.Value) - if value != nil { - *j = *value - } - return nil -} - -// FindEnvTemplate finds one environment template from string -func FindEnvTemplate(input string) *EnvTemplate { - matches := envVariableRegex.FindStringSubmatch(input) - return parseEnvTemplateFromMatches(matches) -} - -// FindAllEnvTemplates finds all unique environment templates from string -func FindAllEnvTemplates(input string) []EnvTemplate { - matches := envVariableRegex.FindAllStringSubmatch(input, -1) - var results []EnvTemplate - for _, item := range matches { - env := parseEnvTemplateFromMatches(item) - if env == nil { - continue - } - doesExist := false - for _, result := range results { - if env.String() == result.String() { - doesExist = true - break - } - } - if !doesExist { - results = append(results, *env) - } - } - return results -} - -func parseEnvTemplateFromMatches(matches []string) *EnvTemplate { - if len(matches) != 4 { - return nil - } - result := &EnvTemplate{ - Name: matches[1], - } - - if matches[2] != "" { - result.DefaultValue = &matches[3] - } - return result -} - -// ReplaceEnvTemplates replaces env templates in the input string with values -func ReplaceEnvTemplates(input string, envTemplates []EnvTemplate) string { - for _, env := range envTemplates { - value, _ := env.Value() - input = strings.ReplaceAll(input, env.String(), value) - } - return input -} - -// EnvString implements the environment encoding and decoding value -type EnvString struct { - value *string - EnvTemplate -} - -// JSONSchema is used to generate a custom jsonschema -func (j EnvString) JSONSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - Type: "string", - } -} - -// WithValue returns a new EnvString instance with new value -func (j EnvString) WithValue(value string) *EnvString { - j.value = &value - return &j -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et *EnvString) Value() *string { - if et.value != nil { - v := *et.value - return &v - } - - strValue, ok := et.EnvTemplate.Value() - if !ok && strValue == "" { - return nil - } - - if ok { - et.value = &strValue - } - copyVal := strValue - return ©Val -} - -// Equal checks if the current value equals the target -func (et EnvString) Equal(target EnvString) bool { - srcValue := et.Value() - targetValue := target.Value() - - return (srcValue == nil && targetValue == nil) || - (srcValue != nil && targetValue != nil && *srcValue == *targetValue) -} - -// String implements the Stringer interface -func (et EnvString) String() string { - if et.IsEmpty() { - if et.value == nil { - return "" - } - return *et.value - } - return et.EnvTemplate.String() -} - -// MarshalJSON implements json.Marshaler. -func (j EnvString) MarshalJSON() ([]byte, error) { - if j.EnvTemplate.IsEmpty() { - return json.Marshal(j.value) - } - return j.EnvTemplate.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvString) UnmarshalJSON(b []byte) error { - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - return j.unmarshalText(rawValue) -} - -// MarshalYAML implements yaml.Marshaler interface -func (j EnvString) MarshalYAML() (any, error) { - if j.EnvTemplate.IsEmpty() { - return j.value, nil - } - return j.EnvTemplate.MarshalYAML() -} - -// UnmarshalYAML implements yaml.Unmarshaler. -func (j *EnvString) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - return j.unmarshalText(node.Value) -} - -// UnmarshalText decodes the integer slice from string -func (j *EnvString) UnmarshalText(text []byte) error { - return j.unmarshalText(string(text)) -} - -func (j *EnvString) unmarshalText(rawValue string) error { - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - j.Value() - } else { - j.value = &rawValue - } - return nil -} - -// NewEnvStringValue creates an EnvString from value -func NewEnvStringValue(value string) *EnvString { - return &EnvString{ - value: &value, - } -} - -// NewEnvStringTemplate creates an EnvString from template -func NewEnvStringTemplate(template EnvTemplate) *EnvString { - return &EnvString{ - EnvTemplate: template, - } -} - -// EnvInt implements the integer environment encoder and decoder -type EnvInt struct { - value *int64 - EnvTemplate -} - -// NewEnvIntValue creates an EnvInt from value -func NewEnvIntValue(value int64) *EnvInt { - return &EnvInt{ - value: &value, - } -} - -// NewEnvIntTemplate creates an EnvInt from template -func NewEnvIntTemplate(template EnvTemplate) *EnvInt { - return &EnvInt{ - EnvTemplate: template, - } -} - -// JSONSchema is used to generate a custom jsonschema -func (j EnvInt) JSONSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - OneOf: []*jsonschema.Schema{ - {Type: "integer"}, - {Type: "string"}, - }, - } -} - -// WithValue returns a new EnvInt instance with new value -func (j EnvInt) WithValue(value int64) *EnvInt { - j.value = &value - return &j -} - -// Equal checks if the current value equals the target -func (et EnvInt) Equal(target EnvInt) bool { - srcValue, err := et.Value() - if err != nil { - return false - } - targetValue, err := target.Value() - if err != nil { - return false - } - - return (srcValue == nil && targetValue == nil) || - (srcValue != nil && targetValue != nil && *srcValue == *targetValue) -} - -// String implements the Stringer interface -func (et EnvInt) String() string { - if et.IsEmpty() { - if et.value == nil { - return "" - } - return strconv.FormatInt(*et.value, 10) - } - return et.EnvTemplate.String() -} - -// MarshalJSON implements json.Marshaler. -func (j EnvInt) MarshalJSON() ([]byte, error) { - if j.EnvTemplate.IsEmpty() { - return json.Marshal(j.value) - } - return j.EnvTemplate.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvInt) UnmarshalJSON(b []byte) error { - var v int64 - if err := json.Unmarshal(b, &v); err == nil { - j.value = &v - return nil - } - - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - return j.unmarshalText(rawValue) -} - -// MarshalYAML implements yaml.Marshaler interface -func (j EnvInt) MarshalYAML() (any, error) { - if j.EnvTemplate.IsEmpty() { - return j.value, nil - } - return j.EnvTemplate.MarshalYAML() -} - -// UnmarshalYAML implements yaml.Unmarshaler. -func (j *EnvInt) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - return j.unmarshalText(node.Value) -} - -// UnmarshalText decodes the integer slice from string -func (j *EnvInt) UnmarshalText(text []byte) error { - return j.unmarshalText(string(text)) -} - -func (j *EnvInt) unmarshalText(rawValue string) error { - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - if rawValue != "" { - intValue, err := strconv.ParseInt(rawValue, 10, 64) - if err != nil { - return err - } - - j.value = &intValue - } - - return nil -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et *EnvInt) Value() (*int64, error) { - if et.value != nil { - v := *et.value - return &v, nil - } - - strValue, ok := et.EnvTemplate.Value() - if !ok && strValue == "" { - return nil, nil - } - - intValue, err := strconv.ParseInt(strValue, 10, 64) - if err != nil { - return nil, err - } - - if ok { - et.value = &intValue - } - - copyVal := intValue - return ©Val, nil -} - -// EnvInts implements the integer environment encoder and decoder -type EnvInts struct { - value []int64 - EnvTemplate -} - -// NewEnvIntsValue creates EnvInts from value -func NewEnvIntsValue(value []int64) *EnvInts { - return &EnvInts{ - value: value, - } -} - -// NewEnvIntsTemplate creates EnvInts from template -func NewEnvIntsTemplate(template EnvTemplate) *EnvInts { - return &EnvInts{ - EnvTemplate: template, - } -} - -// JSONSchema is used to generate a custom jsonschema -func (j EnvInts) JSONSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - OneOf: []*jsonschema.Schema{ - {Type: "string"}, - {Type: "array", Items: &jsonschema.Schema{Type: "integer"}}, - }, - } -} - -// WithValue returns a new EnvInts instance with new value -func (j EnvInts) WithValue(value []int64) *EnvInts { - j.value = value - return &j -} - -// Equal checks if the current value equals the target -func (et EnvInts) Equal(target EnvInts) bool { - srcValue, err := et.Value() - if err != nil { - return false - } - targetValue, err := target.Value() - if err != nil { - return false - } - - return slices.Equal(srcValue, targetValue) -} - -// String implements the Stringer interface -func (et EnvInts) String() string { - if et.IsEmpty() { - return fmt.Sprintf("%v", et.value) - } - return et.EnvTemplate.String() -} - -// MarshalJSON implements json.Marshaler. -func (j EnvInts) MarshalJSON() ([]byte, error) { - if j.EnvTemplate.IsEmpty() { - return json.Marshal(j.value) - } - return j.EnvTemplate.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvInts) UnmarshalJSON(b []byte) error { - var v []int64 - if err := json.Unmarshal(b, &v); err == nil { - j.value = v - return nil - } - - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - return j.unmarshalText(rawValue) -} - -// MarshalYAML implements yaml.Marshaler. -func (j EnvInts) MarshalYAML() (any, error) { - if j.EnvTemplate.IsEmpty() { - return j.value, nil - } - return j.EnvTemplate.MarshalYAML() -} - -// UnmarshalYAML implements yaml.Unmarshaler. -func (j *EnvInts) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - - return j.unmarshalText(node.Value) -} - -// UnmarshalText decodes the integer slice from string -func (j *EnvInts) UnmarshalText(text []byte) error { - return j.unmarshalText(string(text)) -} - -func (j *EnvInts) unmarshalText(rawValue string) error { - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - - if rawValue != "" { - intValues, err := parseIntsFromString(rawValue) - if err != nil { - return err - } - - j.value = intValues - } - - return nil -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et *EnvInts) Value() ([]int64, error) { - if et.value != nil { - return et.value, nil - } - - strValue, ok := et.EnvTemplate.Value() - if !ok && strValue == "" { - return nil, nil - } - - intValues, err := parseIntsFromString(strValue) - if err != nil { - return nil, err - } - if ok { - et.value = intValues - } - - return intValues, nil -} - -func parseIntsFromString(input string) ([]int64, error) { - var intValues []int64 - for _, str := range strings.Split(input, ",") { - intValue, err := strconv.ParseInt(strings.TrimSpace(str), 10, 64) - if err != nil { - return nil, err - } - intValues = append(intValues, intValue) - } - - return intValues, nil -} - -// EnvBoolean implements the boolean environment encoder and decoder -type EnvBoolean struct { - value *bool - EnvTemplate -} - -// NewEnvBooleanValue creates an EnvBoolean from value -func NewEnvBooleanValue(value bool) *EnvBoolean { - return &EnvBoolean{ - value: &value, - } -} - -// NewEnvBooleanTemplate creates an EnvBoolean from template -func NewEnvBooleanTemplate(template EnvTemplate) *EnvBoolean { - return &EnvBoolean{ - EnvTemplate: template, - } -} - -// JSONSchema is used to generate a custom jsonschema -func (j EnvBoolean) JSONSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - OneOf: []*jsonschema.Schema{ - {Type: "boolean"}, - {Type: "string"}, - }, - } -} - -// WithValue returns a new EnvBoolean instance with new value -func (j EnvBoolean) WithValue(value bool) *EnvBoolean { - j.value = &value - return &j -} - -// Equal checks if the current value equals the target -func (et EnvBoolean) Equal(target EnvBoolean) bool { - srcValue, err := et.Value() - if err != nil { - return false - } - targetValue, err := target.Value() - if err != nil { - return false - } - - return (srcValue == nil && targetValue == nil) || - (srcValue != nil && targetValue != nil && *srcValue == *targetValue) -} - -// String implements the Stringer interface -func (et EnvBoolean) String() string { - if et.IsEmpty() { - if et.value == nil { - return "" - } - return strconv.FormatBool(*et.value) - } - return et.EnvTemplate.String() -} - -// MarshalJSON implements json.Marshaler. -func (j EnvBoolean) MarshalJSON() ([]byte, error) { - if j.EnvTemplate.IsEmpty() { - return json.Marshal(j.value) - } - return j.EnvTemplate.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvBoolean) UnmarshalJSON(b []byte) error { - var v bool - if err := json.Unmarshal(b, &v); err == nil { - j.value = &v - return nil - } - - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - return j.unmarshalText(rawValue) -} - -// UnmarshalText decodes boolean from string -func (j *EnvBoolean) UnmarshalText(text []byte) error { - return j.unmarshalText(string(text)) -} - -func (j *EnvBoolean) unmarshalText(rawValue string) error { - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - if rawValue != "" { - boolValue, err := strconv.ParseBool(rawValue) - if err != nil { - return err - } - - j.value = &boolValue - } - - return nil -} - -// MarshalYAML implements yaml.Marshaler interface -func (j EnvBoolean) MarshalYAML() (any, error) { - if j.EnvTemplate.IsEmpty() { - return j.value, nil - } - return j.EnvTemplate.MarshalYAML() -} - -// UnmarshalYAML implements yaml.Unmarshaler. -func (j *EnvBoolean) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - return j.unmarshalText(node.Value) -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et *EnvBoolean) Value() (*bool, error) { - if et.value != nil { - v := *et.value - return &v, nil - } - - strValue, ok := et.EnvTemplate.Value() - if !ok && strValue == "" { - return nil, nil - } - - boolValue, err := strconv.ParseBool(strValue) - if err != nil { - return nil, err - } - - if ok { - et.value = &boolValue - } - - copyVal := boolValue - return ©Val, nil -} - -// EnvStrings implements the string slice environment encoder and decoder -type EnvStrings struct { - value []string - EnvTemplate -} - -// NewEnvStringsValue creates EnvStrings from value -func NewEnvStringsValue(value []string) *EnvStrings { - return &EnvStrings{ - value: value, - } -} - -// NewEnvStringsTemplate creates EnvStrings from template -func NewEnvStringsTemplate(template EnvTemplate) *EnvStrings { - return &EnvStrings{ - EnvTemplate: template, - } -} - -// JSONSchema is used to generate a custom jsonschema -func (j EnvStrings) JSONSchema() *jsonschema.Schema { - return &jsonschema.Schema{ - OneOf: []*jsonschema.Schema{ - {Type: "string"}, - {Type: "array", Items: &jsonschema.Schema{Type: "string"}}, - }, - } -} - -// WithValue returns a new EnvStrings instance with new value -func (j EnvStrings) WithValue(value []string) *EnvStrings { - j.value = value - return &j -} - -// Equal checks if the current value equals the target -func (et EnvStrings) Equal(target EnvStrings) bool { - srcValue, err := et.Value() - if err != nil { - return false - } - targetValue, err := target.Value() - if err != nil { - return false - } - - return slices.Equal(srcValue, targetValue) -} - -// String implements the Stringer interface -func (et EnvStrings) String() string { - if et.IsEmpty() { - return fmt.Sprintf("%v", et.value) - } - return et.EnvTemplate.String() -} - -// MarshalJSON implements json.Marshaler. -func (j EnvStrings) MarshalJSON() ([]byte, error) { - if j.EnvTemplate.IsEmpty() { - return json.Marshal(j.value) - } - return j.EnvTemplate.MarshalJSON() -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *EnvStrings) UnmarshalJSON(b []byte) error { - var v []string - if err := json.Unmarshal(b, &v); err == nil { - j.value = v - return nil - } - - var rawValue string - if err := json.Unmarshal(b, &rawValue); err != nil { - return err - } - - return j.unmarshalText(rawValue) -} - -// MarshalYAML implements yaml.Marshaler. -func (j EnvStrings) MarshalYAML() (any, error) { - if j.EnvTemplate.IsEmpty() { - return j.value, nil - } - return j.EnvTemplate.MarshalYAML() -} - -// UnmarshalYAML implements yaml.Unmarshaler. -func (j *EnvStrings) UnmarshalYAML(node *yaml.Node) error { - if node.Value == "" { - return nil - } - - return j.unmarshalText(node.Value) -} - -// UnmarshalText decodes the integer slice from string -func (j *EnvStrings) UnmarshalText(text []byte) error { - return j.unmarshalText(string(text)) -} - -func (j *EnvStrings) unmarshalText(rawValue string) error { - value := FindEnvTemplate(rawValue) - if value != nil { - j.EnvTemplate = *value - _, err := j.Value() - return err - } - - if rawValue != "" { - values := strings.Split(rawValue, ",") - j.value = make([]string, len(values)) - for i, v := range values { - j.value[i] = strings.TrimSpace(v) - } - } else { - j.value = []string{} - } - - return nil -} - -// Value returns the value which is retrieved from system or the default value if exist -func (et *EnvStrings) Value() ([]string, error) { - if et.value != nil { - return et.value, nil - } - - strValue, ok := et.EnvTemplate.Value() - if !ok && strValue == "" { - return nil, nil - } - - err := et.unmarshalText(strValue) - if err != nil { - return nil, err - } - return et.value, nil -} diff --git a/ndc-rest-schema/schema/env_test.go b/ndc-rest-schema/schema/env_test.go deleted file mode 100644 index 8873d0a..0000000 --- a/ndc-rest-schema/schema/env_test.go +++ /dev/null @@ -1,402 +0,0 @@ -package schema - -import ( - "encoding/json" - "fmt" - "strings" - "testing" - - "github.com/hasura/ndc-sdk-go/utils" - "gopkg.in/yaml.v3" - "gotest.tools/v3/assert" -) - -func TestEnvTemplate(t *testing.T) { - testCases := []struct { - input string - expected string - templateStr string - templates []EnvTemplate - }{ - {}, - { - input: "http://localhost:8080", - expected: "http://localhost:8080", - }, - { - input: "{{SERVER_URL}}", - templates: []EnvTemplate{ - NewEnvTemplate("SERVER_URL"), - }, - templateStr: "{{SERVER_URL}}", - expected: "", - }, - { - input: "{{SERVER_URL:-http://localhost:8080}}", - templates: []EnvTemplate{ - NewEnvTemplateWithDefault("SERVER_URL", "http://localhost:8080"), - }, - templateStr: "{{SERVER_URL:-http://localhost:8080}}", - expected: "http://localhost:8080", - }, - { - input: "{{SERVER_URL:-}}", - templates: []EnvTemplate{ - { - Name: "SERVER_URL", - DefaultValue: utils.ToPtr(""), - }, - }, - templateStr: "{{SERVER_URL:-}}", - expected: "", - }, - { - input: "{{SERVER_URL:-http://localhost:8080}},{{SERVER_URL:-http://localhost:8080}},{{SERVER_URL}}", - templates: []EnvTemplate{ - { - Name: "SERVER_URL", - DefaultValue: utils.ToPtr("http://localhost:8080"), - }, - { - Name: "SERVER_URL", - }, - }, - templateStr: "{{SERVER_URL:-http://localhost:8080}},{{SERVER_URL}}", - expected: "http://localhost:8080,http://localhost:8080,", - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - tmpl := FindEnvTemplate(tc.input) - if len(tc.templates) == 0 { - if tmpl != nil { - t.Errorf("failed to find env template, expected nil, got %s", tmpl) - } - } else { - assert.DeepEqual(t, tc.templates[0].String(), tmpl.String()) - - var jTemplate EnvTemplate - if err := json.Unmarshal([]byte(fmt.Sprintf(`"%s"`, tc.input)), &jTemplate); err != nil { - t.Errorf("failed to unmarshal template from json: %s", err) - t.FailNow() - } - assert.DeepEqual(t, jTemplate, *tmpl) - bs, err := json.Marshal(jTemplate) - if err != nil { - t.Errorf("failed to marshal template from json: %s", err) - t.FailNow() - } - assert.DeepEqual(t, tmpl.String(), strings.Trim(string(bs), `"`)) - - if err := yaml.Unmarshal([]byte(fmt.Sprintf(`"%s"`, tc.input)), &jTemplate); err != nil { - t.Errorf("failed to unmarshal template from yaml: %s", err) - t.FailNow() - } - assert.DeepEqual(t, jTemplate, *tmpl) - bs, err = yaml.Marshal(jTemplate) - if err != nil { - t.Errorf("failed to marshal template from yaml: %s", err) - t.FailNow() - } - assert.DeepEqual(t, tmpl.String(), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - } - - templates := FindAllEnvTemplates(tc.input) - assert.DeepEqual(t, tc.templates, templates) - templateStrings := []string{} - for i, item := range templates { - assert.DeepEqual(t, tc.templates[i].String(), item.String()) - templateStrings = append(templateStrings, item.String()) - } - assert.DeepEqual(t, tc.expected, ReplaceEnvTemplates(tc.input, templates)) - if len(templateStrings) > 0 { - assert.DeepEqual(t, tc.templateStr, strings.Join(templateStrings, ",")) - } - }) - } -} - -func TestEnvString(t *testing.T) { - testCases := []struct { - input string - expected EnvString - }{ - { - input: `"{{FOO:-bar}}"`, - expected: *NewEnvStringTemplate(EnvTemplate{ - Name: "FOO", - DefaultValue: utils.ToPtr("bar"), - }), - }, - { - input: `"baz"`, - expected: *NewEnvStringValue("baz"), - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - var result EnvString - if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) - bs, err := yaml.Marshal(result) - if err != nil { - t.Fatal(t, err) - } - assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - result.JSONSchema() - }) - } -} - -func TestEnvInt(t *testing.T) { - testCases := []struct { - input string - expected EnvInt - }{ - { - input: `400`, - expected: EnvInt{value: utils.ToPtr[int64](400)}, - }, - { - input: `"400"`, - expected: *EnvInt{}.WithValue(400), - }, - { - input: `"{{FOO:-401}}"`, - expected: EnvInt{ - value: utils.ToPtr(int64(401)), - EnvTemplate: EnvTemplate{ - Name: "FOO", - DefaultValue: utils.ToPtr("401"), - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - var result EnvInt - if err := json.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - - if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) - bs, err := yaml.Marshal(result) - if err != nil { - t.Fatal(t, err) - } - assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - result.JSONSchema() - }) - } -} - -func TestEnvInts(t *testing.T) { - testCases := []struct { - input string - expected EnvInts - expectedYaml string - }{ - { - input: `[400, 401, 403]`, - expected: EnvInts{value: []int64{400, 401, 403}}, - expectedYaml: `- 400 -- 401 -- 403`, - }, - { - input: `"400, 401, 403"`, - expected: *NewEnvIntsValue(nil).WithValue([]int64{400, 401, 403}), - expectedYaml: `- 400 -- 401 -- 403`, - }, - { - input: `"{{FOO:-400, 401, 403}}"`, - expected: EnvInts{ - value: []int64{400, 401, 403}, - EnvTemplate: EnvTemplate{ - Name: "FOO", - DefaultValue: utils.ToPtr("400, 401, 403"), - }, - }, - expectedYaml: `{{FOO:-400, 401, 403}}`, - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - var result EnvInts - if err := json.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - - if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.String(), result.String()) - assert.DeepEqual(t, tc.expected.value, result.value) - bs, err := yaml.Marshal(result) - if err != nil { - t.Fatal(t, err) - } - assert.DeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - result.JSONSchema() - }) - } -} - -func TestEnvBoolean(t *testing.T) { - t.Setenv("TEST_BOOL", "false") - testCases := []struct { - input string - expected EnvBoolean - }{ - { - input: `false`, - expected: *NewEnvBooleanValue(false), - }, - { - input: `"true"`, - expected: *NewEnvBooleanValue(true), - }, - { - input: `"{{FOO:-true}}"`, - expected: EnvBoolean{ - value: utils.ToPtr(true), - EnvTemplate: EnvTemplate{ - Name: "FOO", - DefaultValue: utils.ToPtr("true"), - }, - }, - }, - { - input: fmt.Sprintf(`"%s"`, NewEnvBooleanTemplate(EnvTemplate{ - Name: "TEST_BOOL", - }).String()), - expected: EnvBoolean{ - value: utils.ToPtr(false), - EnvTemplate: EnvTemplate{ - Name: "TEST_BOOL", - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - var result EnvBoolean - if err := json.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - - if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - assert.DeepEqual(t, strings.Trim(tc.input, "\""), tc.expected.String()) - bs, err := yaml.Marshal(result) - if err != nil { - t.Fatal(t, err) - } - assert.DeepEqual(t, strings.Trim(tc.input, `"`), strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - result.JSONSchema() - if err = (&EnvBoolean{}).UnmarshalText([]byte(strings.Trim(tc.input, `"`))); err != nil { - t.Error(t, err) - t.FailNow() - } - }) - } -} - -func TestEnvStrings(t *testing.T) { - t.Setenv("TEST_STRINGS", "a,b,c") - testCases := []struct { - input string - expected EnvStrings - expectedYaml string - }{ - { - input: `["foo", "bar"]`, - expected: *NewEnvStringsValue([]string{"foo", "bar"}), - expectedYaml: `- foo -- bar`, - }, - { - input: `"foo, baz"`, - expected: *NewEnvStringsValue(nil).WithValue([]string{"foo", "baz"}), - expectedYaml: `- foo -- baz`, - }, - { - input: fmt.Sprintf(`"%s"`, NewEnvStringsTemplate(NewEnvTemplate("TEST_STRINGS")).String()), - expected: EnvStrings{ - value: []string{"a", "b", "c"}, - EnvTemplate: EnvTemplate{ - Name: "TEST_STRINGS", - }, - }, - expectedYaml: `{{TEST_STRINGS}}`, - }, - { - input: `"{{FOO:-foo, bar}}"`, - expected: EnvStrings{ - value: []string{"foo", "bar"}, - EnvTemplate: EnvTemplate{ - Name: "FOO", - DefaultValue: utils.ToPtr("foo, bar"), - }, - }, - expectedYaml: `{{FOO:-foo, bar}}`, - }, - } - - for _, tc := range testCases { - t.Run(tc.input, func(t *testing.T) { - var result EnvStrings - if err := json.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.EnvTemplate, result.EnvTemplate) - assert.DeepEqual(t, tc.expected.value, result.value) - - if err := yaml.Unmarshal([]byte(tc.input), &result); err != nil { - t.Error(t, err) - t.FailNow() - } - assert.DeepEqual(t, tc.expected.String(), result.String()) - assert.DeepEqual(t, tc.expected.value, result.value) - bs, err := yaml.Marshal(result) - if err != nil { - t.Fatal(t, err) - } - assert.DeepEqual(t, tc.expectedYaml, strings.TrimSpace(strings.ReplaceAll(string(bs), "'", ""))) - result.JSONSchema() - }) - } -} diff --git a/ndc-rest-schema/schema/schema.go b/ndc-rest-schema/schema/schema.go index 801509a..89fc2ac 100644 --- a/ndc-rest-schema/schema/schema.go +++ b/ndc-rest-schema/schema/schema.go @@ -94,34 +94,38 @@ type Response struct { ContentType string `json:"contentType" mapstructure:"contentType" yaml:"contentType"` } +// RuntimeSettings contain runtime settings for a server +type RuntimeSettings struct { // configure the request timeout in seconds, default 30s + Timeout uint `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` + Retry RetryPolicy `json:"retry,omitempty" mapstructure:"retry" yaml:"retry,omitempty"` +} + // Request represents the HTTP request information of the webhook type Request struct { - URL string `json:"url,omitempty" mapstructure:"url" yaml:"url,omitempty"` - Method string `json:"method,omitempty" jsonschema:"enum=get,enum=post,enum=put,enum=patch,enum=delete" mapstructure:"method" yaml:"method,omitempty"` - Type RequestType `json:"type,omitempty" mapstructure:"type" yaml:"type,omitempty"` - Headers map[string]EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` - Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` - // configure the request timeout in seconds, default 30s - Timeout uint `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` - Servers []ServerConfig `json:"servers,omitempty" mapstructure:"servers" yaml:"servers,omitempty"` - RequestBody *RequestBody `json:"requestBody,omitempty" mapstructure:"requestBody" yaml:"requestBody,omitempty"` - Response Response `json:"response" mapstructure:"response" yaml:"response"` - Retry *RetryPolicy `json:"retry,omitempty" mapstructure:"retry" yaml:"retry,omitempty"` + URL string `json:"url,omitempty" mapstructure:"url" yaml:"url,omitempty"` + Method string `json:"method,omitempty" jsonschema:"enum=get,enum=post,enum=put,enum=patch,enum=delete" mapstructure:"method" yaml:"method,omitempty"` + Type RequestType `json:"type,omitempty" mapstructure:"type" yaml:"type,omitempty"` + Headers map[string]utils.EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` + Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` + Servers []ServerConfig `json:"servers,omitempty" mapstructure:"servers" yaml:"servers,omitempty"` + RequestBody *RequestBody `json:"requestBody,omitempty" mapstructure:"requestBody" yaml:"requestBody,omitempty"` + Response Response `json:"response" mapstructure:"response" yaml:"response"` + + *RuntimeSettings `yaml:",inline"` } // Clone copies this instance to a new one func (r Request) Clone() *Request { return &Request{ - URL: r.URL, - Method: r.Method, - Type: r.Type, - Headers: r.Headers, - Timeout: r.Timeout, - Retry: r.Retry, - Security: r.Security, - Servers: r.Servers, - RequestBody: r.RequestBody, - Response: r.Response, + URL: r.URL, + Method: r.Method, + Type: r.Type, + Headers: r.Headers, + Security: r.Security, + Servers: r.Servers, + RequestBody: r.RequestBody, + Response: r.Response, + RuntimeSettings: r.RuntimeSettings, } } diff --git a/ndc-rest-schema/schema/setting.go b/ndc-rest-schema/schema/setting.go index c84bb6a..845e601 100644 --- a/ndc-rest-schema/schema/setting.go +++ b/ndc-rest-schema/schema/setting.go @@ -6,18 +6,19 @@ import ( "fmt" "net/url" "strings" + + "github.com/hasura/ndc-sdk-go/utils" ) // NDCRestSettings represent global settings of the REST API, including base URL, headers, etc... type NDCRestSettings struct { - Servers []ServerConfig `json:"servers" mapstructure:"servers" yaml:"servers"` - Headers map[string]EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` - // configure the request timeout in seconds, default 30s - Timeout *EnvInt `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` - Retry *RetryPolicySetting `json:"retry,omitempty" mapstructure:"retry" yaml:"retry,omitempty"` - SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" mapstructure:"securitySchemes" yaml:"securitySchemes,omitempty"` - Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` - Version string `json:"version,omitempty" mapstructure:"version" yaml:"version,omitempty"` + Servers []ServerConfig `json:"servers" mapstructure:"servers" yaml:"servers"` + Headers map[string]utils.EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` + SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" mapstructure:"securitySchemes" yaml:"securitySchemes,omitempty"` + Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` + Version string `json:"version,omitempty" mapstructure:"version" yaml:"version,omitempty"` + + headers map[string]string } // UnmarshalJSON implements json.Unmarshaler. @@ -39,7 +40,7 @@ func (j *NDCRestSettings) UnmarshalJSON(b []byte) error { } // Validate if the current instance is valid -func (rs NDCRestSettings) Validate() error { +func (rs *NDCRestSettings) Validate() error { for _, server := range rs.Servers { if err := server.Validate(); err != nil { return err @@ -52,82 +53,101 @@ func (rs NDCRestSettings) Validate() error { } } - if rs.Retry != nil { - if err := rs.Retry.Validate(); err != nil { - return fmt.Errorf("retry: %w", err) - } + headers, err := getHeadersFromEnv(rs.Headers) + if err != nil { + return err } + rs.headers = headers + return nil } -// RetryPolicySetting represents retry policy settings -type RetryPolicySetting struct { - // Number of retry times - Times EnvInt `json:"times,omitempty" mapstructure:"times" yaml:"times,omitempty"` - // Delay retry delay in milliseconds - Delay EnvInt `json:"delay,omitempty" mapstructure:"delay" yaml:"delay,omitempty"` - // HTTPStatus retries if the remote service returns one of these http status - HTTPStatus EnvInts `json:"httpStatus,omitempty" mapstructure:"httpStatus" yaml:"httpStatus,omitempty"` +// Validate if the current instance is valid +func (rs NDCRestSettings) GetHeaders() map[string]string { + if rs.headers != nil { + return rs.headers + } + + return getHeadersFromEnvUnsafe(rs.Headers) } -// Validate if the current instance is valid -func (rs RetryPolicySetting) Validate() error { - times, err := rs.Times.Value() - if err != nil { +// ServerConfig contains server configurations +type ServerConfig struct { + URL utils.EnvString `json:"url" mapstructure:"url" yaml:"url"` + ID string `json:"id,omitempty" mapstructure:"id" yaml:"id,omitempty"` + Headers map[string]utils.EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` + SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" mapstructure:"securitySchemes" yaml:"securitySchemes,omitempty"` + Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` + TLS *TLSConfig `json:"tls,omitempty" mapstructure:"tls" yaml:"tls,omitempty"` + + // cached values that are loaded from environment variables + url *string + headers map[string]string +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ServerConfig) UnmarshalJSON(b []byte) error { + type Plain ServerConfig + + var raw Plain + if err := json.Unmarshal(b, &raw); err != nil { return err } - if times != nil && *times < 0 { - return errors.New("retry policy times must be positive") + + result := ServerConfig(raw) + + if err := result.Validate(); err != nil { + return err } + *j = result - delay, err := rs.Times.Value() + return nil +} + +// Validate if the current instance is valid +func (ss *ServerConfig) Validate() error { + urlValue, err := ss.URL.Get() if err != nil { - return err + return fmt.Errorf("server url: %w", err) + } + + if urlValue == "" { + return errors.New("url is required for server") } - if delay != nil && *delay < 0 { - return errors.New("retry delay must be larger than 0") + + if _, err := parseHttpURL(urlValue); err != nil { + return fmt.Errorf("server url: %w", err) } - httpStatus, err := rs.HTTPStatus.Value() + ss.url = &urlValue + + headers, err := getHeadersFromEnv(ss.Headers) if err != nil { return err } - for _, status := range httpStatus { - if status < 400 || status >= 600 { - return errors.New("retry http status must be in between 400 and 599") - } - } + ss.headers = headers return nil } -// ServerConfig contains server configurations -type ServerConfig struct { - URL EnvString `json:"url" mapstructure:"url" yaml:"url"` - ID string `json:"id,omitempty" mapstructure:"id" yaml:"id,omitempty"` - Headers map[string]EnvString `json:"headers,omitempty" mapstructure:"headers" yaml:"headers,omitempty"` - // configure the request timeout in seconds, default 30s - Timeout *EnvInt `json:"timeout,omitempty" mapstructure:"timeout" yaml:"timeout,omitempty"` - Retry *RetryPolicySetting `json:"retry,omitempty" mapstructure:"retry" yaml:"retry,omitempty"` - SecuritySchemes map[string]SecurityScheme `json:"securitySchemes,omitempty" mapstructure:"securitySchemes" yaml:"securitySchemes,omitempty"` - Security AuthSecurities `json:"security,omitempty" mapstructure:"security" yaml:"security,omitempty"` - TLS *TLSConfig `json:"tls,omitempty" mapstructure:"tls" yaml:"tls,omitempty"` +// Validate if the current instance is valid +func (ss ServerConfig) GetURL() string { + if ss.url != nil { + return *ss.url + } + + result, _ := ss.URL.Get() + + return result } // Validate if the current instance is valid -func (ss ServerConfig) Validate() error { - urlValue := ss.URL.Value() - if urlValue == nil || *urlValue == "" { - if ss.URL.EnvTemplate.IsEmpty() { - return errors.New("url is required for server") - } - return nil +func (ss ServerConfig) GetHeaders() map[string]string { + if ss.headers != nil { + return ss.headers } - if _, err := parseHttpURL(*urlValue); err != nil { - return fmt.Errorf("server url: %w", err) - } - return nil + return getHeadersFromEnvUnsafe(ss.Headers) } // parseHttpURL parses and validate if the URL has HTTP scheme @@ -149,35 +169,62 @@ func parseRelativeOrHttpURL(input string) (*url.URL, error) { // TLSConfig represents the transport layer security (LTS) configuration for the mutualTLS authentication type TLSConfig struct { // Path to the TLS cert to use for TLS required connections. - CertFile *EnvString `json:"certFile,omitempty" mapstructure:"certFile" yaml:"certFile,omitempty"` + CertFile *utils.EnvString `json:"certFile,omitempty" mapstructure:"certFile" yaml:"certFile,omitempty"` // Alternative to cert_file. Provide the certificate contents as a string instead of a filepath. - CertPem *EnvString `json:"certPem,omitempty" mapstructure:"certPem" yaml:"certPem,omitempty"` + CertPem *utils.EnvString `json:"certPem,omitempty" mapstructure:"certPem" yaml:"certPem,omitempty"` // Path to the TLS key to use for TLS required connections. - KeyFile *EnvString `json:"keyFile,omitempty" mapstructure:"keyFile" yaml:"keyFile,omitempty"` + KeyFile *utils.EnvString `json:"keyFile,omitempty" mapstructure:"keyFile" yaml:"keyFile,omitempty"` // Alternative to key_file. Provide the key contents as a string instead of a filepath. - KeyPem *EnvString `json:"keyPem,omitempty" mapstructure:"keyPem" yaml:"keyPem,omitempty"` + KeyPem *utils.EnvString `json:"keyPem,omitempty" mapstructure:"keyPem" yaml:"keyPem,omitempty"` // Path to the CA cert. For a client this verifies the server certificate. For a server this verifies client certificates. // If empty uses system root CA. - CAFile *EnvString `json:"caFile,omitempty" mapstructure:"caFile" yaml:"caFile,omitempty"` + CAFile *utils.EnvString `json:"caFile,omitempty" mapstructure:"caFile" yaml:"caFile,omitempty"` // Alternative to ca_file. Provide the CA cert contents as a string instead of a filepath. - CAPem *EnvString `json:"caPem,omitempty" mapstructure:"caPem" yaml:"caPem,omitempty"` + CAPem *utils.EnvString `json:"caPem,omitempty" mapstructure:"caPem" yaml:"caPem,omitempty"` // Additionally you can configure TLS to be enabled but skip verifying the server's certificate chain. - InsecureSkipVerify *EnvBoolean `json:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify" yaml:"insecureSkipVerify,omitempty"` + InsecureSkipVerify *utils.EnvBool `json:"insecureSkipVerify,omitempty" mapstructure:"insecureSkipVerify" yaml:"insecureSkipVerify,omitempty"` // Whether to load the system certificate authorities pool alongside the certificate authority. - IncludeSystemCACertsPool *EnvBoolean `json:"includeSystemCACertsPool,omitempty" mapstructure:"includeSystemCACertsPool" yaml:"includeSystemCACertsPool,omitempty"` + IncludeSystemCACertsPool *utils.EnvBool `json:"includeSystemCACertsPool,omitempty" mapstructure:"includeSystemCACertsPool" yaml:"includeSystemCACertsPool,omitempty"` // Minimum acceptable TLS version. - MinVersion *EnvString `json:"minVersion,omitempty" mapstructure:"minVersion" yaml:"minVersion,omitempty"` + MinVersion *utils.EnvString `json:"minVersion,omitempty" mapstructure:"minVersion" yaml:"minVersion,omitempty"` // Maximum acceptable TLS version. - MaxVersion *EnvString `json:"maxVersion,omitempty" mapstructure:"maxVersion" yaml:"maxVersion,omitempty"` + MaxVersion *utils.EnvString `json:"maxVersion,omitempty" mapstructure:"maxVersion" yaml:"maxVersion,omitempty"` // Explicit cipher suites can be set. If left blank, a safe default list is used. // See https://go.dev/src/crypto/tls/cipher_suites.go for a list of supported cipher suites. - CipherSuites *EnvStrings `json:"cipherSuites,omitempty" mapstructure:"cipherSuites" yaml:"cipherSuites,omitempty"` + CipherSuites []string `json:"cipherSuites,omitempty" mapstructure:"cipherSuites" yaml:"cipherSuites,omitempty"` // Specifies the duration after which the certificate will be reloaded. If not set, it will never be reloaded. // The interval unit is minute - ReloadInterval *EnvInt `json:"reloadInterval,omitempty" mapstructure:"reloadInterval" yaml:"reloadInterval,omitempty"` + ReloadInterval *utils.EnvInt `json:"reloadInterval,omitempty" mapstructure:"reloadInterval" yaml:"reloadInterval,omitempty"` } // Validate if the current instance is valid func (ss TLSConfig) Validate() error { return nil } + +func getHeadersFromEnv(headers map[string]utils.EnvString) (map[string]string, error) { + results := make(map[string]string) + for key, header := range headers { + value, err := header.Get() + if err != nil { + return nil, fmt.Errorf("headers[%s]: %w", key, err) + } + if value != "" { + results[key] = value + } + } + + return results, nil +} + +func getHeadersFromEnvUnsafe(headers map[string]utils.EnvString) map[string]string { + results := make(map[string]string) + for key, header := range headers { + value, _ := header.Get() + if value != "" { + results[key] = value + } + } + + return results +} diff --git a/ndc-rest-schema/schema/setting_test.go b/ndc-rest-schema/schema/setting_test.go index 545a903..4cc1306 100644 --- a/ndc-rest-schema/schema/setting_test.go +++ b/ndc-rest-schema/schema/setting_test.go @@ -2,8 +2,11 @@ package schema import ( "encoding/json" + "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/hasura/ndc-sdk-go/utils" "gotest.tools/v3/assert" ) @@ -18,16 +21,23 @@ func TestNDCRestSettings(t *testing.T) { input: `{ "servers": [ { - "url": "{{PET_STORE_SERVER_URL:-https://petstore3.swagger.io/api/v3}}" + "url": { + "env": "PET_STORE_SERVER_URL", + "value": "https://petstore3.swagger.io/api/v3" + } }, { - "url": "https://petstore3.swagger.io/api/v3.1" + "url": { + "value": "https://petstore3.swagger.io/api/v3.1" + } } ], "securitySchemes": { "api_key": { "type": "apiKey", - "value": "{{PET_STORE_API_KEY}}", + "value": { + "env": "PET_STORE_API_KEY" + }, "in": "header", "name": "api_key" }, @@ -44,12 +54,6 @@ func TestNDCRestSettings(t *testing.T) { } } }, - "timeout": "{{PET_STORE_TIMEOUT}}", - "retry": { - "times": "{{PET_STORE_RETRY_TIMES}}", - "delay": 1000, - "httpStatus": "{{PET_STORE_RETRY_HTTP_STATUS}}" - }, "security": [ {}, { @@ -61,16 +65,16 @@ func TestNDCRestSettings(t *testing.T) { expected: NDCRestSettings{ Servers: []ServerConfig{ { - URL: *NewEnvStringTemplate(NewEnvTemplateWithDefault("PET_STORE_SERVER_URL", "https://petstore3.swagger.io/api/v3")), + URL: utils.NewEnvString("PET_STORE_SERVER_URL", "https://petstore3.swagger.io/api/v3"), }, { - URL: *EnvString{}.WithValue("https://petstore3.swagger.io/api/v3.1"), + URL: utils.NewEnvStringValue("https://petstore3.swagger.io/api/v3.1"), }, }, SecuritySchemes: map[string]SecurityScheme{ "api_key": { Type: APIKeyScheme, - Value: NewEnvStringTemplate(NewEnvTemplate("PET_STORE_API_KEY")), + Value: utils.ToPtr(utils.NewEnvStringVariable("PET_STORE_API_KEY")), APIKeyAuthConfig: &APIKeyAuthConfig{ In: APIKeyInHeader, Name: "api_key", @@ -91,12 +95,6 @@ func TestNDCRestSettings(t *testing.T) { }, }, }, - Timeout: NewEnvIntTemplate(NewEnvTemplate("PET_STORE_TIMEOUT")), - Retry: &RetryPolicySetting{ - Times: *NewEnvIntTemplate(NewEnvTemplate("PET_STORE_RETRY_TIMES")), - Delay: *NewEnvIntValue(1000), - HTTPStatus: *NewEnvIntsTemplate(NewEnvTemplate("PET_STORE_RETRY_HTTP_STATUS")), - }, Security: AuthSecurities{ AuthSecurity{}, NewAuthSecurity("petstore_auth", []string{"write:pets", "read:pets"}), @@ -114,15 +112,12 @@ func TestNDCRestSettings(t *testing.T) { t.FailNow() } for i, s := range tc.expected.Servers { - assert.DeepEqual(t, s.URL.String(), result.Servers[i].URL.String()) + assert.DeepEqual(t, s.URL.Variable, result.Servers[i].URL.Variable) + assert.DeepEqual(t, s.URL.Value, result.Servers[i].URL.Value) } assert.DeepEqual(t, tc.expected.Headers, result.Headers) - assert.DeepEqual(t, tc.expected.Retry.Delay.String(), result.Retry.Delay.String()) - assert.DeepEqual(t, tc.expected.Retry.Times.String(), result.Retry.Times.String()) - assert.DeepEqual(t, tc.expected.Retry.HTTPStatus.String(), result.Retry.HTTPStatus.String()) assert.DeepEqual(t, tc.expected.Security, result.Security) - assert.DeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes) - assert.DeepEqual(t, tc.expected.Timeout, result.Timeout) + assert.DeepEqual(t, tc.expected.SecuritySchemes, result.SecuritySchemes, cmp.Exporter(func(t reflect.Type) bool { return true })) assert.DeepEqual(t, tc.expected.Version, result.Version) _, err := json.Marshal(tc.expected)