From 31996ae710ec3e5a1b6f65942562975056b6f08c Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Wed, 28 Jan 2026 01:18:26 +0100 Subject: [PATCH] minor improvement on the cue schema validator --- internal/schema/marte.cue | 143 +++++++++++++++++++++++++---- test/validator_schema_meta_test.go | 79 ++++++++++++++++ 2 files changed, 205 insertions(+), 17 deletions(-) create mode 100644 test/validator_schema_meta_test.go diff --git a/internal/schema/marte.cue b/internal/schema/marte.cue index 4a79a68..6ad8caa 100644 --- a/internal/schema/marte.cue +++ b/internal/schema/marte.cue @@ -2,9 +2,32 @@ package schema #Classes: { RealTimeApplication: { - Functions: {...} // type: node - Data!: {...} // type: node - States!: {...} // type: node + Functions!: { + Class: "ReferenceContainer" + [_= !~"^Class$"]: { + #meta: type: "gam" + ... + } + } // type: node + Data!: { + Class: "ReferenceContainer" + DefaultDataSource: string + [_= !~"^(Class|DefaultDataSource)$"]: { + #meta: type: "datasource" + ... + } + } + States!: { + Class: "ReferenceContainer" + [_= !~"^Class$"]: { + Class: "RealTimeState" + ... + } + } // type: node + Scheduler!: { + ... + #meta: type: "scheduler" + } ... } Message: { @@ -23,7 +46,7 @@ package schema Class: "ReferenceContainer" ... } - [_ = !~"^(Class|ENTER)$"]: StateMachineEvent + [_ = !~"^(Class|ENTER|EXIT)$"]: StateMachineEvent ... } StateMachine: { @@ -40,16 +63,19 @@ package schema } GAMScheduler: { TimingDataSource: string // type: reference + #meta: type: "scheduler" ... } TimingDataSource: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } IOGAM: { InputSignals?: {...} // type: node OutputSignals?: {...} // type: node + #meta: type: "gam" ... } ReferenceContainer: { @@ -57,11 +83,13 @@ package schema } ConstantGAM: { ... + #meta: type: "gam" } PIDGAM: { Kp: float | int // type: float (allow int as it promotes) Ki: float | int Kd: float | int + #meta: type: "gam" ... } FileDataSource: { @@ -69,37 +97,44 @@ package schema Format?: string #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } LoggerDataSource: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } DANStream: { Timeout?: int #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } EPICSCAInput: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } EPICSCAOutput: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } EPICSPVAInput: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } EPICSPVAOutput: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } SDNSubscriber: { @@ -113,6 +148,7 @@ package schema IgnoreTimeoutError?: 0 | 1 #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } SDNPublisher: { @@ -121,6 +157,7 @@ package schema Interface?: string #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } UDPReceiver: { @@ -128,12 +165,14 @@ package schema Address?: string #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } UDPSender: { Destination: string #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } FileReader: { @@ -142,6 +181,7 @@ package schema Interpolate?: string #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } FileWriter: { @@ -150,6 +190,7 @@ package schema StoreOnTrigger?: int #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } OrderedClass: { @@ -157,15 +198,25 @@ package schema Second: string ... } - BaseLib2GAM: {...} - ConversionGAM: {...} - DoubleHandshakeGAM: {...} + BaseLib2GAM: { + #meta: type: "gam" + ... + } + ConversionGAM: { + #meta: type: "gam" + ... + } + DoubleHandshakeGAM: { + #meta: type: "gam" + ... + } FilterGAM: { Num: [...] Den: [...] ResetInEachState?: _ InputSignals?: {...} OutputSignals?: {...} + #meta: type: "gam" ... } HistogramGAM: { @@ -173,24 +224,57 @@ package schema StateChangeResetName?: string InputSignals?: {...} OutputSignals?: {...} + #meta: type: "gam" + ... + } + Interleaved2FlatGAM: { + #meta: type: "gam" + ... + } + FlattenedStructIOGAM: { + #meta: type: "gam" ... } - Interleaved2FlatGAM: {...} - FlattenedStructIOGAM: {...} MathExpressionGAM: { Expression: string InputSignals?: {...} OutputSignals?: {...} + #meta: type: "gam" + ... + } + MessageGAM: { + #meta: type: "gam" + ... + } + MuxGAM: { + #meta: type: "gam" + ... + } + SimulinkWrapperGAM: { + #meta: type: "gam" + ... + } + SSMGAM: { + #meta: type: "gam" + ... + } + StatisticsGAM: { + #meta: type: "gam" + ... + } + TimeCorrectionGAM: { + #meta: type: "gam" + ... + } + TriggeredIOGAM: { + + #meta: type: "gam" + ... + } + WaveformGAM: { + #meta: type: "gam" ... } - MessageGAM: {...} - MuxGAM: {...} - SimulinkWrapperGAM: {...} - SSMGAM: {...} - StatisticsGAM: {...} - TimeCorrectionGAM: {...} - TriggeredIOGAM: {...} - WaveformGAM: {...} DAN: { #meta: multithreaded: bool | *false #meta: direction: "OUT" @@ -206,11 +290,13 @@ package schema Signals: {...} #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } LinkDataSource: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } MDSReader: { @@ -220,6 +306,7 @@ package schema Signals: {...} #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } MDSWriter: { @@ -237,72 +324,86 @@ package schema Messages?: {...} #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } NI1588TimeStamp: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } NI6259ADC: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } NI6259DAC: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } NI6259DIO: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } NI6368ADC: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } NI6368DAC: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } NI6368DIO: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } NI9157CircularFifoReader: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } NI9157MxiDataSource: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } OPCUADSInput: { #meta: multithreaded: bool | *false #meta: direction: "IN" + #meta: type: "datasource" ... } OPCUADSOutput: { #meta: multithreaded: bool | *false #meta: direction: "OUT" + #meta: type: "datasource" ... } RealTimeThreadAsyncBridge: { #meta: direction: "INOUT" #meta: multithreaded: bool | true + #meta: type: "datasource" ... } RealTimeThreadSynchronisation: {...} UARTDataSource: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } BaseLib2Wrapper: {...} @@ -314,15 +415,23 @@ package schema GAMDataSource: { #meta: multithreaded: bool | *false #meta: direction: "INOUT" + #meta: type: "datasource" ... } } +#Meta: { + direction?: "IN" | "OUT" | "INOUT" + multithreaded?: bool + ... +} + // Definition for any Object. // It must have a Class field. // Based on Class, it validates against #Classes. #Object: { Class: string + "#meta"?: #Meta // Allow any other field by default (extensibility), // unless #Classes definition is closed. // We allow open structs now. diff --git a/test/validator_schema_meta_test.go b/test/validator_schema_meta_test.go new file mode 100644 index 0000000..a9053fc --- /dev/null +++ b/test/validator_schema_meta_test.go @@ -0,0 +1,79 @@ +package integration + +import ( + "strings" + "testing" + + "github.com/marte-community/marte-dev-tools/internal/index" + "github.com/marte-community/marte-dev-tools/internal/parser" + "github.com/marte-community/marte-dev-tools/internal/validator" +) + +func TestSchemaMetaValidation(t *testing.T) { + // 1. Valid Usage + validContent := ` ++App = { + Class = RealTimeApplication + Functions = { Class = ReferenceContainer } + Data = { Class = ReferenceContainer DefaultDataSource = "DS" } + States = { Class = ReferenceContainer } + Scheduler = { Class = GAMScheduler TimingDataSource = "DS" } + #meta = { + multithreaded = true + } +} +` + pt := index.NewProjectTree() + p := parser.NewParser(validContent) + cfg, err := p.Parse() + if err != nil { + t.Fatal(err) + } + pt.AddFile("valid.marte", cfg) + + v := validator.NewValidator(pt, "") + v.ValidateProject() + + if len(v.Diagnostics) > 0 { + for _, d := range v.Diagnostics { + t.Logf("Diag: %s", d.Message) + } + t.Errorf("Expected no errors for valid #meta") + } + + // 2. Invalid Usage (Wrong Type) + invalidContent := ` ++App = { + Class = RealTimeApplication + Functions = { Class = ReferenceContainer } + Data = { Class = ReferenceContainer DefaultDataSource = "DS" } + States = { Class = ReferenceContainer } + Scheduler = { Class = GAMScheduler TimingDataSource = "DS" } + #meta = { + multithreaded = "yes" // Should be bool + } +} +` + pt2 := index.NewProjectTree() + p2 := parser.NewParser(invalidContent) + cfg2, _ := p2.Parse() + pt2.AddFile("invalid.marte", cfg2) + + v2 := validator.NewValidator(pt2, "") + v2.ValidateProject() + + foundError := false + for _, d := range v2.Diagnostics { + // CUE validation error message + if strings.Contains(d.Message, "mismatched types") || strings.Contains(d.Message, "conflicting values") { + foundError = true + } + } + + if !foundError { + t.Error("Expected error for invalid #meta type, got nothing") + for _, d := range v2.Diagnostics { + t.Logf("Diag: %s", d.Message) + } + } +}