diff --git a/pkg/plugin/implementation/schemav2validator/README.md b/pkg/plugin/implementation/schemav2validator/README.md index f72ac9b..b76ad99 100644 --- a/pkg/plugin/implementation/schemav2validator/README.md +++ b/pkg/plugin/implementation/schemav2validator/README.md @@ -17,7 +17,8 @@ Validates Beckn protocol requests against OpenAPI 3.1 specifications using kin-o schemaValidator: id: schemav2validator config: - url: https://example.com/openapi-spec.yaml + type: url + location: https://example.com/openapi-spec.yaml cacheTTL: "3600" ``` @@ -25,9 +26,12 @@ schemaValidator: | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| -| `url` | string | Yes | - | URL or file path to OpenAPI 3.1 specification | +| `type` | string | Yes | - | Type of spec source: "url" or "file" ("dir" reserved for future) | +| `location` | string | Yes | - | URL or file path to OpenAPI 3.1 spec | | `cacheTTL` | string | No | "3600" | Cache TTL in seconds before reloading spec | + + ## How It Works 1. **Load Spec**: Loads OpenAPI spec from configured URL at startup @@ -94,26 +98,30 @@ The loader will automatically fetch and resolve the external reference. ## Example Usage -### Local File - -```yaml -schemaValidator: - id: schemav2validator - config: - url: ./validation-scripts/l2-config/mobility_1.1.0_openapi_3.1.yaml - cacheTTL: "3600" -``` - ### Remote URL ```yaml schemaValidator: id: schemav2validator config: - url: https://raw.githubusercontent.com/beckn/protocol-specifications/master/api/beckn-2.0.0.yaml + type: url + location: https://raw.githubusercontent.com/beckn/protocol-specifications/master/api/beckn-2.0.0.yaml cacheTTL: "7200" ``` +### Local File + +```yaml +schemaValidator: + id: schemav2validator + config: + type: file + location: ./validation-scripts/l2-config/mobility_1.1.0_openapi_3.1.yaml + cacheTTL: "3600" +``` + + + ## Dependencies - `github.com/getkin/kin-openapi` - OpenAPI 3 parser and validator diff --git a/pkg/plugin/implementation/schemav2validator/cmd/plugin.go b/pkg/plugin/implementation/schemav2validator/cmd/plugin.go index 78b6226..9d147fe 100644 --- a/pkg/plugin/implementation/schemav2validator/cmd/plugin.go +++ b/pkg/plugin/implementation/schemav2validator/cmd/plugin.go @@ -18,21 +18,26 @@ func (vp schemav2ValidatorProvider) New(ctx context.Context, config map[string]s return nil, nil, errors.New("context cannot be nil") } - url, ok := config["url"] - if !ok || url == "" { - return nil, nil, errors.New("url not configured") - } + typeVal, hasType := config["type"] + locVal, hasLoc := config["location"] - cacheTTL := 3600 - if ttlStr, ok := config["cacheTTL"]; ok { - if ttl, err := strconv.Atoi(ttlStr); err == nil && ttl > 0 { - cacheTTL = ttl - } + if !hasType || typeVal == "" { + return nil, nil, errors.New("type not configured") + } + if !hasLoc || locVal == "" { + return nil, nil, errors.New("location not configured") } cfg := &schemav2validator.Config{ - URL: url, - CacheTTL: cacheTTL, + Type: typeVal, + Location: locVal, + CacheTTL: 3600, + } + + if ttlStr, ok := config["cacheTTL"]; ok { + if ttl, err := strconv.Atoi(ttlStr); err == nil && ttl > 0 { + cfg.CacheTTL = ttl + } } return schemav2validator.New(ctx, cfg) diff --git a/pkg/plugin/implementation/schemav2validator/cmd/plugin_test.go b/pkg/plugin/implementation/schemav2validator/cmd/plugin_test.go index af9c10d..42798dc 100644 --- a/pkg/plugin/implementation/schemav2validator/cmd/plugin_test.go +++ b/pkg/plugin/implementation/schemav2validator/cmd/plugin_test.go @@ -48,39 +48,64 @@ func TestProvider_New(t *testing.T) { errMsg: "context cannot be nil", }, { - name: "missing url", + name: "missing type", ctx: context.Background(), - config: map[string]string{}, + config: map[string]string{"location": server.URL}, wantErr: true, - errMsg: "url not configured", + errMsg: "type not configured", }, { - name: "empty url", + name: "missing location", ctx: context.Background(), - config: map[string]string{"url": ""}, + config: map[string]string{"type": "url"}, wantErr: true, - errMsg: "url not configured", + errMsg: "location not configured", + }, + { + name: "empty type", + ctx: context.Background(), + config: map[string]string{"type": "", "location": server.URL}, + wantErr: true, + errMsg: "type not configured", + }, + { + name: "empty location", + ctx: context.Background(), + config: map[string]string{"type": "url", "location": ""}, + wantErr: true, + errMsg: "location not configured", }, { name: "valid config with default TTL", ctx: context.Background(), - config: map[string]string{"url": server.URL}, + config: map[string]string{"type": "url", "location": server.URL}, wantErr: false, }, { name: "valid config with custom TTL", ctx: context.Background(), config: map[string]string{ - "url": server.URL, + "type": "url", + "location": server.URL, "cacheTTL": "7200", }, wantErr: false, }, + { + name: "valid file type", + ctx: context.Background(), + config: map[string]string{ + "type": "file", + "location": "/tmp/spec.yaml", + }, + wantErr: true, // file doesn't exist + }, { name: "invalid TTL falls back to default", ctx: context.Background(), config: map[string]string{ - "url": server.URL, + "type": "url", + "location": server.URL, "cacheTTL": "invalid", }, wantErr: false, @@ -89,7 +114,8 @@ func TestProvider_New(t *testing.T) { name: "negative TTL falls back to default", ctx: context.Background(), config: map[string]string{ - "url": server.URL, + "type": "url", + "location": server.URL, "cacheTTL": "-100", }, wantErr: false, @@ -98,7 +124,8 @@ func TestProvider_New(t *testing.T) { name: "zero TTL falls back to default", ctx: context.Background(), config: map[string]string{ - "url": server.URL, + "type": "url", + "location": server.URL, "cacheTTL": "0", }, wantErr: false, diff --git a/pkg/plugin/implementation/schemav2validator/schemav2validator.go b/pkg/plugin/implementation/schemav2validator/schemav2validator.go index e9b43db..bebbcca 100644 --- a/pkg/plugin/implementation/schemav2validator/schemav2validator.go +++ b/pkg/plugin/implementation/schemav2validator/schemav2validator.go @@ -37,7 +37,8 @@ type cachedSpec struct { // Config struct for Schemav2Validator. type Config struct { - URL string + Type string // "url", "file", or "dir" + Location string // URL, file path, or directory path CacheTTL int } @@ -46,8 +47,14 @@ func New(ctx context.Context, config *Config) (*schemav2Validator, func() error, if config == nil { return nil, nil, fmt.Errorf("config cannot be nil") } - if config.URL == "" { - return nil, nil, fmt.Errorf("config URL cannot be empty") + if config.Type == "" { + return nil, nil, fmt.Errorf("config type cannot be empty") + } + if config.Location == "" { + return nil, nil, fmt.Errorf("config location cannot be empty") + } + if config.Type != "url" && config.Type != "file" && config.Type != "dir" { + return nil, nil, fmt.Errorf("config type must be 'url', 'file', or 'dir'") } if config.CacheTTL == 0 { @@ -156,20 +163,23 @@ func (v *schemav2Validator) loadSpec(ctx context.Context) error { var doc *openapi3.T var err error - urlOrPath := v.config.URL - - if strings.HasPrefix(urlOrPath, "http://") || strings.HasPrefix(urlOrPath, "https://") { - u, parseErr := url.Parse(urlOrPath) + switch v.config.Type { + case "url": + u, parseErr := url.Parse(v.config.Location) if parseErr != nil { return fmt.Errorf("failed to parse URL: %v", parseErr) } doc, err = loader.LoadFromURI(u) - } else { - doc, err = loader.LoadFromFile(urlOrPath) + case "file": + doc, err = loader.LoadFromFile(v.config.Location) + case "dir": + return fmt.Errorf("directory loading not yet implemented") + default: + return fmt.Errorf("unsupported type: %s", v.config.Type) } if err != nil { - log.Errorf(ctx, err, "Invalid URL or unreachable: %s", urlOrPath) + log.Errorf(ctx, err, "Failed to load from %s: %s", v.config.Type, v.config.Location) return fmt.Errorf("failed to load OpenAPI document: %v", err) } @@ -187,7 +197,7 @@ func (v *schemav2Validator) loadSpec(ctx context.Context) error { } v.specMutex.Unlock() - log.Debugf(ctx, "Loaded OpenAPI spec from %s", urlOrPath) + log.Debugf(ctx, "Loaded OpenAPI spec from %s: %s", v.config.Type, v.config.Location) return nil } @@ -220,7 +230,7 @@ func (v *schemav2Validator) reloadExpiredSpec(ctx context.Context) { if err := v.loadSpec(ctx); err != nil { log.Errorf(ctx, err, "Failed to reload spec") } else { - log.Debugf(ctx, "Reloaded spec from %s", v.config.URL) + log.Debugf(ctx, "Reloaded spec from %s: %s", v.config.Type, v.config.Location) } } } diff --git a/pkg/plugin/implementation/schemav2validator/schemav2validator_test.go b/pkg/plugin/implementation/schemav2validator/schemav2validator_test.go index 6f5a71f..8852fff 100644 --- a/pkg/plugin/implementation/schemav2validator/schemav2validator_test.go +++ b/pkg/plugin/implementation/schemav2validator/schemav2validator_test.go @@ -63,8 +63,10 @@ func TestNew(t *testing.T) { wantErr bool }{ {"nil config", nil, true}, - {"empty URL", &Config{URL: ""}, true}, - {"invalid URL", &Config{URL: "http://invalid-domain-12345.com/spec.yaml"}, true}, + {"empty type", &Config{Type: "", Location: "http://example.com"}, true}, + {"empty location", &Config{Type: "url", Location: ""}, true}, + {"invalid type", &Config{Type: "invalid", Location: "http://example.com"}, true}, + {"invalid URL", &Config{Type: "url", Location: "http://invalid-domain-12345.com/spec.yaml"}, true}, } for _, tt := range tests { @@ -83,7 +85,7 @@ func TestValidate_ActionExtraction(t *testing.T) { })) defer server.Close() - validator, _, err := New(context.Background(), &Config{URL: server.URL, CacheTTL: 3600}) + validator, _, err := New(context.Background(), &Config{Type: "url", Location: server.URL, CacheTTL: 3600}) if err != nil { t.Fatalf("Failed to create validator: %v", err) } @@ -156,7 +158,7 @@ func TestValidate_NestedValidation(t *testing.T) { })) defer server.Close() - validator, _, err := New(context.Background(), &Config{URL: server.URL, CacheTTL: 3600}) + validator, _, err := New(context.Background(), &Config{Type: "url", Location: server.URL, CacheTTL: 3600}) if err != nil { t.Fatalf("Failed to create validator: %v", err) } @@ -200,7 +202,7 @@ func TestLoadSpec_LocalFile(t *testing.T) { } tmpFile.Close() - validator, _, err := New(context.Background(), &Config{URL: tmpFile.Name(), CacheTTL: 3600}) + validator, _, err := New(context.Background(), &Config{Type: "file", Location: tmpFile.Name(), CacheTTL: 3600}) if err != nil { t.Fatalf("Failed to load local spec: %v", err) } @@ -219,7 +221,7 @@ func TestCacheTTL_DefaultValue(t *testing.T) { })) defer server.Close() - validator, _, err := New(context.Background(), &Config{URL: server.URL}) + validator, _, err := New(context.Background(), &Config{Type: "url", Location: server.URL}) if err != nil { t.Fatalf("Failed to create validator: %v", err) } @@ -235,7 +237,7 @@ func TestValidate_EdgeCases(t *testing.T) { })) defer server.Close() - validator, _, err := New(context.Background(), &Config{URL: server.URL, CacheTTL: 3600}) + validator, _, err := New(context.Background(), &Config{Type: "url", Location: server.URL, CacheTTL: 3600}) if err != nil { t.Fatalf("Failed to create validator: %v", err) }