Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
.PHONY:gen format lint
.PHONY:gen format lint gen-schema-registry-client

gen:
gen: gen-mocks gen-schema-registry-client

gen-mocks:
mockery

gen-schema-registry-client:
@echo "Fetching OpenAPI schema from Redpanda..."
@mkdir -p gen/schema-registry
@curl -sSL https://docs.redpanda.com/api/doc/schema-registry.yaml -o gen/schema-registry/openapi2.yaml
@echo "Converting OpenAPI 2.0 to OpenAPI 3.0..."
@curl -sSL -X POST "https://converter.swagger.io/api/convert" \
-H "Content-Type: application/yaml" \
-H "Accept: application/yaml" \
--data-binary @gen/schema-registry/openapi2.yaml \
-o gen/schema-registry/openapi.yaml
@echo "Generating Go HTTP client..."
@cd gen/schema-registry && go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen -config oapi-codegen.yaml openapi.yaml
@echo "Formatting generated code..."
@go fmt ./gen/schema-registry/client.go
@echo "Cleaning up schema files..."
@rm -f gen/schema-registry/openapi2.yaml gen/schema-registry/openapi.yaml
@echo "Schema registry client generated successfully in gen/schema-registry/client.go"

lint:
golangci-lint run

Expand Down
43 changes: 22 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ with various Charge Point Management Systems or Charge Point implementations.

## Features

- [x] Parse Raw OCPP JSON messages
- [x] Support for OCPP 1.6, 2.0.1 and 2.1
- [x] Request and Response payload validation
- [x] Validate messages from a file
- [x] Validate Raw OCPP JSON messages against multiple OCPP schemas
- [x] Generate human-readable reports
- [x] Support for remote schema registries using Kafka-compatible Schemas Registry APIs
- [x] Bring your own OCPP schemas for vendor-specific extensions

## Compatibility matrix

| OCPP specification | Supported |
|----------------------------:|:---------:|
| OCPP 1.6 | ✅ |
| OCPP 1.6 Security Extension | ✅ |
| OCPP 2.0.1 | ✅ |
| OCPP 2.1 | ✅ |

### Roadmap

- [ ] Support for signed messages
- [ ] Compatibility checks
- [ ] Remote schema registry for vendors and models

## Installation

Expand All @@ -36,25 +43,19 @@ chargeflow validate '[2, "123456", "BootNotification", {"chargePointVendor": "Te
For more options, you can run:

```bash
chargeflow validate

Validate the OCPP message(s) against the registered OCPP schema(s).

Usage:
chargeflow validate [flags]
chargeflow [flags]
chargeflow [command]

Examples:
chargeflow --version 1.6 validate '[2, "123456", "BootNotification", {"chargePointVendor": "TestVendor", "chargePointModel": "TestModel"}]'
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
schema Manage schemas on a remote schema registry
validate Validate the OCPP message(s) against the registered OCPP schemas

Flags:
-f, --file string Path to a file containing the OCPP message to validate. If this flag is set, the message will be read from the file instead of the command line argument.
-h, --help help for validate
-o, --output string Path to write validation report. Supports .json, .csv and .txt extensions.
-r, --response-type string Response type to validate against (e.g. 'BootNotificationResponse'). Currently needed if you want to validate a single response message.
-a, --schemas string Path to additional OCPP schemas folder

Global Flags:
-d, --debug Enable debug mode
-h, --help help for chargeflow
-v, --version string OCPP version to use (1.6, 2.0.1 or 2.1) (default "1.6")
```

Expand All @@ -68,8 +69,8 @@ version!
Additionally, you can specify a custom path to vendor-specific OCPP schemas using the `--schemas` flag.

> [!TIP]
> You can also validate multiple messages (both request and responses) from a file using the `-f` flag.

> You can also validate multiple OCPP messages from a file using the `-f` flag.
> The file should be a newline-separated list of JSON strings.

## License

Expand Down
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import (
"context"

"go.uber.org/zap"

Check failure on line 6 in cmd/root.go

View workflow job for this annotation

GitHub Actions / Lint

import 'go.uber.org/zap' is not allowed from list 'Main' (depguard)

"github.com/pkg/errors"

Check failure on line 8 in cmd/root.go

View workflow job for this annotation

GitHub Actions / Lint

import 'github.com/pkg/errors' is not allowed from list 'Main' (depguard)
"github.com/spf13/cobra"

Check failure on line 9 in cmd/root.go

View workflow job for this annotation

GitHub Actions / Lint

import 'github.com/spf13/cobra' is not allowed from list 'Main' (depguard)
"github.com/spf13/viper"

Check failure on line 10 in cmd/root.go

View workflow job for this annotation

GitHub Actions / Lint

import 'github.com/spf13/viper' is not allowed from list 'Main' (depguard)

"github.com/ChargePi/chargeflow/pkg/ocpp"

Check failure on line 12 in cmd/root.go

View workflow job for this annotation

GitHub Actions / Lint

import 'github.com/ChargePi/chargeflow/pkg/ocpp' is not allowed from list 'Main' (depguard)
)

const (
Expand All @@ -32,6 +32,7 @@

func init() {
rootCmd.AddCommand(validate)
rootCmd.AddCommand(schemaCmd)
}

// setDefaults sets the default values for the configuration.
Expand Down
133 changes: 133 additions & 0 deletions cmd/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package cmd

import (
"time"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"go.uber.org/zap"

"github.com/ChargePi/chargeflow/pkg/schema_registry/registries/remote_registry"
)

type schemaConfig struct {
URL string
AuthType string
Username string
Password string
BearerToken string
APIKey string
APIKeyHeader string
CustomHeader string
CustomValue string
Timeout time.Duration
}

var schemaCfg schemaConfig

var schemaCmd = &cobra.Command{
Use: "schema",
Short: "Manage schemas on a remote schema registry",
Long: `Commands for registering and removing OCPP schemas on a remote schema registry.`,
}

// loadSchemaConfig loads common schema registry configuration from viper.
func loadSchemaConfig() schemaConfig {
cfg := schemaConfig{
URL: viper.GetString("schema.url"),
AuthType: viper.GetString("schema.auth-type"),
Username: viper.GetString("schema.username"),
Password: viper.GetString("schema.password"),
BearerToken: viper.GetString("schema.bearer-token"),
APIKey: viper.GetString("schema.api-key"),
APIKeyHeader: viper.GetString("schema.api-key-header"),
CustomHeader: viper.GetString("schema.custom-header"),
CustomValue: viper.GetString("schema.custom-value"),
Timeout: viper.GetDuration("schema.timeout"),
}

if cfg.APIKey != "" && cfg.APIKeyHeader == "" {
cfg.APIKeyHeader = "X-API-Key"
}

return cfg
}

// validateSchemaConfig validates the common schema registry flags.
// Called from each subcommand's PreRunE to avoid shadowing rootCmd's PersistentPreRun.
func validateSchemaConfig() error {
cfg := loadSchemaConfig()

if cfg.URL == "" {
return errors.New("remote registry URL is required (use --url flag)")
}

switch cfg.AuthType {
case "basic":
if cfg.Username == "" || cfg.Password == "" {
return errors.New("both --username and --password are required for basic authentication")
}
case "bearer":
if cfg.BearerToken == "" {
return errors.New("Bearer token is required for API-key authentication")
}
case "api-key":
if cfg.APIKey == "" {
return errors.New("API-key is required for API-key authentication")
}
}

return nil
}

// buildRemoteRegistry creates a remote schema registry from the common schema config.
func buildRemoteRegistry(logger *zap.Logger) (*remote_registry.SchemaRegistry, error) {
cfg := loadSchemaConfig()

opts := []remote_registry.Options{
remote_registry.WithTimeout(cfg.Timeout),
}

switch cfg.AuthType {
case "basic":
opts = append(opts, remote_registry.WithBasicAuth(cfg.Username, cfg.Password))
case "bearer":
opts = append(opts, remote_registry.WithBearerToken(cfg.BearerToken))
case "api-key":
opts = append(opts, remote_registry.WithAPIKey(cfg.APIKey, cfg.APIKeyHeader))
}

if cfg.CustomHeader != "" && cfg.CustomValue != "" {
opts = append(opts, remote_registry.WithCustomHeader(cfg.CustomHeader, cfg.CustomValue))
}

return remote_registry.NewRemoteSchemaRegistry(cfg.URL, logger, opts...)
}

func init() {
schemaCmd.PersistentFlags().StringVar(&schemaCfg.URL, "url", "", "Remote schema registry URL (required)")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.AuthType, "auth-type", "", "Authentication type (basic, bearer, api-key or none)")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.Username, "username", "", "Username for basic authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.Password, "password", "", "Password for basic authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.BearerToken, "bearer-token", "", "Bearer token for authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.APIKey, "api-key", "", "API key for authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.APIKeyHeader, "api-key-header", "X-API-Key", "Header name for API key authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.CustomHeader, "custom-header", "", "Custom header name for authentication")
schemaCmd.PersistentFlags().StringVar(&schemaCfg.CustomValue, "custom-value", "", "Custom header value for authentication")
schemaCmd.PersistentFlags().DurationVar(&schemaCfg.Timeout, "timeout", 5*time.Second, "Request timeout duration")

_ = viper.BindPFlag("schema.url", schemaCmd.PersistentFlags().Lookup("url"))
_ = viper.BindPFlag("schema.auth-type", schemaCmd.PersistentFlags().Lookup("auth-type"))
_ = viper.BindPFlag("schema.username", schemaCmd.PersistentFlags().Lookup("username"))
_ = viper.BindPFlag("schema.password", schemaCmd.PersistentFlags().Lookup("password"))
_ = viper.BindPFlag("schema.bearer-token", schemaCmd.PersistentFlags().Lookup("bearer-token"))
_ = viper.BindPFlag("schema.api-key", schemaCmd.PersistentFlags().Lookup("api-key"))
_ = viper.BindPFlag("schema.api-key-header", schemaCmd.PersistentFlags().Lookup("api-key-header"))
_ = viper.BindPFlag("schema.custom-header", schemaCmd.PersistentFlags().Lookup("custom-header"))
_ = viper.BindPFlag("schema.custom-value", schemaCmd.PersistentFlags().Lookup("custom-value"))
_ = viper.BindPFlag("schema.timeout", schemaCmd.PersistentFlags().Lookup("timeout"))

schemaCmd.AddCommand(register)
schemaCmd.AddCommand(removeCmd)
}
Loading
Loading