Giter Club home page Giter Club logo

eiffelevents-sdk-go's Introduction

Eiffel Events SDK โ€“ Go

Go Reference Sandbox badge

Eiffel Events SDK โ€“ Go

This repository contains data types, constants, and functions for working with Eiffel events in the Go language, including marshaling to and from JSON. Its goal is to make it easy to create and process Eiffel events in Go.

The module declares a Go struct for every major version of each event type. These structs are generated from the JSON schemas and named as in the examples 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 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.

package main

import (
	"fmt"
	"time"

	"github.com/eiffel-community/eiffelevents-sdk-go"
)

func main() {
	// Manual initialization of all struct members.
	var event1 eiffelevents.CompositionDefinedV3
	event1.Meta.Type = "EiffelCompositionDefinedEvent"
	event1.Meta.Version = "3.2.0"
	event1.Meta.ID = "87dac043-2e1b-41c5-833a-712833f2a613"
	event1.Meta.Time = time.Now().UnixMilli()
	event1.Data.Name = "my-composition"
	fmt.Println(event1.String())

	// Equivalent example using the factory that pre-populates all
	// required meta members (picking the most recent event version in
	// the chosen major version). Note that the factory returns
	// a struct pointer.
	event2, err := eiffelevents.NewCompositionDefinedV3()
	if err != nil {
		panic(err)
	}
	event2.Data.Name = "my-composition"
	fmt.Println(event2.String())
}

The example below shows how modifier functions can be passed to factories to populate the newly created events with additional fields. In trivial cases modifiers are superfluous and the caller can just set the desired fields after obtaining the event from the factory, but apart from being a compact representation modifiers can be used with any event type. You can also use them to create custom factories that apply a preconfigured set of modifiers.

package main

import (
	"fmt"

	"github.com/eiffel-community/eiffelevents-sdk-go"
)

func main() {
	// Create an event with modifiers that select a particular
	// version of the event and makes sure meta.source.host is
	// populated with the name of the current host.
	event1, err := eiffelevents.NewCompositionDefinedV3(
		eiffelevents.WithVersion("3.1.0"),
		eiffelevents.WithAutoSourceHost(),
	)
	if err != nil {
		panic(err)
	}
	event1.Data.Name = "my-composition"
	fmt.Println(event1.String())

	// Create a custom factory with the chosen modifiers.
	newComposition := func() (*eiffelevents.CompositionDefinedV3, error) {
		return eiffelevents.NewCompositionDefinedV3(
			eiffelevents.WithVersion("3.1.0"),
			eiffelevents.WithAutoSourceHost(),
		)
	}

	// Create a new event using the custom factory.
	event2, err := newComposition()
	if err != nil {
		panic(err)
	}
	event2.Data.Name = "my-composition"
	fmt.Println(event2.String())
}

Preferring events from a particular Eiffel edition

Each Eiffel edition has a subpackage containing version-less struct type aliases and factories for creating events with the correct version for the chosen edition. This removes much of the need to litter the code with "V3" etc suffixes.

package main

import (
	"fmt"

	"github.com/eiffel-community/eiffelevents-sdk-go/editions/lyon"
)

func main() {
	event, err := eiffelevents.NewCompositionDefined()
	if err != nil {
		panic(err)
	}
	// The event struct has the type eiffelevents.CompositionDefined,
	// which is an alias for the parent package's CompositionDefinedV3.
	// The event version is set to 3.2.0. By instead importing the paris
	// subpackage the event version would've been set to 3.1.0.

	event.Data.Name = "my-composition"
	fmt.Println(event.String())
}

Unmarshaling event JSON strings into Go structs

To unmarshal a JSON string into one of the structs defined in this package use the UnmarshalAny function and use e.g. a type switch to access the event members:

package main

import (
	"fmt"
	"io"
	"os"

	"github.com/eiffel-community/eiffelevents-sdk-go"
)

func main() {
	input, err := io.ReadAll(os.Stdin)
	if err != nil {
		panic(err)
	}

	anyEvent, err := eiffelevents.UnmarshalAny(input)
	if err != nil {
		panic(err)
	}

	switch event := anyEvent.(type) {
	case *eiffelevents.CompositionDefinedV3:
		fmt.Printf("Received %s composition\n", event.Data.Name)
	default:
		fmt.Printf("This event I don't know much about: %s\n", event)
	}
}

If you have a compound JSON structure containing e.g. an array of event objects you can declare its type to be []*eiffelevents.Any. After unmarshaling the data you can use a type switch to process the events:

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"os"

	"github.com/eiffel-community/eiffelevents-sdk-go"
)

func main() {
	input, err := io.ReadAll(os.Stdin)
	if err != nil {
		panic(err)
	}

	var apiResponse struct {
		Events []*eiffelevents.Any `json:"events"`
	}

	err = json.Unmarshal(input, &apiResponse)
	if err != nil {
		panic(err)
	}

	for _, anyEvent := range apiResponse.Events {
		switch event := anyEvent.Get().(type) {
		case *eiffelevents.CompositionDefinedV3:
			fmt.Printf("Received %s composition\n", event.Data.Name)
		default:
			fmt.Printf("This event I don't know much about: %s\n", event)
		}
	}
}

Validating events

Eiffel events are defined by their schemas, and publishers are expected to send events that conform to those schemas. The validator subpackage can assist with that task as well as other user-defined validation tasks. Validation is done via a validator.Set instance, where one or more implementations of validator.Validator inspect an event in the configured order. To ease the configuration burden, validator.DefaultSet returns a reasonably configured validator.Set instance that's ready to be used. See the documentation of the validator subpackage for details.

Code of Conduct and Contributing

To get involved, please see Code of Conduct and contribution guidelines.

Note that these files are located in the .github repository. See this page for further details regarding default community health files.

About this repository

The contents of this repository are licensed under the Apache License 2.0.

About Eiffel

This repository forms part of the Eiffel Community. Eiffel is a protocol for technology agnostic machine-to-machine communication in continuous integration and delivery pipelines, aimed at securing scalability, flexibility and traceability. Eiffel is based on the concept of decentralized real time messaging, both to drive the continuous integration and delivery system and to document it.

Visit Eiffel Community to get started and get involved.

eiffelevents-sdk-go's People

Contributors

dependabot[bot] avatar jonasalfredsson avatar magnusbaeck avatar

Stargazers

 avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

eiffelevents-sdk-go's Issues

Consider making the meta.time field based on time.Time

Description

The current data type used to represent the meta.time field is a plain integer. Someone who wants to e.g. compute the age of an event needs to apply some conversions to turn it into a time.Time value that can be used for date math (or just passing the untouched value to a function that accepts standard time.Time values).

What makes this a bit tricky is that time.Time isn't serialized as an integer but as an RFC 3339 string. The only realistic option is probably to create a new data type for which we could implement MarshalJSON and UnmarshalJSON, but then we won't be able to use the resulting meta.time values straight away anyway. Not sure what to do about this.

Motivation

Making it easier to work with the meta.time value.

Exemplification

Would simplify computation of the age of an event.

Benefits

Again, would make it easier to work with meta.time values without having to perform additional conversions.

Possible Drawbacks

None in particular.

Makefile should verify downloaded golangci-lint executables

Description

Right now the makefile blindly downloads and executes the golangci-lint installation script and accepts whatever it downloads as the linter to use. We should store the signatures of both files in the makefile and verify that they match.

Motivation

An attacker could compromise either file so that whoever builds the SDK runs arbitrary code.

Exemplification

N/A

Benefits

One security vulnerability down, N to go.

Possible Drawbacks

None.

Add meta.security support

Description

There should be functions or event struct methods for

Motivation

It's a reasonable expectation that an SDK should provide means for producing and validating signed events.

Exemplification

N/A

Benefits

Obvious.

Possible Drawbacks

None.

Add support for experimental event types

Description

The protocol repo will add support for experimental event types, signified with a pre-1.0 version (see eiffel-community/eiffel#343). These will need special treatment since backwards incompatible changes may occur at any time, so we'll have to generate one type per pre-1.0 event version.

Motivation

The points of having experimental event types is to allow experimentation and iterative development. This ability is limited if you can't use this SDK when processing such events in Go code.

Exemplification

The upcoming EiffelArtifactDeployedEvent (see eiffel-community/eiffel#322) will start its life as experimental, and we obviously want to support its use in any way we can.

Benefits

Equally easy to work with experimental events than any other events.

Possible Drawbacks

None apart from more complexity in the code generation.

Use new event_manifest.yml file to generate edition files

Description

Now that eiffel-community/eiffel#353 has been merged, the protocol repository contains a YAML file that describes the available editions and what event types and versions exist in that edition. We can use that to simplify
internal/cmd/editiongen/main.go.

Motivation

Less code to maintain.

Exemplification

N/A

Benefits

The end result should be identical to what we have now so in that sense it won't make a difference, but if we can do the same thing with less code that's a good thing.

Possible Drawbacks

We'll introduce a dependency to a new metadata file in the protocol git. On the other hand we get rid of a dependency to the protocol git's directory layout (and that it needs to be a Git repository).

Configure Dependabot

Description

We should configure Dependabot for automatic PR creation.

Motivation

We should be made aware of security issues in our dependencies and it should be as simple as possible to address any such issues.

Exemplification

N/A

Benefits

Decreased risk for our dependents to get in trouble because of security problems in what we drag in for them.

Possible Drawbacks

It might get a bit spammy, but we don't have that many dependencies so I'm not worried.

Use new schema definition files to avoid duplicating types for e.g. meta

Description

Now that eiffel-community/eiffel#315 has been merged we should use the new schema definition files to stop generating distinct but not unique types for link objects and meta. Those are now defined in subschemas with the same kind of semantic versioning as the events so we can have a "global" MetaV1 type instead of one per major version of each event type.

Motivation

This change would allow us to generate fewer types (which helps with the current cluttering) but also make them interchangeable, i.e. a function can act on any link object. We've partially solved this with various interface types but that's not always a realistic option.

Exemplification

Instead of

type ActivityCanceledV1 struct {
	Data  ActCV1Data  `json:"data"`
	Links ActCV1Links `json:"links"`
	Meta  ActCV1Meta  `json:"meta"`
}
type ActCV1Links []ActCV1Link
type ActCV1Link struct { ... }

we get this:

type ActivityCanceledV1 struct {
	Data  ActCV1Data `json:"data"`
	Links LinksV1    `json:"links"`
	Meta  MetaV1     `json:"meta"`
}
type LinksV1 []LinkV1
type LinkV1 struct { ... }

Benefits

See motivation.

Possible Drawbacks

None.

Make it easy to unmarshal compound JSON structures containing Eiffel events

Description

The eiffelevents.UnmarhalAny function makes it easy to unmarshal a byte slice into an interface{} containing one of the Eiffel structs defined in the package, but this is only useful if the byte slice only contains an Eiffel event.

Sometimes you want to unmarshal byte slices from e.g. API responses where you e.g. have a slice of Eiffel event objects. Let's introduce a simple type that implements UnmarshalJSON by calling UnmarshalAny and stores the result in a struct member that we can access through a method. Something like this should work:

type AnyEvent struct {
        event interface{}
}

func (ae *AnyEvent) Get() interface{} {
        return ae.event
}

func (ae *AnyEvent) UnmarshalJSON(b []byte) error {
        var err error
        ae.event, err = UnmarshalAny(b)
        return err
}

Motivation

It should be easy to deserialize Eiffel events even when they're part of larger JSON structures.

Exemplification

The code above should allow us to write at struct like

type APIResponse struct {
        Results []*eiffelevents.AnyEvent `json:"results"`
}

and use it to unmarshal an API response that includes a single key containing a slice of events.

Benefits

Easier deserialization of events into workable structs.

Possible Drawbacks

None.

Generate table for roundtrip tests

Description

TestRoundtrip in events_test.go relies on the table in events_test_roundtripdata.go that maps example files from the protocol repo to the struct type the should be used to represent the event (based on the declared event type and version in the file). This file currently needs to be maintained by hand as the available examples change (which doesn't happen very often). To avoid this we should generate the file.

Motivation

Less manual work when stepping the protocol repository.

Exemplification

N/A

Benefits

Less manual work when stepping the protocol repository.

Possible Drawbacks

Slightly more code to maintain.

Remove dependency to github.com/go-git/go-git

Description

We should remove the generation-time dependency on github.com/go-git/go-git and run plain Git commands instead.

Motivation

The dependency drags in a bunch of indirect dependencies that litter go.mod of our dependent modules. The idea of using a library for Git interactions isn't bad, but in this case it's just unnecessary. We only need a single git ls-tree invocation to mimic what we're currently doing.

Exemplification

See eiffel-community/eiffel-goer#32 for an example of what happens to go.mod when you depend on this module. Not all of those indirect dependencies are because of go-git but quite a few of them.

Benefits

Fewer Go module dependencies.

Possible Drawbacks

None. One would think that using a library instead of running commands would make for less code but I actually don't think that's the case. The SDK generation process gains a dependency to the Git binaries but that's not a problem (in practice that dependency already exists).

Generate factory function for each type

Description

When #2 gets merged we'll have basic functionality in place but creating events from scratch will be rather cumbersome since there aren't any factory functions that populate primarily meta fields with reasonable defaults. Quoting the README.md example:

var event eiffelevents.CompositionDefinedV3
event.Meta.Type = "EiffelCompositionDefinedEvent"
event.Meta.Version = "3.2.0"
event.Meta.ID = "87dac043-2e1b-41c5-833a-712833f2a613"
event.Meta.Time = time.Now().UnixMilli()

To solve this we should generate factory functions with the following example signature:

func NewCompositionDefinedV3(modifiers ...EventModifier) (*CompositionDefinedV3, error)

The EventModifier type would be a function type whose implementations could make various transforms of the newly created event. Unfortunately it's a bit icky to write such functions that work on all events since there currently aren't any common types, i.e. every single event type (and major version) has its own struct for the meta field, but maybe we can use reflection and define an interface like

type FieldSetter interface {
	SetIntField(field string, value int) error
	SetStringField(field string, value string) error
	...
}

that all struct types can implement? This is what this could look like from the client side to explicitly select a particular event version (if you don't want the latest v3.x.y):

event := eiffelevents.NewCompositionDefinedV3(eventmodifier.Version("3.1.1"))

Other useful EventModifier implementations includes those that modify meta.source.serializer, meta.source.name, meta.source.host, and meta.source.domainId.

We could also consider supporting factory factory functions, i.e. a function that returns a function that creates an event. This would be useful to avoid repeating the same set of factory configurations.

cdFactory := eiffelevents.NewCompositionDefinedV3Factory(eventmodifier.Version("3.1.1"), eventmodifier.DomainID(...))
...
event1 := cdFactory()
event2 := cdFactory()
...
eventN := cdFactory()

Motivation

This'll make it much easier for programs that create events to construct valid event structs. It'll reduce the amount of boilerplate code and the risk of bugs.

Exemplification

N/A

Benefits

See above.

Possible Drawbacks

Nothing besides more code and complexity.

Add validation against schemas

Description

There should be a function or event struct method for validating events against the schemas.

Motivation

It should be easy to produce valid events.

Exemplification

N/A

Benefits

Easier event validation.

Possible Drawbacks

None.

Generate subpackage for each Eiffel edition

Description

It should be easy to create events from a particular Eiffel edition, but as it stands you have to keep track of exactly which version of each event you should use if you want to stick to, say, the Lyon edition. This affects both what meta.version to put in the event but also which versioned struct type to use in the first place.

To improve this we should generate a subpackage for each Eiffel edition. The package name can still be eiffelevents but the package's import path can be github.com/eiffel-community/eiffelevents-sdk-go/lyon or perhaps github.com/eiffel-community/eiffelevents-sdk-go/editions/lyon. Each such package will define type aliases like

type CompositionDefined = rooteiffelevents.CompositionDefinedV3

(where rooteiffelevents is the imported name of the root package) so that clients can import the edition-specific package and reference structs without a version suffix, and have those structs interoperable with structs that were specified with an exact major version. Each package will also contain a factory function that returns a struct with the correct meta.version for that edition:

event := eiffelevents.NewCompositionDefined()

Motivation

This change would make it much easier to produce events from a particular edition of the Eiffel protocol. It would also make it fairly painless to upgrade from one edition to the next (as painless as such an option can be with potentially backwards incompatible changes).

Exemplification

Right now an SDK client must hardcode the desired version of an event every time it's initialized. That requirement would be removed.

Benefits

See above.

Possible Drawbacks

None.

Add function for generating Sepia routing key

Description

There should be a function for generating a RabbitMQ routing key that conforms to the Sepia standard, perhaps something like this:

func SepiaRoutingKey(event MetaTeller, family string, tag string) string

Motivation

While Sepia isn't a part of Eiffel and we generally don't suggest including broker-specific code in the SDKs, a simple function like above doesn't warrant a separate repository. We could place it in a subpackage though, e.g. github.com/eiffel-community/eiffelevents-sdk-go/routingkeys, if we want to make some kind of separation.

While the function would be simple (probably not much more than a fmt.Sprintf call and a couple of conditionals) it shouldn't have to be copied around between every single publisher.

Exemplification

Anyone using the SDK to publish messages to RabbitMQ should use a Sepia routing key.

Benefits

It'll be easier for SDK clients to publish messages with the right routing key, and that's something we want to encourage.

Possible Drawbacks

None, except maybe violating the policy (or opinion?) that SDKs should be entirely broker-agnostic.

Add Go Doc comments for structs and their fields

Description

Now that we have the event documentation in structured form rather than just a bunch of Markdown files we should include this documentation in the generated code.

Motivation

If the package documentation includes event documentation you can almost use it as the sole documentation source when writing code that interprets or creates Eiffel events.

Exemplification

N/A

Benefits

Possible Drawbacks

None, except obviously more complexity in the code generation.

Use os.ReadFile whenever possible

Description

In a couple of places the existence of os.ReadFile had been forgotten and we're doing the whole

f, err := os.Open(tc.Filename)
if err != nil { ... }
defer f.Close()         
input, err := io.ReadAll(f)
if err != nil { ... }

dance. Let's clean this up.

Motivation

There's no reason to use this more verbose form.

Exemplification

N/A

Benefits

Less verbose code.

Possible Drawbacks

None.

Add modifier that populates meta.source.serializer with golang purl

Description

It should be easy to populate meta.source.serializer with a meaningful value. The spec isn't entirely clear on whether that field is for the program that produces the event or the software component (like this SDK) actually doing the serialization of the event, but arguably the most useful interpretation is that it's the program.

Ideally I think the serializer should be set to a reference to the compiled binary or equivalent (e.g. a Docker image) but figuring out that from the program itself isn't trivial. Instead let's provide an option for at least populating the value with a purl that represents the main Go package. Example:

bi, ok := debug.ReadBuildInfo()
if !ok {
	panic("no build info")
}
purl := packageurl.NewPackageURL("golang", path.Dir(bi.Path), path.Base(bi.Path), "1.0", packageurl.Qualifiers{}, "")

Motivation

When debugging it's very useful to get a proper specification of who actually sent an event. Even if I know what program it is and where I can find it the exact version can still be crucial.

Exemplification

N/A

Benefits

Make it easier to write event producers with useful meta.source.serializer values.

Possible Drawbacks

None. The modifier will, of course, be optional.

Make sure all packages have a godoc comment

Description

Not all packages have a description, i.e. a "Package foo does this and that" godoc comment, but they should. Notably, the edition packages (editions/arica) are missing it but there might be others.

Motivation

A package description serves as a good introduction to the package. In the edition package case we could explain the purpose of the package and how it should be used as well as include a table with the (event type, version) pairs of the edition.

Exemplification

N/A

Benefits

See above.

Possible Drawbacks

None.

Define types for link slices

Description

The Links field for all the event types is currently defined as a slice of XXXLink structs, e.g. like this:

type ActivityCanceledV3 struct {
	...        
	Links []ActCV3Link `json:"links"`
	...        
}

Nothing wrong with that, but it makes it a bit verbose to add links to an event:

event.Links = append(event.Links, ActCV3Link{"CONTEXT", otherEvent.Meta.ID})

We can improve this by defining a type for this and adding methods to that type:

type ActivityCanceledV3 struct {
	...        
	Links ActCV3Links `json:"links"`
	...        
}

type ActCV3Links []ActCV3Link

func (links ActCV3Links) Add(linkType string, target MetaGetter) { ... }
func (links ActCV3Links) AddByID(linkType string, target string) { ... }

This'll require that all event types implement a MetaGetter interface defined like this:

type MetaGetter interface {
	ID() string
	Type() string
	Version() string
	DomainID() string
}

It's not the intention to have the MetaGetter type act as an entrypoint to all meta field but to those that we expected are stable over time and can be useful to be able to call on any event without having a concrete type. I foresee other uses for the MetaGetter interface as well, so it's not solely to enable the Add method.

Motivation

This'll make code that assembles events way nicer and it'll also enable validation of valid link types and target event types.

Exemplification

We'll turn

event.Links = append(event.Links, ActCV3Link{"CONTEXT", otherEvent.Meta.ID})

into this:

event.Links.Add("CONTEXT", otherEvent)

Note how the dependency to the event type's major version disappears. That'll make it easier to switch between Eiffel editions.

Benefits

See above.

Possible Drawbacks

None I can think of.

Update to the Orizaba edition

Description

The SDK should be updated to include the event versions introduced in the Orizaba edition as well as the edition itself.

Motivation

We should include cover all public event types and versions.

Exemplification

N/A

Benefits

SDK can be used to produce and consume the versions of events introduced in Orizaba.

Possible Drawbacks

None.

Add additional methods to links types

Description

When working with Eiffel events in a processing pipeline it's common to look for certain link types in the links of an event. "What ActT does this ActS point to", "what's the context of this ArtC", and so on. We should have a convenience method in for doing this. Examples:

var elements []string = event.Links.FindAll("ELEMENT")
var cause string = event.Links.FindFirst("CAUSE")

The implementation of FindAll and FindFirst would of course be very similar and the latter is strictly not necessary but, again, convenient. The zero value would be returned if no matching links were found.

Motivation

This'll make it more convenient to write code that processes events. Since all event types currently have distinct types for the links slices you can't write a local function that searches those slices, unless you happen to only work with a single event type. (That'll change when generics are introduced.)

Exemplification

Say you have an event processor that acts upon artifacts. The artifact identity (i.e. ArtC's data.identity) tells you whether the artifact is interesting to you, but you need the URL from the ArtP event. When an interesting ArtC arrives the event is saved, and when you process the ArtP you want to locate the ArtC it points to. This would be a nice way of doing that:

createdArtifact := event.Links.FindFirst("ARTIFACT")

It's of course not hard to do

var createdArtifact string
for _, link := range event.Links {
	if link.Type == "ARTIFACT" {
		createdArtifact = link.Target
	}
}

but it gets tedious after a while, and without generics it's hard to extract to a reusable function.

Benefits

See above.

Possible Drawbacks

None.

Make dependabot watch workflow files as well

Description

In issue #26 dependabot was added for the Go ecosystem, and it appears to have worked well.
It is possible to expand the configuration to also create updates for dependencies defined in the .github/workflows folder, so they are also up to date.

Motivation

With this we will be notified, and a pull request automatically created, every time there is an update to the dependencies.
It will remove the tedious task of manually checking for updates (and performing them), which is great.

Exemplification

After the introduction of #30 dependabot immediately created #31, #32 and #33.
Something similar will probably happen this time around as well.

Benefits

Using the most recent update is probably preferred so that security fixes are added as soon as they are available.

Possible Drawbacks

It could be that updates to dependencies introduce new bugs, and major version bumps may introduce breaking API changes.
However, I think it is better to run into such issues as soon as possible rather than a year down the line when we might have multiple dependencies do major version upgrades.

Explicit installation of GNU make in the GitHub Actions workflow is pointless

Description

The build-and-test.yml workflow unnecessarily installs GNU make even though it's already available thanks to the documented inclusion of the build-essential package for the Ubuntu virtual environment.

Motivation

This adds upwards of 10 s to the workflow execution. While the package installation itself is a no-op since the package is already installed the apt-get update operation takes time.

Exemplification

N/A

Benefits

Quicker workflow executions, less cruft in the workflow definition.

Possible Drawbacks

None.

Update to the Arica edition

Description

The SDK should be updated to include the event versions introduced in the Arica edition as well as the edition itself.

Motivation

We should include cover all public event types and versions.

Exemplification

N/A

Benefits

SDK can be used to produce and consume the versions of events introduced in Arica.

Possible Drawbacks

None.

Edition-specific constructors might mutate modifier slice

Description

The edition-specific constructors like

func NewTestExecutionRecipeCollectionCreated(modifiers ...eiffeleventsroot.Modifier) (*TestExecutionRecipeCollectionCreated, error) {
	return eiffeleventsroot.NewTestExecutionRecipeCollectionCreatedV4(append(modifiers, eiffeleventsroot.WithVersion("4.1.1"))...)
}

look suspicious to me since append() might modify its first argument, depending on the slice's capacity. I'm not sure this applies in this case because of the variadic argument, but if a slice is passed I wonder if it's passed on to append() or if a copy is made. Let's play this safe and make sure that we can't mutate the modifier slice by manually copying the elements into a new slice or passing a full slice expression to append() to force a copy.

Motivation

We clearly shouldn't modify any slice of modifiers that's passed to the constructors.

Exemplification

N/A

Benefits

Could potentially fix a hard to diagnose bug.

Possible Drawbacks

A slight performance degrade, but insignificant compared to the overall cost of publishing an event.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.