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
9 changes: 9 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@
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 (
serviceVersion = "0.1.0-beta"
)

var (
vendor = ""
model = ""
)

var rootCmd = &cobra.Command{
Use: "chargeflow",
Short: "",
Expand Down Expand Up @@ -45,9 +50,13 @@
// Add flag for OCPP version
rootCmd.PersistentFlags().StringP("version", "v", ocpp.V16.String(), "OCPP version to use (1.6, 2.0.1 or 2.1)")
rootCmd.PersistentFlags().BoolP("debug", "d", false, "Enable debug mode")
rootCmd.PersistentFlags().StringVarP(&vendor, "vendor", "V", "", "Charging-station vendor for vendor/model-specific schema selection")
rootCmd.PersistentFlags().StringVarP(&model, "model", "m", "", "Charging-station model for vendor/model-specific schema selection")

_ = viper.BindPFlag("ocpp.version", rootCmd.PersistentFlags().Lookup("version"))
_ = viper.BindPFlag("debug", rootCmd.PersistentFlags().Lookup("debug"))
_ = viper.BindPFlag("vendor", rootCmd.PersistentFlags().Lookup("vendor"))
_ = viper.BindPFlag("model", rootCmd.PersistentFlags().Lookup("model"))
}

func Execute(ctx context.Context) error {
Expand Down
52 changes: 38 additions & 14 deletions cmd/schema_register.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ type registerConfig struct {
SchemaFile string
SchemaDir string
Action string
Vendor string
Model string
}

var registerCfg registerConfig
Expand Down Expand Up @@ -63,7 +65,11 @@ The schema file names should match the OCPP action names (e.g., "BootNotificatio
logger := zap.L()

cfg := loadRegisterConfig()
version := ocpp.Version(viper.GetString("ocpp.version"))
octx := ocpp.OcppContext{
Version: ocpp.Version(viper.GetString("ocpp.version")),
Vendor: cfg.Vendor,
Model: cfg.Model,
}

remoteRegistry, err := buildRemoteRegistry(logger)
if err != nil {
Expand All @@ -74,9 +80,9 @@ The schema file names should match the OCPP action names (e.g., "BootNotificatio

switch {
case cfg.SchemaFile != "":
return registerSingleSchema(ctx, logger, remoteRegistry, version, cfg.SchemaFile, cfg.Action)
return registerSingleSchema(ctx, logger, remoteRegistry, octx, cfg.SchemaFile, cfg.Action)
default:
return registerSchemasFromDir(ctx, logger, remoteRegistry, version, cfg.SchemaDir)
return registerSchemasFromDir(ctx, logger, remoteRegistry, octx, cfg.SchemaDir)
}
},
}
Expand All @@ -85,39 +91,51 @@ func registerSingleSchema(
ctx context.Context,
logger *zap.Logger,
registry schema_registry.SchemaRegistry,
version ocpp.Version,
octx ocpp.OcppContext,
filePath, action string,
) error {
logger.Info("Registering schema",
logger = logger.With(
zap.String("file", filePath),
zap.String("action", action),
zap.String("version", version.String()))
zap.String("vendor", octx.Vendor),
zap.String("model", octx.Model),
zap.String("version", octx.Version.String()),
)

logger.Info("Registering schema")

schemaData, err := os.ReadFile(filePath)
if err != nil {
return errors.Wrapf(err, "failed to read schema file: %s", filePath)
}

if err := registry.RegisterSchema(ctx, version, action, schemaData); err != nil {
if err := registry.RegisterSchema(ctx, schema_registry.CreateSchemaRequest{
OcppContext: octx,
Action: action,
Schema: schemaData,
}); err != nil {
return errors.Wrapf(err, "failed to register schema for action %s", action)
}

logger.Info("Successfully registered schema",
zap.String("action", action),
zap.String("version", version.String()))
logger.Info("Successfully registered schema")
return nil
}

func registerSchemasFromDir(
ctx context.Context,
logger *zap.Logger,
registry schema_registry.SchemaRegistry,
version ocpp.Version,
octx ocpp.OcppContext,
dir string,
) error {
logger.Info("Registering schemas from directory",
logger = logger.With(
zap.String("directory", dir),
zap.String("version", version.String()))
zap.String("vendor", octx.Vendor),
zap.String("model", octx.Model),
zap.String("version", octx.Version.String()),
)

logger.Info("Registering schemas from directory")

entries, err := os.ReadDir(dir)
if err != nil {
Expand Down Expand Up @@ -150,7 +168,11 @@ func registerSchemasFromDir(
continue
}

if err := registry.RegisterSchema(ctx, version, action, schemaData); err != nil {
if err := registry.RegisterSchema(ctx, schema_registry.CreateSchemaRequest{
OcppContext: octx,
Action: action,
Schema: schemaData,
}); err != nil {
logger.Error("Failed to register schema",
zap.String("file", schemaPath),
zap.String("action", action),
Expand Down Expand Up @@ -181,6 +203,8 @@ func loadRegisterConfig() registerConfig {
SchemaFile: viper.GetString("schema.register.file"),
SchemaDir: viper.GetString("schema.register.dir"),
Action: viper.GetString("schema.register.action"),
Vendor: viper.GetString("vendor"),
Model: viper.GetString("model"),
}
}

Expand Down
48 changes: 32 additions & 16 deletions cmd/schema_remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ You can remove a single schema by action name, derive the action from a file nam
logger := zap.L()

cfg := loadRemoveConfig()
version := ocpp.Version(viper.GetString("ocpp.version"))
octx := ocpp.OcppContext{
Version: ocpp.Version(viper.GetString("ocpp.version")),
Vendor: vendor,
Model: model,
}

remoteRegistry, err := buildRemoteRegistry(logger)
if err != nil {
Expand All @@ -82,12 +86,12 @@ You can remove a single schema by action name, derive the action from a file nam

switch {
case cfg.Action != "":
return removeSingleSchema(ctx, logger, remoteRegistry, version, cfg.Action)
return removeSingleSchema(ctx, logger, remoteRegistry, octx, cfg.Action)
case cfg.SchemaFile != "":
action, _ := strings.CutSuffix(filepath.Base(cfg.SchemaFile), ".json")
return removeSingleSchema(ctx, logger, remoteRegistry, version, action)
return removeSingleSchema(ctx, logger, remoteRegistry, octx, action)
default:
return removeSchemasFromDir(ctx, logger, remoteRegistry, version, cfg.SchemaDir)
return removeSchemasFromDir(ctx, logger, remoteRegistry, octx, cfg.SchemaDir)
}
},
}
Expand All @@ -96,33 +100,42 @@ func removeSingleSchema(
ctx context.Context,
logger *zap.Logger,
registry schema_registry.SchemaRegistry,
version ocpp.Version,
octx ocpp.OcppContext,
action string,
) error {
logger.Info("Removing schema",
logger = logger.With(
zap.String("action", action),
zap.String("version", version.String()))

if err := registry.DeleteSchema(ctx, version, action); err != nil {
zap.String("vendor", octx.Vendor),
zap.String("model", octx.Model),
zap.String("version", octx.Version.String()),
)
logger.Info("Removing schema")

if err := registry.DeleteSchema(ctx, schema_registry.DeleteSchemaRequest{
OcppContext: octx,
Action: action,
}); err != nil {
return errors.Wrapf(err, "failed to remove schema for action %s", action)
}

logger.Info("Successfully removed schema",
zap.String("action", action),
zap.String("version", version.String()))
logger.Info("Successfully removed schema")
return nil
}

func removeSchemasFromDir(
ctx context.Context,
logger *zap.Logger,
registry schema_registry.SchemaRegistry,
version ocpp.Version,
octx ocpp.OcppContext,
dir string,
) error {
logger.Info("Removing schemas matching directory",
logger = logger.With(
zap.String("directory", dir),
zap.String("version", version.String()))
zap.String("vendor", octx.Vendor),
zap.String("model", octx.Model),
zap.String("version", octx.Version.String()),
)
logger.Info("Removing schemas matching directory")

entries, err := os.ReadDir(dir)
if err != nil {
Expand All @@ -145,7 +158,10 @@ func removeSchemasFromDir(

action, _ := strings.CutSuffix(fileName, ".json")

if err := registry.DeleteSchema(ctx, version, action); err != nil {
if err := registry.DeleteSchema(ctx, schema_registry.DeleteSchemaRequest{
OcppContext: octx,
Action: action,
}); err != nil {
logger.Error("Failed to remove schema",
zap.String("action", action),
zap.String("file", fileName),
Expand Down
55 changes: 24 additions & 31 deletions cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,11 @@ func registerSchemas(
// Also could determine the OCPP version from the schema ID.

action, _ := strings.CutSuffix(name, ".json")
err = registry.RegisterSchema(ctx, version, action, schemaData)
err = registry.RegisterSchema(ctx, schema_registry.CreateSchemaRequest{
OcppContext: ocpp.OcppContext{Version: version},
Action: action,
Schema: schemaData,
})
if err != nil {
return errors.Wrapf(err, "unable to register OCPP schema: %s", name)
}
Expand Down Expand Up @@ -154,7 +158,7 @@ var validate = &cobra.Command{
if overwrite {
ocppVersion := viper.GetString("ocpp.version")
version := ocpp.Version(ocppVersion)
err = registerSchemasFromDir(ctx, logger, registry, version, additionalOcppSchemasFolder)
err = registerSchemasFromDir(ctx, logger, registry, ocpp.OcppContext{Version: version, Vendor: vendor, Model: model}, additionalOcppSchemasFolder)
if err != nil {
return err
}
Expand All @@ -165,7 +169,7 @@ var validate = &cobra.Command{
RunE: func(cmd *cobra.Command, args []string) error {
ocppVersion := viper.GetString("ocpp.version")
file := viper.GetString("file")
version := ocpp.Version(ocppVersion)
output := viper.GetString("output")

logger := zap.L()
logger = logger.WithOptions(zap.WithCaller(false), zap.AddStacktrace(zap.FatalLevel))
Expand All @@ -177,49 +181,38 @@ var validate = &cobra.Command{
message = args[0]
}

output := viper.GetString("output")
validationOpts := []validation.Option{}
if file == "" && message == "" {
return errors.New("no message provided to validate, please provide a message as a command line argument or use the --file flag to read from a file")
}

// Validate provided output extension if present
if output != "" {
ext := strings.ToLower(filepath.Ext(output))
if !supportedOutputFormats[ext] {
return errors.Errorf("unsupported output format '%s', supported: .json, .csv, .txt", ext)
}

validationOpts = append(validationOpts, validation.WithOutput(output))
}

switch {
case file == "" && message == "":
return errors.New("no message provided to validate, please provide a message as a command line argument or use the --file flag to read from a file")
case message != "":
// The message is expected to be a JSON string in the format:
// '[2, "uniqueId", "BootNotification", {"chargePointVendor": "TestVendor", "chargePointModel": "TestModel"}]'
if output == "" {
return service.ValidateMessage(message, version)
}

// Validate and write report
r, err := service.ValidateMessageWithReport(message, version)
if err != nil {
return err
}

return validation.WriteReport(output, r)
req := validation.Request{
OcppContext: ocpp.OcppContext{
Version: ocpp.Version(ocppVersion),
Vendor: vendor,
Model: model,
},
Output: output,
}

case file != "":
// Use the options pattern to write output using registered strategies
// Read the messages from the file
return service.ValidateFile(file, version, validationOpts...)
if message != "" {
req.Messages = []string{message}
} else {
req.File = file
}

return nil
_, err := service.Validate(req)
return err
},
}

func init() {
// Add flags for additional OCPP schemas folder
validate.Flags().StringVarP(&additionalOcppSchemasFolder, "schemas", "a", "", "Path to additional OCPP schemas folder")
validate.Flags().StringP("response-type", "r", "", "Response type to validate against (e.g. 'BootNotificationResponse'). Currently needed if you want to validate a single response message. ")
validate.Flags().StringP("file", "f", "", "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.")
Expand Down
3 changes: 1 addition & 2 deletions cmd/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ func Test_registerAdditionalSchemas(t *testing.T) {

// Call the function to register additional schemas
ctx := context.Background()
version := ocpp.Version(test.defaultOcppVersion)
err = registerSchemasFromDir(ctx, logger, registry, version, tempDir)
err = registerSchemasFromDir(ctx, logger, registry, ocpp.OcppContext{Version: ocpp.Version(test.defaultOcppVersion)}, tempDir)
if test.expected != nil {
// The shared function returns aggregated errors, so we check that an error occurred
assert.Error(t, err)
Expand Down
Loading
Loading