diff --git a/data/spec/bad.module.yaml b/data/spec/bad.module.yaml index f7af3a4c..4af4518b 100644 --- a/data/spec/bad.module.yaml +++ b/data/spec/bad.module.yaml @@ -1,6 +1,6 @@ schema: apigear.module/1.0 -name: Hello -version: "1.0" +name: demo +version: 1.0.0 interfaces: - name: Hello properties: diff --git a/pkg/cfg/api.go b/pkg/cfg/api.go index 73ea6d1a..5f3c5af5 100644 --- a/pkg/cfg/api.go +++ b/pkg/cfg/api.go @@ -6,7 +6,9 @@ import ( ) func ConfigDir() string { + rw.RLock() file := v.ConfigFileUsed() + rw.RUnlock() return filepath.Dir(file) } @@ -27,8 +29,15 @@ func AppendRecentEntry(value string) error { } // prepend the new value recent = append([]string{value}, recent...) + + rw.Lock() v.Set(KeyRecent, recent) - return v.WriteConfig() + err := v.WriteConfig() + rw.Unlock() + if err != nil { + return err + } + return nil } // RemoveRecentEntry removes a recent entry from the list @@ -40,13 +49,24 @@ func RemoveRecentEntry(value string) error { break } } + rw.Lock() v.Set(KeyRecent, recent) - return v.WriteConfig() + err := v.WriteConfig() + rw.Unlock() + if err != nil { + return err + } + return nil } // RecentEntries returns the list of recent entries func RecentEntries() []string { + rw.RLock() items := v.GetStringSlice(KeyRecent) + rw.RUnlock() + if len(items) == 0 { + return []string{} + } if len(items) > 5 { return items[len(items)-5:] } @@ -54,49 +74,68 @@ func RecentEntries() []string { } func SetBuildInfo(version, commit, date string) { + rw.Lock() v.Set(KeyVersion, version) v.Set(KeyCommit, commit) v.Set(KeyDate, date) err := v.WriteConfig() + rw.Unlock() if err != nil { log.Printf("error writing config: %v", err) } } func IsSet(key string) bool { - return v.IsSet(key) + rw.RLock() + result := v.IsSet(key) + rw.RUnlock() + return result } func Set(key string, value any) { + rw.Lock() v.Set(key, value) + rw.Unlock() } func Get(key string) any { - return v.Get(key) + rw.RLock() + result := v.Get(key) + rw.RUnlock() + return result } func GetString(key string) string { - return v.GetString(key) + rw.RLock() + result := v.GetString(key) + rw.RUnlock() + return result } func WriteConfig() error { - return v.WriteConfig() + rw.Lock() + err := v.WriteConfig() + rw.Unlock() + if err != nil { + return err + } + return nil } func EditorCommand() string { - return v.GetString(KeyEditorCommand) + return GetString(KeyEditorCommand) } func ServerPort() string { - return v.GetString(KeyServerPort) + return GetString(KeyServerPort) } func UpdateChannel() string { - return v.GetString(KeyUpdateChannel) + return GetString(KeyUpdateChannel) } func RegistryDir() string { - return v.GetString(KeyRegistryDir) + return GetString(KeyRegistryDir) } func RegistryCachePath() string { @@ -104,29 +143,35 @@ func RegistryCachePath() string { } func AllSettings() map[string]interface{} { - return v.AllSettings() + rw.RLock() + result := v.AllSettings() + rw.RUnlock() + return result } func ConfigFileUsed() string { - return v.ConfigFileUsed() + rw.RLock() + result := v.ConfigFileUsed() + rw.RUnlock() + return result } func CacheDir() string { - return v.GetString(KeyCacheDir) + return GetString(KeyCacheDir) } func RegistryUrl() string { - return v.GetString(KeyRegistryUrl) + return GetString(KeyRegistryUrl) } func BuildVersion() string { - return v.GetString(KeyVersion) + return GetString(KeyVersion) } func BuildDate() string { - return v.GetString(KeyDate) + return GetString(KeyDate) } func BuildCommit() string { - return v.GetString(KeyCommit) + return GetString(KeyCommit) } diff --git a/pkg/cfg/config.go b/pkg/cfg/config.go index 30bed3e6..60e0d056 100644 --- a/pkg/cfg/config.go +++ b/pkg/cfg/config.go @@ -3,6 +3,7 @@ package cfg import ( "fmt" "os" + "sync" "github.com/apigear-io/cli/pkg/helper" "github.com/spf13/viper" @@ -28,7 +29,8 @@ const ( ) var ( - v *viper.Viper + v *viper.Viper + rw = sync.RWMutex{} ) func init() { @@ -43,7 +45,9 @@ func init() { fmt.Println(err) os.Exit(1) } + rw.Lock() v = vip + rw.Unlock() } func NewConfig(cfgDir string) (*viper.Viper, error) { @@ -105,5 +109,7 @@ func NewConfig(cfgDir string) (*viper.Viper, error) { } func SetConfig(c *viper.Viper) { + rw.Lock() v = c + rw.Unlock() } diff --git a/pkg/helper/fs.go b/pkg/helper/fs.go index 6c97cd6a..13359d78 100644 --- a/pkg/helper/fs.go +++ b/pkg/helper/fs.go @@ -116,6 +116,10 @@ func ReadYamlFromData(in []byte, out any) error { return yaml.Unmarshal(in, out) } +func ReadYamlFromString(in string, out any) error { + return yaml.Unmarshal([]byte(in), out) +} + func YamlToJson(in []byte) ([]byte, error) { out := make(map[string]any) err := yaml.Unmarshal(in, &out) diff --git a/pkg/idl/helper.go b/pkg/idl/helper.go new file mode 100644 index 00000000..461d5f3b --- /dev/null +++ b/pkg/idl/helper.go @@ -0,0 +1,25 @@ +package idl + +import "github.com/apigear-io/cli/pkg/model" + +func LoadIdlFromString(name string, content string) (*model.System, error) { + system := model.NewSystem(name) + parser := NewParser(system) + err := parser.ParseString(content) + if err != nil { + return nil, err + } + return system, nil +} + +func LoadIdlFromFiles(name string, files []string) (*model.System, error) { + system := model.NewSystem(name) + for _, file := range files { + parser := NewParser(system) + err := parser.ParseFile(file) + if err != nil { + return nil, err + } + } + return system, nil +} diff --git a/pkg/idl/idl_advanced_test.go b/pkg/idl/idl_advanced_test.go index a9d99062..a1917895 100644 --- a/pkg/idl/idl_advanced_test.go +++ b/pkg/idl/idl_advanced_test.go @@ -7,7 +7,7 @@ import ( ) func TestManyParamsFuncs(t *testing.T) { - s, err := loadIdl("advanced", []string{"./testdata/advanced.idl"}) + s, err := LoadIdlFromFiles("advanced", []string{"./testdata/advanced.idl"}) assert.NoError(t, err) assert.NotNil(t, s) table := []struct { @@ -39,7 +39,7 @@ func TestManyParamsFuncs(t *testing.T) { } func TestManyParamsSigs(t *testing.T) { - s, err := loadIdl("advanced", []string{"./testdata/advanced.idl"}) + s, err := LoadIdlFromFiles("advanced", []string{"./testdata/advanced.idl"}) assert.NoError(t, err) assert.NotNil(t, s) table := []struct { diff --git a/pkg/idl/idl_data_test.go b/pkg/idl/idl_data_test.go index 1c58a0a0..3c6037b0 100644 --- a/pkg/idl/idl_data_test.go +++ b/pkg/idl/idl_data_test.go @@ -7,7 +7,7 @@ import ( ) func TestDataProps(t *testing.T) { - s, err := loadIdl("data", []string{"./testdata/data.idl"}) + s, err := LoadIdlFromFiles("data", []string{"./testdata/data.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -44,7 +44,7 @@ func TestDataProps(t *testing.T) { } func TestDataFuncs(t *testing.T) { - s, err := loadIdl("data", []string{"./testdata/data.idl"}) + s, err := LoadIdlFromFiles("data", []string{"./testdata/data.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -84,7 +84,7 @@ func TestDataFuncs(t *testing.T) { } func TestDataSignals(t *testing.T) { - s, err := loadIdl("data", []string{"./testdata/data.idl"}) + s, err := LoadIdlFromFiles("data", []string{"./testdata/data.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -124,7 +124,7 @@ func TestDataSignals(t *testing.T) { } func TestStructs(t *testing.T) { - s, err := loadIdl("structs", []string{"./testdata/data.idl"}) + s, err := LoadIdlFromFiles("structs", []string{"./testdata/data.idl"}) assert.NoError(t, err) table := []struct { sName string diff --git a/pkg/idl/idl_enum_test.go b/pkg/idl/idl_enum_test.go index 00816d66..d4222e27 100644 --- a/pkg/idl/idl_enum_test.go +++ b/pkg/idl/idl_enum_test.go @@ -8,7 +8,7 @@ import ( ) func TestEnumIdl(t *testing.T) { - s, err := loadIdl("enum", []string{"./testdata/enum.idl"}) + s, err := LoadIdlFromFiles("enum", []string{"./testdata/enum.idl"}) assert.NoError(t, err) table := []struct { eName string @@ -40,7 +40,7 @@ func TestEnumIdl(t *testing.T) { } func TestEnumProps(t *testing.T) { - s, err := loadIdl("enum", []string{"./testdata/enum.idl"}) + s, err := LoadIdlFromFiles("enum", []string{"./testdata/enum.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -70,7 +70,7 @@ func TestEnumProps(t *testing.T) { } func TestEnumFuncs(t *testing.T) { - s, err := loadIdl("enum", []string{"./testdata/enum.idl"}) + s, err := LoadIdlFromFiles("enum", []string{"./testdata/enum.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -105,7 +105,7 @@ func TestEnumFuncs(t *testing.T) { } func TestEnumSignals(t *testing.T) { - s, err := loadIdl("enum", []string{"./testdata/enum.idl"}) + s, err := LoadIdlFromFiles("enum", []string{"./testdata/enum.idl"}) assert.NoError(t, err) table := []struct { iName string diff --git a/pkg/idl/idl_many_test.go b/pkg/idl/idl_many_test.go index 8968481a..c52b10d3 100644 --- a/pkg/idl/idl_many_test.go +++ b/pkg/idl/idl_many_test.go @@ -7,7 +7,7 @@ import ( ) func TestManyModules(t *testing.T) { - s, err := loadIdl("many", []string{"./testdata/simple.idl", "./testdata/data.idl", "./testdata/enum.idl"}) + s, err := LoadIdlFromFiles("many", []string{"./testdata/simple.idl", "./testdata/data.idl", "./testdata/enum.idl"}) assert.NoError(t, err) assert.NotNil(t, s) assert.Equal(t, "many", s.Name) diff --git a/pkg/idl/idl_meta_test.go b/pkg/idl/idl_meta_test.go index 8a551c81..2a406c8a 100644 --- a/pkg/idl/idl_meta_test.go +++ b/pkg/idl/idl_meta_test.go @@ -10,7 +10,7 @@ import ( ) func TestSimpleTag(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/meta.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/meta.idl"}) assert.NoError(t, err) table := []struct { ifaceId string @@ -31,7 +31,7 @@ func TestSimpleTag(t *testing.T) { } func TestPropertyMeta(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/meta.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/meta.idl"}) assert.NoError(t, err) table := []struct { ifaceId string @@ -54,7 +54,7 @@ func TestPropertyMeta(t *testing.T) { } func TestOperationMeta(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/meta.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/meta.idl"}) assert.NoError(t, err) table := []struct { ifaceId string @@ -77,7 +77,7 @@ func TestOperationMeta(t *testing.T) { } func TestSignalMeta(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/meta.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/meta.idl"}) assert.NoError(t, err) table := []struct { ifaceId string @@ -100,7 +100,7 @@ func TestSignalMeta(t *testing.T) { } func TestStructMeta(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/meta.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/meta.idl"}) assert.NoError(t, err) table := []struct { structId string @@ -204,7 +204,7 @@ const out1 = ` ` func TestTemplate(t *testing.T) { - s, err := loadIdlFromString("meta", idl1) + s, err := LoadIdlFromString("meta", idl1) assert.NoError(t, err) tpl, err := template.New("test").Parse(tpl1) assert.NoError(t, err) diff --git a/pkg/idl/idl_properties_test.go b/pkg/idl/idl_properties_test.go index e10db31a..8a422f85 100644 --- a/pkg/idl/idl_properties_test.go +++ b/pkg/idl/idl_properties_test.go @@ -7,7 +7,7 @@ import ( ) func TestProperties(t *testing.T) { - s, err := loadIdl("meta", []string{"./testdata/properties.idl"}) + s, err := LoadIdlFromFiles("meta", []string{"./testdata/properties.idl"}) assert.NoError(t, err) iface := s.LookupInterface("demo", "Demo") assert.NotNil(t, iface) diff --git a/pkg/idl/idl_simple_test.go b/pkg/idl/idl_simple_test.go index c72b94bd..5d771850 100644 --- a/pkg/idl/idl_simple_test.go +++ b/pkg/idl/idl_simple_test.go @@ -8,7 +8,7 @@ import ( ) func TestSimpleProps(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -39,7 +39,7 @@ func TestSimpleProps(t *testing.T) { } func TestSimpleFuncs(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -75,7 +75,7 @@ func TestSimpleFuncs(t *testing.T) { } func TestSimpleSignals(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -110,7 +110,7 @@ func TestSimpleSignals(t *testing.T) { } func TestSimpleArrayProps(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -142,7 +142,7 @@ func TestSimpleArrayProps(t *testing.T) { } func TestSimpleArrayFuncs(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string @@ -179,7 +179,7 @@ func TestSimpleArrayFuncs(t *testing.T) { } func TestSimpleArraySignals(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) table := []struct { iName string diff --git a/pkg/idl/idl_test.go b/pkg/idl/idl_test.go index a8cd4ce3..f2a4ed2e 100644 --- a/pkg/idl/idl_test.go +++ b/pkg/idl/idl_test.go @@ -3,34 +3,11 @@ package idl import ( "testing" - "github.com/apigear-io/cli/pkg/model" "github.com/stretchr/testify/assert" ) -func loadIdl(name string, files []string) (*model.System, error) { - system := model.NewSystem(name) - for _, file := range files { - parser := NewParser(system) - err := parser.ParseFile(file) - if err != nil { - return nil, err - } - } - return system, nil -} - -func loadIdlFromString(name string, content string) (*model.System, error) { - system := model.NewSystem(name) - parser := NewParser(system) - err := parser.ParseString(content) - if err != nil { - return nil, err - } - return system, nil -} - func TestSimpleIdl(t *testing.T) { - s, err := loadIdl("simple", []string{"./testdata/simple.idl"}) + s, err := LoadIdlFromFiles("simple", []string{"./testdata/simple.idl"}) assert.NoError(t, err) assert.NotNil(t, s) assert.Equal(t, "simple", s.Name) diff --git a/pkg/log/eventwriter.go b/pkg/log/eventwriter.go index 4edcd3aa..133f299e 100644 --- a/pkg/log/eventwriter.go +++ b/pkg/log/eventwriter.go @@ -4,21 +4,13 @@ import ( "bytes" "encoding/json" "io" - "time" ) var ( - eventEmitter func(*ReportEvent) + eventEmitter func(e map[string]interface{}) bytesEmitter func(s string) ) -type ReportEvent struct { - Level string `json:"level"` - Message string `json:"message"` - Timestamp time.Time `json:"timestamp"` - Error string `json:"error,omitempty"` -} - type EventLogWriter struct { } @@ -31,19 +23,20 @@ func (w *EventLogWriter) Write(p []byte) (n int, err error) { bytesEmitter(string(p)) } if eventEmitter != nil { - var event ReportEvent + event := map[string]interface{}{} d := json.NewDecoder(bytes.NewReader(p)) d.UseNumber() err = d.Decode(&event) if err != nil { return 0, err } - eventEmitter(&event) + // event["id"] = uuid.New().String() + eventEmitter(event) } return len(p), nil } -func OnReportEvent(handler func(*ReportEvent)) { +func OnReportEvent(handler func(e map[string]interface{})) { eventEmitter = handler } diff --git a/pkg/log/logger.go b/pkg/log/logger.go index 8d8ae6f2..82659e16 100644 --- a/pkg/log/logger.go +++ b/pkg/log/logger.go @@ -12,6 +12,13 @@ var ( logger zerolog.Logger ) +type UUIDHook struct { +} + +func (h UUIDHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + e.Str("id", helper.NewUUID()) +} + func init() { level := zerolog.InfoLevel debug := os.Getenv("DEBUG") == "1" @@ -29,7 +36,8 @@ func init() { NewEventLogWriter(), newRollingFile(logFile), ) - logger = zerolog.New(multi).With().Timestamp().Logger().Level(level) + logger = zerolog.New(multi). + With().Timestamp().Logger().Hook(&UUIDHook{}).Level(level) if verbose { logger = logger.With().Caller().Logger() diff --git a/pkg/model/collection.go b/pkg/model/collection.go new file mode 100644 index 00000000..109821f6 --- /dev/null +++ b/pkg/model/collection.go @@ -0,0 +1,97 @@ +package model + +import "fmt" + +type NamedElement interface { + Name() string + Kind() Kind +} + +type Collection struct { + items []NamedElement + keys map[string]bool +} + +func NewCollection() *Collection { + return &Collection{ + items: make([]NamedElement, 0), + keys: make(map[string]bool), + } +} + +func (c *Collection) Items() []NamedElement { + return c.items +} + +func (c *Collection) Filter(t Kind) []NamedElement { + r := make([]NamedElement, 0) + for _, i := range c.items { + if i.Kind() == t { + r = append(r, i) + } + } + return r +} + +func (c *Collection) Add(n NamedElement) error { + if c.keys[n.Name()] { + return fmt.Errorf("duplicate name: %s", n.Name()) + } + c.items = append(c.items, n) + c.keys[n.Name()] = true + return nil +} + +func (c *Collection) Lookup(name string) NamedElement { + for _, i := range c.items { + if i.Name() == name { + return i + } + } + return nil +} + +// NestedCollection is a collection of collections. +type NestedCollection struct { + collections []*Collection +} + +// NewNestedCollection returns a new NestedCollection. +func NewNestedCollection() *NestedCollection { + return &NestedCollection{ + collections: make([]*Collection, 0), + } +} + +// Items returns all items in all collections. +func (c *NestedCollection) Items() []NamedElement { + r := make([]NamedElement, 0) + for _, i := range c.collections { + r = append(r, i.Items()...) + } + return r +} + +// Add adds a collection to the nested collection. +func (c *NestedCollection) Add(n *Collection) { + c.collections = append(c.collections, n) +} + +// Filter returns all items with the given kind. +func (c *NestedCollection) Filter(t Kind) []NamedElement { + r := make([]NamedElement, 0) + for _, i := range c.collections { + r = append(r, i.Filter(t)...) + } + return r +} + +// Lookup returns the first item with the given name. +func (c *NestedCollection) Lookup(name string) NamedElement { + for _, i := range c.collections { + if r := i.Lookup(name); r != nil { + return r + } + } + return nil +} diff --git a/pkg/model/enum.go b/pkg/model/enum.go index bd10a2a1..1aafdbf9 100644 --- a/pkg/model/enum.go +++ b/pkg/model/enum.go @@ -1,5 +1,7 @@ package model +import "fmt" + // Enum is an enumeration. type Enum struct { NamedNode `json:",inline" yaml:",inline"` @@ -18,8 +20,13 @@ func NewEnum(name string) *Enum { // ResolveAll resolves all references in the enum. func (e *Enum) ResolveAll(mod *Module) error { + names := make(map[string]bool) autoValue := true for _, mem := range e.Members { + if names[mem.Name] { + return fmt.Errorf("%s: duplicate name: %s", e.Name, mem.Name) + } + names[mem.Name] = true err := mem.ResolveAll(mod) if err != nil { return err diff --git a/pkg/model/iface.go b/pkg/model/iface.go index 5f5b49fc..17d5f3dc 100644 --- a/pkg/model/iface.go +++ b/pkg/model/iface.go @@ -1,5 +1,7 @@ package model +import "fmt" + type Signal struct { NamedNode `json:",inline" yaml:",inline"` Params []*TypedNode `json:"params" yaml:"params"` @@ -122,19 +124,33 @@ func (i Interface) LookupSignal(name string) *Signal { } func (i *Interface) ResolveAll(mod *Module) error { + // check if any names are duplicated + names := make(map[string]bool) for _, p := range i.Properties { + if names[p.Name] { + return fmt.Errorf("%s: duplicate name: %s", i.Name, p.Name) + } + names[p.Name] = true err := p.ResolveAll(mod) if err != nil { return err } } for _, op := range i.Operations { + if names[op.Name] { + return fmt.Errorf("%s: duplicate name: %s", i.Name, op.Name) + } + names[op.Name] = true err := op.ResolveAll(mod) if err != nil { return err } } for _, s := range i.Signals { + if names[s.Name] { + return fmt.Errorf("%s: duplicate name: %s", i.Name, s.Name) + } + names[s.Name] = true err := s.ResolveAll(mod) if err != nil { return err diff --git a/pkg/model/iface_test.go b/pkg/model/iface_test.go index 4c3195d2..ffc64628 100644 --- a/pkg/model/iface_test.go +++ b/pkg/model/iface_test.go @@ -13,7 +13,7 @@ func TestInterface(t *testing.T) { err := helper.ReadDocument("./testdata/module.yaml", &module) assert.NoError(t, err) assert.Equal(t, "Module01", module.Name) - assert.Equal(t, "1.0", string(module.Version)) + assert.Equal(t, "1.0.0", string(module.Version)) assert.Equal(t, 5, len(module.Interfaces)) iface0 := module.Interfaces[0] assert.Equal(t, "Interface01", iface0.Name) @@ -62,5 +62,55 @@ func TestOperations(t *testing.T) { assert.Equal(t, "param01", op0.Params[0].Name) assert.Equal(t, "bool", op0.Params[0].Schema.Type) assert.Equal(t, "bool", op0.Return.Schema.Type) +} + +const duplicatesYAML = `schema: apigear.module/1.0 +name: demo +version: "0.0.1" +interfaces: + - name: Hello + - name: Hello` + +func TestInterfaceNameDuplicates(t *testing.T) { + var module Module + err := helper.ReadYamlFromString(duplicatesYAML, &module) + assert.NoError(t, err) + err = module.ResolveAll() + assert.Error(t, err) + assert.Equal(t, err.Error(), "demo: duplicate name Hello") +} + +const duplicates2YAML = `schema: apigear.module/1.0 +name: demo +version: "0.0.1" +interfaces: + - name: Hello +structs: + - name: Hello` +func TestStructNameDuplicates(t *testing.T) { + var module Module + err := helper.ReadYamlFromString(duplicates2YAML, &module) + assert.NoError(t, err) + err = module.ResolveAll() + assert.Error(t, err) + assert.Equal(t, err.Error(), "demo: duplicate name Hello") +} + +const duplicates3YAML = `schema: apigear.module/1.0 +name: demo +version: "0.0.1" +interfaces: + - name: Hello +enums: + - name: Hello +` + +func TestEnumNameDuplicates(t *testing.T) { + var module Module + err := helper.ReadYamlFromString(duplicates3YAML, &module) + assert.NoError(t, err) + err = module.ResolveAll() + assert.Error(t, err) + assert.Equal(t, err.Error(), "demo: duplicate name Hello") } diff --git a/pkg/model/module.go b/pkg/model/module.go index d7d93b2b..aa365f7e 100644 --- a/pkg/model/module.go +++ b/pkg/model/module.go @@ -1,6 +1,7 @@ package model import ( + "fmt" "strconv" "strings" ) @@ -136,19 +137,33 @@ func (m Module) LookupDefaultEnumMember(name string) *EnumMember { } func (m *Module) ResolveAll() error { + // check for duplicate names + names := make(map[string]bool) for _, i := range m.Interfaces { + if names[i.Name] { + return fmt.Errorf("%s: duplicate name %s", m.Name, i.Name) + } + names[i.Name] = true err := i.ResolveAll(m) if err != nil { return err } } for _, s := range m.Structs { + if names[s.Name] { + return fmt.Errorf("%s: duplicate name %s", m.Name, s.Name) + } + names[s.Name] = true err := s.ResolveAll(m) if err != nil { return err } } for _, e := range m.Enums { + if names[e.Name] { + return fmt.Errorf("%s: duplicate name %s", m.Name, e.Name) + } + names[e.Name] = true err := e.ResolveAll(m) if err != nil { return err diff --git a/pkg/model/module_test.go b/pkg/model/module_test.go index bcd98d68..a15190fc 100644 --- a/pkg/model/module_test.go +++ b/pkg/model/module_test.go @@ -13,7 +13,7 @@ func TestModuleYaml(t *testing.T) { err := helper.ReadDocument("./testdata/module.yaml", &module) assert.NoError(t, err) assert.Equal(t, "Module01", module.Name) - assert.Equal(t, "1.0", string(module.Version)) + assert.Equal(t, "1.0.0", string(module.Version)) } func TestModuleJson(t *testing.T) { @@ -21,5 +21,5 @@ func TestModuleJson(t *testing.T) { err := helper.ReadDocument("./testdata/module.json", &module) assert.NoError(t, err) assert.Equal(t, "Module01", module.Name) - assert.Equal(t, "1.0", string(module.Version)) + assert.Equal(t, "1.0.0", string(module.Version)) } diff --git a/pkg/model/struct.go b/pkg/model/struct.go index 19ad38ff..425129a3 100644 --- a/pkg/model/struct.go +++ b/pkg/model/struct.go @@ -1,5 +1,7 @@ package model +import "fmt" + type Struct struct { NamedNode `json:",inline" yaml:",inline"` Fields []*TypedNode `json:"fields" yaml:"fields"` @@ -16,10 +18,16 @@ func NewStruct(name string) *Struct { } func (s *Struct) ResolveAll(m *Module) error { + // check for duplicate fields + names := make(map[string]bool) if s.Fields == nil { s.Fields = make([]*TypedNode, 0) } for _, f := range s.Fields { + if names[f.Name] { + return fmt.Errorf("%s: duplicate name: %s", s.Name, f.Name) + } + names[f.Name] = true err := f.ResolveAll(m) if err != nil { return err diff --git a/pkg/model/system.go b/pkg/model/system.go index 0dc12468..7cf0ed91 100644 --- a/pkg/model/system.go +++ b/pkg/model/system.go @@ -1,5 +1,7 @@ package model +import "fmt" + type System struct { NamedNode `json:",inline" yaml:",inline"` Modules []*Module `json:"modules" yaml:"modules"` @@ -74,7 +76,12 @@ func (s System) LookupSignal(moduleName string, ifaceName string, eventName stri } func (s *System) ResolveAll() error { + names := make(map[string]bool) for _, m := range s.Modules { + if names[m.Name] { + return fmt.Errorf("%s: duplicate name: %s", s.Name, m.Name) + } + names[m.Name] = true err := m.ResolveAll() if err != nil { return err diff --git a/pkg/model/testdata/duplicates.module.yaml b/pkg/model/testdata/duplicates.module.yaml new file mode 100644 index 00000000..dce694bc --- /dev/null +++ b/pkg/model/testdata/duplicates.module.yaml @@ -0,0 +1,8 @@ +schema: apigear.module/1.0 +version: "0.1.0" + +name: duplicates + +interfaces: + - name: Demo + - name: Demo \ No newline at end of file diff --git a/pkg/model/testdata/module.json b/pkg/model/testdata/module.json index df430acd..1a3334d1 100644 --- a/pkg/model/testdata/module.json +++ b/pkg/model/testdata/module.json @@ -1,6 +1,6 @@ { "name": "Module01", - "version": "1.0", + "version": "1.0.0", "interfaces": [ { "name": "Interface01", diff --git a/pkg/model/testdata/module.yaml b/pkg/model/testdata/module.yaml index 648adbb9..f3d4d9f3 100644 --- a/pkg/model/testdata/module.yaml +++ b/pkg/model/testdata/module.yaml @@ -1,5 +1,5 @@ name: Module01 -version: "1.0" +version: "1.0.0" interfaces: - name: Interface01 properties: diff --git a/pkg/spec/check.go b/pkg/spec/check.go index ba63b7da..761b6412 100644 --- a/pkg/spec/check.go +++ b/pkg/spec/check.go @@ -70,7 +70,15 @@ func CheckFile(file string) (*Result, error) { if err != nil { return nil, err } - return CheckJson(typ, data) + result, error := CheckJson(typ, data) + if error != nil { + return nil, error + } + if !result.Valid() { + result.File = file + log.Error().Msgf("file %s is not valid: %s", file, result.Errors) + } + return result, nil } func CheckNdjsonFile(name string) error { diff --git a/pkg/up/updater.go b/pkg/up/updater.go index 9772c307..75ab3c88 100644 --- a/pkg/up/updater.go +++ b/pkg/up/updater.go @@ -51,10 +51,19 @@ func (u *Updater) Check(ctx context.Context) (*selfupdate.Release, error) { if err != nil { return nil, err } - if found && latest.GreaterThan(u.version) { - return latest, nil + if !found { + return nil, fmt.Errorf("no release found for %s", u.repo) } - return nil, nil + if latest == nil { + return nil, fmt.Errorf("no release found for %s", u.repo) + } + log.Info().Msgf("latest release: %s", latest.Version()) + if !latest.GreaterThan(u.version) { + log.Info().Msgf("current version %s is the latest", u.version) + return nil, nil + } + log.Info().Msgf("new version %s is available", latest.Version()) + return latest, nil } // Update updates the current executable to the latest release