Skip to content

Latest commit

 

History

History
117 lines (89 loc) · 4.76 KB

marks.md

File metadata and controls

117 lines (89 loc) · 4.76 KB

Value Marks


Note: Marks are an optional feature that will not be needed in most applications. If your application never uses this API then you don't need to worry about encountering marked values, and you can ignore this document.


A cty.Value can optionally be marked, which causes it to carry around some additonal metadata along with its value. Marks are just normal Go values that are value to use as map keys, and are compared by equality.

For example, an application might use marks to track the origin of a particular value in order to give better error messages, or to present the value in a different way in a UI.

// Use a named type for all marks, for namespacing purposes.
type fromConfigType int
val fromConfig fromConfigType

return val.Mark(fromConfig)
if val.HasMark(fromConfig) {
    // Maybe warn the user that the value is derived from configuration?
    // Or whatever makes sense for the calling application.
}

When a value is marked, operation methods using that value will propagate the marks to any result values. That makes marks "infectious" in the sense that they propagate through operations and accumulate in the result automatically.

However, marks cannot propagate automatically thruogh integration methods, and so a calilng application is required to explicitly unmark a value before using them:

val, valMarks := val.Unmark()
// ...then use integration methods with val,
// eventually producing a result that propgates
// the marks:
return result.WithMarks(valMarks)

Marked Values in Sets

Sets present an interesting problem for marks because marks do not contribute to equality of two values and thus it would be possible in principle to add the same value to a given set twice with different marks.

To avoid the complexity of tracking superset marks on a per-element basis, cty instead makes a compromise: sets can never contain marked values, and if any marked values are passed to cty.SetVal then they will be automatically unmarked and the superset of all marks applied to the resulting set as a whole.

This is lossy about exactly which elements contributed which marks, but is conservative in the sense that any access to elements in the set will encounter the superset marks as expected.

Marks Under Conversion

The cty/convert package is aware of marks and will automatically propagate them through conversions. That includes nested values that are marked, which will be propagated to the corresponding nested value in the result if possible, or will be simplified to marks on a container where an exact propagation is not possible.

Marks as Function Arguments

The cty/function package is aware of marks and will, by default, automatically unmark all function arguments prior to calling a function and propagate the argument marks to the result value so that most functions do not need to worry about handling marks.

A function may opt in to handling marks itself for a particular parameter by setting AllowMarks: true on the definition of that parameter. If a function opts in, it is therefore responsible for correctly propagating any marks onto its result.

A function's Type implementation will receive automatically-unmarked values unless AllowMarks is set, which means that return-type checking alone will disregard any marks unless AllowMarks is set. Because type checking does not return a value, there is no way for type checking alone to communicate which marks it encountered during its work.

If you're using marks in a use-case around obscuring sensitive values, beware that type checking of some functions could extract information without preserving the sensitivity mark. For example, if a string marked as sensitive were passed as the first argument to the stdlib JSONDecode function then type-checking of that function will betray the object property names inside that value as part of the inferred result type.

Marks Under Serialization

Marks cannot be represented in either the JSON nor the msgpack serializations of cty values, and so the Marshal functions for those will return errors if they encounter marked values.

If you need to serialize values that might contain marks, you must explicitly unmark the whole data structure first (e.g. using Value.UnmarkDeep) and then decide what to do with those marks in order to ensure that if it makes sense to propagate them through the serialization then they will get represented somehow.

Relationship to "Refinements"

The idea of annotating a value with additional information has some overlap with the concept of Refinements. However, the two have different purposes and so different design details and tradeoffs.

For more details, see the corresponding section in the Refinements documentation.