grafana / alloy

OpenTelemetry Collector distribution with programmable pipelines
https://grafana.com/oss/alloy
Apache License 2.0
1.31k stars 180 forks source link

syntax: require all capsule values to implement marker interface #159

Open rfratto opened 1 year ago

rfratto commented 1 year ago

Today, a Go value maps to a Alloy syntax capsule when any of the following are true:

  1. The Go type implements the capsule marker interface
  2. It is a Go interface type but not interface{}
  3. It is a Go map which is not keyed by string
  4. It does not map to a Alloy null, number, string, bool, object, or function.

These rules were intended to make capsules feel "magical," have historically caused bugs (#2361, grafana/agent#2362) and are generally confusing to remember (I even had to look up the rules again just when writing this). The biggest issue with capsules is that capsule information can get lost if a type is casted to an interface{}, which can happen in many situations, including storing a capsule value in a *vm.Scope as a variable.

About a month ago I suggested changing these rules, and this issue is a more formal proposal of that suggestion.

Proposal

I propose that:

  1. Go types only map to an Alloy capsule if they implement the capsule marker interface
  2. We introduce a high-level API to encapsulate any Go value
  3. Go types which do not map to any Alloy type now cause an encoding error (rather than defaulting to a capsule type)

This proposal only affects developers building components on top of Alloy. Users of Alloy are not impacted.

The new API looks like this:

package syntax

// Capsule wraps around a value and marks it as an Alloy capsule, 
// allowing arbitrary Go types to be passed around in Alloy. 
//
// A manual capsule type can be created by implementing the 
// non-pointer receiver AlloyCapsule method on a type.    
type Capsule[T any] struct {
  val T 
} 

// Encapsulate creates a new Capsule[T] for a value.
func Encapsulate[T any](v T) Capsule[T] {
  return Capsule[T]{val: v} 
} 

// NewCapsule creates a new *Capsule[T] for a value. This is 
// useful when capsules values are optional. 
func NewCapsule[T any](v T) *Capsule[T] {
  return &Capsule[T]{val: v} 
}

// AlloyCapsule marks Capsule[T] as a capsule type. 
func (c Capsule[T]) AlloyCapsule() {} 

// Get returns the underlying value for the Capsule. If 
// the Capsule is nil, Get returns the zero value for the 
// underlying type. 
func (c *Capsule[T]) Get() T {
  if c == nil {
    var zero T 
    return zero 
  }
  return c.val 
}

Example usage

Using the new proposed API above, the exports for prometheus.remote_write change to:

package remotewrite 

... 

type Exports struct {
  Receiver syntax.Capsule[storage.Appendable] `alloy:"receiver,attr"`
}

The arguments type for types which accept capsules would also change:

package scrape // prometheus.scrape 

... 

type Arguments struct {
  ForwardTo []syntax.Capsule[storage.Appendable] `alloy:"forward_to,attr"`

  ...
}

Discussion

This proposal does come at a cost: developing against Alloy becomes slightly more tedious for capsule values, as it removes the magic introduced in the initial implementation.

However, the new rules are much easier to remember, and the new API should offload most of the tedium introduced by the new rules. This proposal even potentially makes Alloy easier to understand for developers reading the code, as it now must be explicit when something is being encapsulated.

github-actions[bot] commented 4 months ago

This issue has not had any activity in the past 30 days, so the needs-attention label has been added to it. If the opened issue is a bug, check to see if a newer release fixed your issue. If it is no longer relevant, please feel free to close this issue. The needs-attention label signals to maintainers that something has fallen through the cracks. No action is needed by you; your issue will be kept open and you do not have to respond to this comment. The label will be removed the next time this job runs if there is new activity. Thank you for your contributions!