diff --git a/EiffelArtifactDeployedEventV0_1_0.go b/EiffelArtifactDeployedEventV0_1_0.go new file mode 100644 index 0000000..36f5b7f --- /dev/null +++ b/EiffelArtifactDeployedEventV0_1_0.go @@ -0,0 +1,135 @@ +// Copyright Axis Communications AB. +// +// For a full list of individual contributors, please see the commit history. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// THIS FILE IS AUTOMATICALLY GENERATED AND MUST NOT BE EDITED BY HAND. + +package eiffelevents + +import ( + "fmt" + "reflect" + "time" + + "github.com/clarketm/json" + "github.com/google/uuid" +) + +// NewArtifactDeployedV0_1_0 creates a new struct pointer that represents version +// 0.1.0 of EiffelArtifactDeployedEvent. The returned struct has all required +// meta members populated. +func NewArtifactDeployedV0_1_0(modifiers ...Modifier) (*ArtifactDeployedV0_1_0, error) { + var event ArtifactDeployedV0_1_0 + event.Meta.Type = "EiffelArtifactDeployedEvent" + event.Meta.ID = uuid.NewString() + event.Meta.Version = eventTypeTable[event.Meta.Type][0].latestVersion + event.Meta.Time = time.Now().UnixMilli() + for _, modifier := range modifiers { + if err := modifier(&event); err != nil { + return nil, fmt.Errorf("error applying modifier to new ArtifactDeployedV0_1_0: %w", err) + } + } + return &event, nil +} + +// MarshalJSON returns the JSON encoding of the event. +func (e *ArtifactDeployedV0_1_0) MarshalJSON() ([]byte, error) { + // The standard encoding/json package doesn't honor omitempty for + // non-pointer structs (it doesn't recurse into values, only examines + // the immediate value). This is a not terribly elegant way of making + // sure that this struct is marshaled by github.com/clarketm/json + // without the infinite loop we'd get by just passing the struct to + // github.com/clarketm/json.Marshal. + // + // Make sure the links slice is non-null so that non-initialized slices + // get serialized as "[]" instead of "null". + links := e.Links + if links == nil { + links = make(EventLinksV1, 0) + } + s := struct { + Data *ArtDV0_1_0Data `json:"data"` + Links EventLinksV1 `json:"links"` + Meta *MetaV3 `json:"meta"` + }{ + Data: &e.Data, + Links: links, + Meta: &e.Meta, + } + return json.Marshal(s) +} + +func (e *ArtifactDeployedV0_1_0) SetField(fieldName string, value interface{}) error { + return setField(reflect.ValueOf(e), fieldName, value) +} + +// String returns the JSON encoding of the event. +func (e *ArtifactDeployedV0_1_0) String() string { + b, err := e.MarshalJSON() + if err != nil { + // This should never happen, and if it does happen it's not clear that + // there's a reasonable way to recover. Returning an error message + // instead of the JSON string won't end well. + panic(err) + } + return string(b) +} + +var _ FieldSetter = &ArtifactDeployedV0_1_0{} +var _ MetaTeller = &ArtifactDeployedV0_1_0{} + +// ID returns the value of the meta.id field. +func (e ArtifactDeployedV0_1_0) ID() string { + return e.Meta.ID +} + +// Type returns the value of the meta.type field. +func (e ArtifactDeployedV0_1_0) Type() string { + return e.Meta.Type +} + +// Version returns the value of the meta.version field. +func (e ArtifactDeployedV0_1_0) Version() string { + return e.Meta.Version +} + +// Time returns the value of the meta.time field. +func (e ArtifactDeployedV0_1_0) Time() int64 { + return e.Meta.Time +} + +// DomainID returns the value of the meta.source.domainId field. +func (e ArtifactDeployedV0_1_0) DomainID() string { + return e.Meta.Source.DomainID +} + +type ArtifactDeployedV0_1_0 struct { + // Mandatory fields + Data ArtDV0_1_0Data `json:"data"` + Links EventLinksV1 `json:"links"` + Meta MetaV3 `json:"meta"` + + // Optional fields + +} + +type ArtDV0_1_0Data struct { + // Mandatory fields + + // Optional fields + CustomData []CustomDataV1 `json:"customData,omitempty"` + Description string `json:"description,omitempty"` + URI string `json:"uri,omitempty"` +} diff --git a/README.md b/README.md index 7b2b9d0..81ce5db 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,11 @@ below. ## Creating new events The struct types used to represent Eiffel events are named after the event -types without the "Eiffel" prefix and "Event" suffix, and the event's major -version as a suffix. Hence, each event's major version gets its own struct. +types without the "Eiffel" prefix and "Event" suffix, and with a version suffix. +For non-experimental event versions (1.0.0 and up) the version suffix is the +event's major version (i.e. each major version gets its own struct) while for +experimental event versions (0.x.y) every single version gets its own struct +(because every version is allowed to contain backwards incompatible changes). The following example shows two methods of creating events, with and without a factory. diff --git a/events_test_roundtripdata.go b/events_test_roundtripdata.go index 7b629a6..efaba15 100644 --- a/events_test_roundtripdata.go +++ b/events_test_roundtripdata.go @@ -33,6 +33,7 @@ var eventRoundTripTestTable = []struct { {"protocol/examples/events/EiffelArtifactCreatedEvent/dependent.json", &ArtifactCreatedV3{}}, {"protocol/examples/events/EiffelArtifactCreatedEvent/interface.json", &ArtifactCreatedV3{}}, {"protocol/examples/events/EiffelArtifactCreatedEvent/simple.json", &ArtifactCreatedV3{}}, + {"protocol/examples/events/EiffelArtifactDeployedEvent/simple.json", &ArtifactDeployedV0_1_0{}}, {"protocol/examples/events/EiffelArtifactPublishedEvent/multifile.json", &ArtifactPublishedV3{}}, {"protocol/examples/events/EiffelArtifactPublishedEvent/simple.json", &ArtifactPublishedV3{}}, {"protocol/examples/events/EiffelArtifactReusedEvent/simple.json", &ArtifactReusedV3{}}, diff --git a/eventtypetable.go b/eventtypetable.go index c0e2043..0e5f206 100644 --- a/eventtypetable.go +++ b/eventtypetable.go @@ -61,6 +61,9 @@ var eventTypeTable = map[string]map[int64]majorEventVersion{ 2: majorEventVersion{reflect.TypeOf(ArtifactCreatedV2{}), "2.0.0"}, 3: majorEventVersion{reflect.TypeOf(ArtifactCreatedV3{}), "3.3.0"}, }, + "EiffelArtifactDeployedEvent": { + 0: majorEventVersion{reflect.TypeOf(ArtifactDeployedV0_1_0{}), "0.1.0"}, + }, "EiffelArtifactPublishedEvent": { 1: majorEventVersion{reflect.TypeOf(ArtifactPublishedV1{}), "1.1.0"}, 2: majorEventVersion{reflect.TypeOf(ArtifactPublishedV2{}), "2.0.0"}, @@ -79,7 +82,7 @@ var eventTypeTable = map[string]map[int64]majorEventVersion{ "EiffelConfidenceLevelModifiedEvent": { 1: majorEventVersion{reflect.TypeOf(ConfidenceLevelModifiedV1{}), "1.1.0"}, 2: majorEventVersion{reflect.TypeOf(ConfidenceLevelModifiedV2{}), "2.0.0"}, - 3: majorEventVersion{reflect.TypeOf(ConfidenceLevelModifiedV3{}), "3.2.0"}, + 3: majorEventVersion{reflect.TypeOf(ConfidenceLevelModifiedV3{}), "3.3.0"}, }, "EiffelEnvironmentDefinedEvent": { 1: majorEventVersion{reflect.TypeOf(EnvironmentDefinedV1{}), "1.1.0"}, @@ -100,7 +103,7 @@ var eventTypeTable = map[string]map[int64]majorEventVersion{ 1: majorEventVersion{reflect.TypeOf(IssueVerifiedV1{}), "1.1.0"}, 2: majorEventVersion{reflect.TypeOf(IssueVerifiedV2{}), "2.0.0"}, 3: majorEventVersion{reflect.TypeOf(IssueVerifiedV3{}), "3.0.0"}, - 4: majorEventVersion{reflect.TypeOf(IssueVerifiedV4{}), "4.2.0"}, + 4: majorEventVersion{reflect.TypeOf(IssueVerifiedV4{}), "4.3.0"}, }, "EiffelSourceChangeCreatedEvent": { 1: majorEventVersion{reflect.TypeOf(SourceChangeCreatedV1{}), "1.1.0"}, @@ -131,7 +134,7 @@ var eventTypeTable = map[string]map[int64]majorEventVersion{ "EiffelTestCaseTriggeredEvent": { 1: majorEventVersion{reflect.TypeOf(TestCaseTriggeredV1{}), "1.1.0"}, 2: majorEventVersion{reflect.TypeOf(TestCaseTriggeredV2{}), "2.0.0"}, - 3: majorEventVersion{reflect.TypeOf(TestCaseTriggeredV3{}), "3.2.0"}, + 3: majorEventVersion{reflect.TypeOf(TestCaseTriggeredV3{}), "3.4.0"}, }, "EiffelTestExecutionRecipeCollectionCreatedEvent": { 1: majorEventVersion{reflect.TypeOf(TestExecutionRecipeCollectionCreatedV1{}), "1.0.0"}, diff --git a/internal/cmd/editiongen/templates/eventfile.tmpl b/internal/cmd/editiongen/templates/eventfile.tmpl index 51c9380..9081abd 100644 --- a/internal/cmd/editiongen/templates/eventfile.tmpl +++ b/internal/cmd/editiongen/templates/eventfile.tmpl @@ -30,6 +30,11 @@ type {{.StructName}} = eiffeleventsroot.{{.VersionedStructName}} // version {{.Version}} of {{.EventType}}. The returned struct has // all required meta members populated. func New{{.StructName}}(modifiers ...eiffeleventsroot.Modifier) (*{{.StructName}}, error) { +{{- /* For 0.x.y events there's one type per version so no need to set the version explicitly with a modifier. */ -}} +{{if eq .Version.Major 0}} + return eiffeleventsroot.New{{.VersionedStructName}}(modifiers) +{{- else -}} return eiffeleventsroot.New{{.VersionedStructName}}(append(modifiers, eiffeleventsroot.WithVersion({{printf "%q" .Version}}))...) +{{- end }} } {{end}} diff --git a/internal/cmd/eventgen/eventtypes.go b/internal/cmd/eventgen/eventtypes.go index ce9767c..2a8e763 100644 --- a/internal/cmd/eventgen/eventtypes.go +++ b/internal/cmd/eventgen/eventtypes.go @@ -22,6 +22,8 @@ import ( "fmt" "os" "path/filepath" + "strconv" + "strings" jsschema "github.com/lestrrat-go/jsschema" "gopkg.in/yaml.v3" @@ -63,7 +65,7 @@ func (t *goInterface) String() string { // those structs) for the latest version within each major version of each type. func generateTypes(schemaDefs map[string][]schemaDefinitionRenderer, outputDir string) error { for _, defs := range schemaDefs { - for majorVersion, schema := range latestMajorVersions(defs) { + for significantVersion, schema := range significantVersions(defs) { schemaFile, err := os.Open(schema.Filename()) if err != nil { return err @@ -80,7 +82,7 @@ func generateTypes(schemaDefs map[string][]schemaDefinitionRenderer, outputDir s return err } - outputFile := filepath.Join(outputDir, fmt.Sprintf("%sV%d.go", schema.TypeName(), majorVersion)) + outputFile := filepath.Join(outputDir, fmt.Sprintf("%sV%s.go", schema.TypeName(), strings.ReplaceAll(significantVersion, ".", "_"))) if err = schema.Render(&jsonDef, outputFile); err != nil { return err } @@ -89,17 +91,30 @@ func generateTypes(schemaDefs map[string][]schemaDefinitionRenderer, outputDir s return nil } -// latestMajorVersions inspects a list of schema definitions for a single type and -// maps each encountered major version to the schemaDefinitionRenderer that -// represents the most recent minor.patch version within that major version. -func latestMajorVersions(schemas []schemaDefinitionRenderer) map[int64]schemaDefinitionRenderer { - majorVersions := map[int64]schemaDefinitionRenderer{} +// significantVersions inspects a list of schema definitions for a single type and maps the type +// versions to generate to the corresponding schemaDefinitionRenderers. For non-experimental +// event versions, the type version will be the major version of the most recent major.minor.patch +// version within each major version will be returned, while all experimental event versions will +// be returned with the type version set to the full major.minor.patch version. +// +// For example, given the versions (0.1.0, 0.2.0, 1.0.0, 1.1.0, 2.0.0) a map with the keys +// (0.1.0, 0.2.0, 1, 2) will be returned. +func significantVersions(schemas []schemaDefinitionRenderer) map[string]schemaDefinitionRenderer { + versions := map[string]schemaDefinitionRenderer{} for _, schema := range schemas { - if current, exists := majorVersions[schema.Version().Major()]; !exists || current.Version().LessThan(schema.Version()) { - majorVersions[schema.Version().Major()] = schema + // Keep all schemas for experimental versions. + if schema.Version().Major() == 0 { + versions[schema.Version().String()] = schema + continue + } + + // Only keep the latest non-experimental version. + majorStr := strconv.Itoa(int(schema.Version().Major())) + if current, exists := versions[majorStr]; !exists || current.Version().LessThan(schema.Version()) { + versions[majorStr] = schema } } - return majorVersions + return versions } // goTypeFromSchema returns a goType that represents a node in a JSON schema. diff --git a/internal/cmd/eventgen/eventtypes_test.go b/internal/cmd/eventgen/eventtypes_test.go index f4dfa63..8be266d 100644 --- a/internal/cmd/eventgen/eventtypes_test.go +++ b/internal/cmd/eventgen/eventtypes_test.go @@ -27,12 +27,12 @@ func TestLatestMajorVersions(t *testing.T) { testcases := []struct { name string input []schemaDefinitionRenderer - expected map[int64]schemaDefinitionRenderer + expected map[string]schemaDefinitionRenderer }{ { name: "Empty input map", input: []schemaDefinitionRenderer{}, - expected: map[int64]schemaDefinitionRenderer{}, + expected: map[string]schemaDefinitionRenderer{}, }, { name: "Multiple major versions", @@ -59,25 +59,44 @@ func TestLatestMajorVersions(t *testing.T) { definitionFile: definitionFile{"/path/to/EiffelActivityStartedEvent/4.2.0.json", "EiffelActivityStartedEvent", semver.MustParse("4.2.0")}, }, }, - expected: map[int64]schemaDefinitionRenderer{ - 1: &eventDefinitionFile{ + expected: map[string]schemaDefinitionRenderer{ + "1": &eventDefinitionFile{ definitionFile: definitionFile{"/path/to/EiffelActivityStartedEvent/1.1.0.json", "EiffelActivityStartedEvent", semver.MustParse("1.1.0")}, }, - 2: &eventDefinitionFile{ + "2": &eventDefinitionFile{ definitionFile: definitionFile{"/path/to/EiffelActivityStartedEvent/2.0.0.json", "EiffelActivityStartedEvent", semver.MustParse("2.0.0")}, }, - 3: &eventDefinitionFile{ + "3": &eventDefinitionFile{ definitionFile: definitionFile{"/path/to/EiffelActivityStartedEvent/3.0.0.json", "EiffelActivityStartedEvent", semver.MustParse("3.0.0")}, }, - 4: &eventDefinitionFile{ + "4": &eventDefinitionFile{ definitionFile: definitionFile{"/path/to/EiffelActivityStartedEvent/4.2.0.json", "EiffelActivityStartedEvent", semver.MustParse("4.2.0")}, }, }, }, + { + name: "Experimental versions", + input: []schemaDefinitionRenderer{ + &eventDefinitionFile{ + definitionFile: definitionFile{"/path/to/EiffelArtifactDeployedEvent/0.1.0.json", "EiffelArtifactDeployedEvent", semver.MustParse("0.1.0")}, + }, + &eventDefinitionFile{ + definitionFile: definitionFile{"/path/to/EiffelArtifactDeployedEvent/0.2.0.json", "EiffelArtifactDeployedEvent", semver.MustParse("0.2.0")}, + }, + }, + expected: map[string]schemaDefinitionRenderer{ + "0.1.0": &eventDefinitionFile{ + definitionFile: definitionFile{"/path/to/EiffelArtifactDeployedEvent/0.1.0.json", "EiffelArtifactDeployedEvent", semver.MustParse("0.1.0")}, + }, + "0.2.0": &eventDefinitionFile{ + definitionFile: definitionFile{"/path/to/EiffelArtifactDeployedEvent/0.2.0.json", "EiffelArtifactDeployedEvent", semver.MustParse("0.2.0")}, + }, + }, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - actual := latestMajorVersions(tc.input) + actual := significantVersions(tc.input) assert.Equal(t, tc.expected, actual) }) } diff --git a/internal/cmd/eventgen/eventtypetable.go b/internal/cmd/eventgen/eventtypetable.go index 4e345ce..7dd507a 100644 --- a/internal/cmd/eventgen/eventtypetable.go +++ b/internal/cmd/eventgen/eventtypetable.go @@ -21,6 +21,7 @@ import ( "strings" "text/template" + "github.com/Masterminds/semver" "github.com/eiffel-community/eiffelevents-sdk-go" "github.com/eiffel-community/eiffelevents-sdk-go/internal/codetemplate" ) @@ -28,6 +29,7 @@ import ( type MajorEventVersion struct { StructName string LatestVersion string + latest *semver.Version } //go:embed templates/eventtypetable.tmpl @@ -39,17 +41,25 @@ var eventTableFileTemplate string func generateEventTypeTable(schemas map[string][]schemaDefinitionRenderer, outputFile string) error { table := make(map[string]map[int]MajorEventVersion) for _, eventSchemas := range schemas { - latestVersions := latestMajorVersions(eventSchemas) - for majorVersion, schema := range latestVersions { + latestVersions := significantVersions(eventSchemas) + for _, schema := range latestVersions { if !strings.HasSuffix(schema.TypeName(), "Event") { continue } if table[schema.TypeName()] == nil { table[schema.TypeName()] = make(map[int]MajorEventVersion) } - table[schema.TypeName()][int(majorVersion)] = MajorEventVersion{ - eiffelevents.VersionedStructName(schema.TypeName(), schema.Version()), - schema.Version().String(), + + // For non-experimental event versions this conditional is unnecessary since significantVersions() + // has already weeded out non-latest versions, but for experimental versions we need to make sure + // we fill the table with the latest one (and the latest one only). + major := int(schema.Version().Major()) + if current, exists := table[schema.TypeName()][major]; !exists || current.latest.LessThan(schema.Version()) { + table[schema.TypeName()][major] = MajorEventVersion{ + eiffelevents.VersionedStructName(schema.TypeName(), schema.Version()), + schema.Version().String(), + schema.Version(), + } } } } diff --git a/internal/cmd/eventgen/schemadefs.go b/internal/cmd/eventgen/schemadefs.go index 8ef3ab5..dc4e402 100644 --- a/internal/cmd/eventgen/schemadefs.go +++ b/internal/cmd/eventgen/schemadefs.go @@ -20,6 +20,7 @@ import ( _ "embed" "fmt" "io" + "strings" "text/template" "github.com/Masterminds/semver" @@ -77,6 +78,7 @@ var eventTypeAbbrevMap = map[string]string{ "EiffelActivityTriggeredEvent": "ActT", "EiffelAnnouncementPublishedEvent": "AnnP", "EiffelArtifactCreatedEvent": "ArtC", + "EiffelArtifactDeployedEvent": "ArtD", "EiffelArtifactPublishedEvent": "ArtP", "EiffelArtifactReusedEvent": "ArtR", "EiffelCompositionDefinedEvent": "CD", @@ -116,18 +118,24 @@ func (edf *eventDefinitionFile) Render(schema io.Reader, outputFile string) erro // Gather some metadata about the event type. This struct is later // supplied to the template that generates the event source file. + var subTypeNamePrefix string + if edf.version.Major() == 0 { + subTypeNamePrefix = fmt.Sprintf("%sV%s", eventTypeAbbrev, strings.ReplaceAll(edf.version.String(), ".", "_")) + } else { + subTypeNamePrefix = fmt.Sprintf("%sV%d", eventTypeAbbrev, edf.version.Major()) + } eventMeta := struct { - EventType string // The name of the event type, e.g. EiffelActivityTriggeredEvent. - EventTypeAbbrev string // The abbreviated event type name, e.g. ActT. - StructName string // The name of the struct that represents the event type. - SubTypeNamePrefix string // The prefix that any subtypes of the event type struct gets to their names. - MajorVersion int64 // The event type's major version. + EventType string // The name of the event type, e.g. EiffelActivityTriggeredEvent. + EventTypeAbbrev string // The abbreviated event type name, e.g. ActT. + StructName string // The name of the struct that represents the event type. + SubTypeNamePrefix string // The prefix that any subtypes of the event type struct gets to their names. + Version *semver.Version // The event version. }{ EventType: edf.typeName, EventTypeAbbrev: eventTypeAbbrev, StructName: eiffelevents.VersionedStructName(edf.typeName, edf.version), - SubTypeNamePrefix: fmt.Sprintf("%sV%d", eventTypeAbbrev, edf.version.Major()), - MajorVersion: edf.version.Major(), + SubTypeNamePrefix: subTypeNamePrefix, + Version: edf.version, } rootStruct, err := newEventStruct(eventMeta.SubTypeNamePrefix, eventMeta.StructName, s) diff --git a/internal/cmd/eventgen/templates/eventfile.tmpl b/internal/cmd/eventgen/templates/eventfile.tmpl index 0d08c55..4e3372d 100644 --- a/internal/cmd/eventgen/templates/eventfile.tmpl +++ b/internal/cmd/eventgen/templates/eventfile.tmpl @@ -27,16 +27,22 @@ import ( "github.com/google/uuid" ) +{{if eq .Version.Major 0}} +// New{{.StructName}} creates a new struct pointer that represents version +// {{.Version}} of {{.EventType}}. The returned struct has all required +// meta members populated. +{{- else}} // New{{.StructName}} creates a new struct pointer that represents -// major version {{.MajorVersion}} of {{.EventType}}. +// major version {{.Version.Major}} of {{.EventType}}. // The returned struct has all required meta members populated. -// The event version is set to the most recent {{.MajorVersion}}.x.x +// The event version is set to the most recent {{.Version.Major}}.x.x // currently known by this SDK. +{{- end }} func New{{.StructName}}(modifiers ...Modifier) (*{{.StructName}}, error) { var event {{.StructName}} event.Meta.Type = {{ printf "%q" .EventType }} event.Meta.ID = uuid.NewString() - event.Meta.Version = eventTypeTable[event.Meta.Type][{{.MajorVersion}}].latestVersion + event.Meta.Version = eventTypeTable[event.Meta.Type][{{.Version.Major}}].latestVersion event.Meta.Time = time.Now().UnixMilli() for _, modifier := range modifiers { if err := modifier(&event); err != nil { diff --git a/naming.go b/naming.go index 3fbda11..380fde1 100644 --- a/naming.go +++ b/naming.go @@ -47,5 +47,8 @@ func StructName(eventType string, eventVersion *semver.Version) string { // VersionedStructName returns the name of the Go struct used to represent // a particular version of a type. func VersionedStructName(eventType string, eventVersion *semver.Version) string { + if eventVersion.Major() == 0 { + return fmt.Sprintf("%sV%s", StructName(eventType, eventVersion), strings.ReplaceAll(eventVersion.String(), ".", "_")) + } return fmt.Sprintf("%sV%d", StructName(eventType, eventVersion), eventVersion.Major()) } diff --git a/protocol b/protocol index 21148da..6ee6e4f 160000 --- a/protocol +++ b/protocol @@ -1 +1 @@ -Subproject commit 21148da04579dd9dce6266c5586b5e834d72e839 +Subproject commit 6ee6e4fcfef701dd68bb9cc7265138739307179a