pax_global_header00006660000000000000000000000064143325674640014530gustar00rootroot0000000000000052 comment=65607d49c7de277d1cd8c6da8c7d07c3d9b8e881 go-cty-1.12.1/000077500000000000000000000000001433256746400130145ustar00rootroot00000000000000go-cty-1.12.1/.travis.sh000077500000000000000000000004011433256746400147340ustar00rootroot00000000000000#!/bin/bash set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do go test -coverprofile=profile.out -covermode=atomic $d if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out fi done go-cty-1.12.1/.travis.yml000066400000000000000000000001621433256746400151240ustar00rootroot00000000000000language: go go: - 1.15.x - 1.16.x - tip before_install: - go get -t -v ./... script: - ./.travis.sh go-cty-1.12.1/CHANGELOG.md000066400000000000000000000675531433256746400146450ustar00rootroot00000000000000# 1.12.1 (November 8, 2022) * `convert`: Will now produce correct type constraints when the input value is an empty collection and the target element type has optional attributes. In this case the conversion process must remove the optional attribute annotations because those are only for type conversion purposes and have no meaning when used in the type constraint for an empty collection. ([#143](https://github.com/zclconf/go-cty/pull/143)) * `convert`: Will now prefer to retain a concrete type in the input value when the input is either null or unknown and the target type is `cty.DynamicPseudoType`, which represents "any type". ([#144](https://github.com/zclconf/go-cty/pull/144)) # 1.12.0 (October 27, 2022) * `function`: Each function can now have an English-language description summarizing its behavior. This is intended as a default string to use when an application wants to provide code hover tips or similar development aids. However, these descriptions are basic and only available in English, so applications may still prefer to provide their own descriptions and ignore those encoded in this module. ([#137](https://github.com/zclconf/go-cty/pull/137)) * `convert`: When running in "unsafe mode" (which allows additional conversions that can potentially fail with certain input values), we'll now allow converting from a map type to an object type with optional attributes as long as all of the _present_ map elements are compatible with their corresponding optional attributes. It's still a dynamic error to convert a map whose element type is incompatible with any of the attributes that _do_ have corresponding keys in the given map. ([#139](https://github.com/zclconf/go-cty/pull/139)) * `convert`: Will now produce correct type constraints when the input value is null and the target type has optional attributes. In this case the conversion process must remove the optional attribute annotations because those are only for type conversion purposes and have no meaning when used in the type constraint for a null or unknown value. ([#140](https://github.com/zclconf/go-cty/pull/140), [#141](https://github.com/zclconf/go-cty/pull/141)) # 1.11.1 (October 17, 2022) * `convert`: Fix for error when converting empty sets and lists with nested optional attributes by explicitly removing optional attribute information from collections. # 1.11.0 (August 22, 2022) ## Upgrade Notes This release contains some changes to some aspects of the API that are either legacy or de-facto internal (from before the Go toolchain had an explicit idea of that). Any external module using these will experience these as breaking changes, but we know of no such caller and so are admitting these without a major release in the interests of not creating churn for users of the main API. * **`encoding/gob` support utilities removed**: we added these as a concession to HashiCorp who wanted to try to send `cty` values over some legacy protocols/formats used by legacy versions of HashiCorp Terraform. In the end those efforts were not successful for other reasons and so no Terraform release ever relied on this functionality. `encoding/gob` support has been burdensome due to how its unmarshaler interface is defined and so `cty` values and types are no longer automatically compatible with `encoding/gob`. Callers should instead use explicitly-implemented encodings, such as the built-in JSON and msgpack encodings or external libraries which use the public `cty` API to encode and decode. * **cty now requires Go 1.18**: although the main API is not yet making any use of type parameters, we've begun to adopt it in the hope of improving the maintainability of some internal details, starting with the backing implementation of set types. Since type parameters are not supported by earlier versions of the Go compiler, callers must upgrade to Go 1.18 before using cty v1.11.0 or later. ## Other changes in this release * `cty`: Improved performance when comparing nonzero numbers to zero, by performing a relatively-cheap sign check on both numbers before falling back on the more expensive general equality implementation. ([#125](https://github.com/zclconf/go-cty/pull/125)) * `cty`: It's now possible to use capsule types in the elements of sets. Previously `cty` would panic if asked to construct a value of a set type whose element type either is or contains a capsule type, but there is now explicit support for storing encapsulated values in sets and optional (but recommended) support for a custom hashing function per type in order to improve performance for sets with a large number of elements. * `convert`: Unify will no longer panic when asked to find a common base type for a tuple type and a list of unknown element type, and will instead just signal that such a unification is not possible. ([#126](https://github.com/zclconf/go-cty/pull/126)) * `stdlib`: `FlattenFunc` will no longer panic if it encounters a null value of a type that would normally be subject to flattening. Instead, it will treat it in the same way as a null value of any non-flattenable type. ([#129](https://github.com/zclconf/go-cty/pull/129)) # 1.10.0 (November 2, 2021) * `cty`: The documented definition and comparison logic of `cty.Number` is now refined to acknowledge that its true range is limited only to values that have both a binary floating point and decimal representation, because `cty` values are primarily designed to traverse JSON serialization where numbers are always defined as decimal strings. In particular, that means that two `cty.Number` values now always compare as equal if their representation in JSON (under `cty`'s own JSON encoder) would be equal, even though the decimal approximation we use for that conversion is slightly lossy. This pragmatic compromise avoids confusing situations where a round-trip through JSON serialization (or other serializations that use the same number format) may produce a value that doesn't compare equal to the original. This new definition of equals should not cause any significant behavior change for any integer in our in-memory storage range, but may cause some fractional values to compare equal where they didn't before if they differ only by a small fraction. * `cty`: Don't panic in `Value.Equals` if comparing complex data structures with nested marked values. Instead, `Equals` will aggregate all of the marks on the resulting boolean value as we typically expect for operations that derived from marked values. ([#112](https://github.com/zclconf/go-cty/pull/112)) * `cty`: `Value.AsBigFloat` now properly isolates its result from the internal state of the associated value. It previously _attempted_ to do this (so that modifying the result would not affect the supposedly-immutable `cty.Number` value) but ended up creating an object which still had some shared buffers. The result is now entirely separate from the internal state of the recieving value. ([#114](https://github.com/zclconf/go-cty/pull/114)) * `function/stdlib`: The `FormatList` function will now return an unknown value if any of the arguments have an unknown type, because in that case it can't tell whether that value will ultimately become a string or a list of strings, and thus it can't predict how many elements the result will have. ([#115](https://github.com/zclconf/go-cty/pull/115)) # 1.9.1 (August 17, 2021) * `cty`: Don't panic in `Value.Equals` if comparing complex data structures with nested marked values. Instead, `Equals` will aggregate all of the marks on the resulting boolean value as we typically expect for operations that derived from marked values. ([#112](https://github.com/zclconf/go-cty/pull/112)) * `cty`: `Value.AsBigFloat` now properly isolates its result from the internal state of the associated value. It previously _attempted_ to do this (so that modifying the result would not affect the supposedly-immutable `cty.Number` value) but ended up creating an object which still had some shared buffers. The result is now entirely separate from the internal state of the recieving value. ([#114](https://github.com/zclconf/go-cty/pull/114)) * `function/stdlib`: The `FormatList` function will now return an unknown value if any of the arguments have an unknown type, because in that case it can't tell whether that value will ultimately become a string or a list of strings, and thus it can't predict how many elements the result will have. ([#115](https://github.com/zclconf/go-cty/pull/115)) # 1.9.0 (July 6, 2021) * `cty`: `cty.Walk`, `cty.Transform`, and `cty.TransformWithTransformer` now all correctly support marked values. Previously they would panic when encountering marked collections, because they would try to recurse into them without handling the markings. * `function/stdlib`: The `floor` and `ceil` functions no longer lower the precision of arguments to what would fit inside a 64-bit float, instead preserving precision in a similar way as most other arithmetic functions. ([#111](https://github.com/zclconf/go-cty/pull/111)) * `function/stdlib`: The `flatten` function was incorrectly treating null values of an unknown type as if they were unknown values. Now it will treat them the same as any other non-list/non-tuple value, flattening them down into the result as-is. ([#110](https://github.com/zclconf/go-cty/pull/110)) # 1.8.4 (June 22, 2021) * `function/stdlib`: The `flatten` function will now correctly return `cty.DynamicVal` if it encounters `cty.DynamicVal` anywhere in the given data structure, because it can't predict how many elements the result will have in that situation. ([#106](https://github.com/zclconf/go-cty/pull/106), [#107](https://github.com/zclconf/go-cty/pull/107)) * `function/stdlib`: The `setproduct` function will no longer panic when given a set containing unknown values, which would therefore be a set with an unknown length. ([#109](https://github.com/zclconf/go-cty/pull/109)) # 1.8.3 (May 4, 2021) * `function/stdlib`: Fix a panic in `SetproductFunc` in situations where one of the input collections is empty. ([#103](https://github.com/zclconf/go-cty/pull/103)) * `function/stdlib`: Improvements to `ElementFunc`, `ReverseListFunc`, and `SliceFunc` to handle marked values more precisely (individual element vs. whole-collection marks). ([#101](https://github.com/zclconf/go-cty/pull/101)) # 1.8.2 (April 20, 2021) * `cty`: `Value.Mark` will no longer incorrectly create nested markings when applied to a value that is already marked. Instead, it will unpack the reciever and use its underlying value directly, merging all of the marks into a new mark set. ([#96](https://github.com/zclconf/go-cty/pull/96)) * `cty:` `Value.RawEquals` will no longer panic if asked to compare two maps where at least one of them is marked. ([#96](https://github.com/zclconf/go-cty/pull/96)) * `function/stdlib`: Improvements to `ChunklistFunc`, `ConcatFunc`, `FlattenFunc`, `KeysFunc`, `LengthFunc`, `LookupFunc`, `MergeFunc`, `SetproductFunc`, `ValuesFunc`, and `ZipmapFunc` to handle marked values more precisely (individual element vs. whole-collection marks). ([#94](https://github.com/zclconf/go-cty/pull/94), [#95](https://github.com/zclconf/go-cty/pull/95), [#96](https://github.com/zclconf/go-cty/pull/96), [#97](https://github.com/zclconf/go-cty/pull/97), [#98](https://github.com/zclconf/go-cty/pull/98), [#99](https://github.com/zclconf/go-cty/pull/99), [#100](https://github.com/zclconf/go-cty/pull/100)) # 1.8.1 (March 16, 2021) * `convert`: Fix for panics and some general misbehavior when converting null values to type constraints containing objects with optional attributes. ([#88](https://github.com/zclconf/go-cty/pull/88)) * `convert`: Type unification of a mixture of list and tuple types and for a mixture of map and object types will now do the same recursive unification that we previously did for unification of just list types and just map types respectively, to avoid producing a very different and confusing result in situations where callers try to construct collections from a mixture of nested collections and nested structural types. ([#89](https://github.com/zclconf/go-cty/pull/89)) * `convert`: Conversion will no longer panic if we can't find a suitable single element type to use when converting to a collection type with a dynamically-selected element type. ([#91](https://github.com/zclconf/go-cty/pull/91)) * `function`: The `ReturnTypeForValues` and `Call` methods on `Function` will now protect functions from having to deal with nested marked values for arguments that don't specifically declare `AllowMarks: true`, as a concession for the fact that many functions were written prior to the introduction of marks as a concept. ([#92](https://github.com/zclconf/go-cty/pull/92)) # 1.8.0 (February 22, 2021) * `cty`: When running on Go 1.16 or later, the `cty.String` type will now normalize incoming string values using the Unicode 13 normalization rules. * `function/stdlib`: The various string functions which split strings into individual characters as part of their work will now use the Unicode 13 version of the text segmentation algorithm to do so. # 1.7.2 (February 22, 2021) * `cty`: The `Type.GoString` implementation for object types with optional attributes was previously producing incorrect results due to an implementation bug. ([#86](https://github.com/zclconf/go-cty/pull/86)) # 1.7.1 (December 15, 2020) * `cty`: The `Value.Multiply` and `Value.Modulo` functions now correctly propagate the floating point precision of the arguments, which avoids generating incorrect results for large integer operands. ([#75](https://github.com/zclconf/go-cty/pull/75)) * `convert`: The `convert.MismatchMessage` function will now correctly identify mismatching attributes in objects, rather than misreporting attributes that are actually present and correct. ([#78](https://github.com/zclconf/go-cty/pull/78)) * `function/stdlib`: The `merge` function now returns an empty object if all of its arguments are `null`, rather than returning `null` as before. That's more consistent with its usual behavior of ignoring `null` arguments when there is at least one non-null argument. ([#82](https://github.com/zclconf/go-cty/pull/82)) * `function/stdlib`: The `coalescelist` function now ignores any arguments that are null, rather than panicking as before.. ([#81](https://github.com/zclconf/go-cty/pull/81)) # 1.7.0 (October 20, 2020) * `cty`: `Value.UnmarkDeepWithPaths` and `Value.MarkWithPaths` are like `Value.UnmarkDeep` and `Value.Mark` but they retain path information for each marked value, so that marks can be re-applied later without all the loss of detail that results from `Value.UnmarkDeep` aggregating together all of the nested marks. * `function`: Unless a parameter has `AllowMarks: true` explicitly set, the functions infrastructure will now guarantee that it never sees a marked value even if the mark is deep inside a data structure. Previously that guarantee was only shallow for the top-level value, similar to `AllowUnknown`, but because marks are a relatively new addition to `cty` and numerous existing functions are not written to deal with them this is the more conservative and robust default. ([#72](https://github.com/zclconf/go-cty/pull/72)) * `function/stdlib`: The `formatdate` function was not correctly handling literal sequences at the end of the format string. It will now handle those as intended. ([#69](https://github.com/zclconf/go-cty/pull/69)) # 1.6.1 (September 2, 2020) * `cty`:: Fix a regression from 1.6.0 where `Value.RawEqual` no longer returned the correct result given a pair of sets containing partially-unknown values. ([#64](https://github.com/zclconf/go-cty/pull/64)) # 1.6.0 (August 30, 2020) * Fixed various defects in the handling of sets containing unknown values. This will cause unknown values to now be returned in more situations, whereas before `cty` would often return incorrect results when working with sets containing unknown values. The list of defects fixed in this release includes: - `cty`: The length of a set containing unknown values, as defined by `Value.Length`, is itself unknown, reflecting the fact that unknown values may be placeholders for values that are equal to other values in the set, which would thus coalesce into a single value. - `cty:` Converting a set with unknown values to a list produces an unknown value, because type conversion can't predict which indices each element of the set should take (the unknown elements could appear anywhere in the sort order) or the length of the resulting list. - `function/stdlib`: the `LengthFunc` and `ToList` functions wrap the behaviors described in the previous two items and are therefore also fixed in the same way. - `function/stclib`: `FormatListFunc` depends on knowing the length of all of its sequence arguments (which includes support for sets), so it will return an unknown result if given a set with an unknown length. - `function/stdlib`: The various set operation functions were previously producing incorrect results if one of their given sets contained unknown values, because they didn't consider that unknown values on one set may be placeholders for values that are equal to elements of the other set. For example, `SetSubtractFunc` now produces a wholly-unknown result if either of its arguments contains an unknown element, because it can't predict whether that unknown element represents a value equal to an element in the other set. - `cty`: The `Value.Equal` function would previously incorrectly return a known `cty.False` if one of the given sets contained an unknown value. It will now return `cty.UnknownVal(cty.Bool)` in that case, reflecting that the result could be either `cty.True` or `cty.False` were the unknown values to be replaced with known values. - `cty`: The `Value.LengthInt` function was also returning incorrect results for sets containing unknown elements. However, given that it is commonly used in conjunction with `ElementIterator` to determine the capacity for a slice to append elements to, it is not fixed and is instead redefined to return the _maximum possible length_, which would result if all of the unknown values represent values that are not equal to any other set element. Applications that use `Value.LengthInt` to determine lengths to return to users who are working in the space of `cty` values should switch to using `Value.Length` instead and handle the possibility of the length being unknown, to avoid returning incorrect results for sets with unknown values. These are not classified as breaking changes because the previous behavior was defective per the design goals for unknown values. However, callers may notice their application behavior changes along with these fixes when upgrading. The new behaviors should all be more correct than the old; if you observe a change in behavior where there is now an _incorrect_ result for sets containing unknown values (that is, where `cty` claims it knows an answer that it should not actually know), please report that in a GitHub issue. We advise callers which work with sets that may potentially contain unknown values to review their own set-handling functions to check if they too might be handling sets with unknown values incorrectly, particularly if they work with sets using [integration methods rather than operation methods](./docs/types.md#common-operations-and-integration-methods) (for example, using `Value.ValueList` or `Value.ValueSet` to extract elements directly). It seems that incorrect handling of sets with unknown values has been a common hazard, particularly in codepaths that aim to treat lists and sets as being interchangable. * `function/stdlib`: The `element` function will no longer panic if given a negative index. Instead, it will return a proper error. ([#62](https://github.com/zclconf/go-cty/pull/62)) * `convert`: **Experimental** support for annotating one or more attributes of an object type as "optional", which the `convert` package can then use to suppress the error that would normally be returned if the source type has no corresponding attribute, and can substitute a correctly-typed null value instead. This new behavior is subject to change even in minor release of `cty`, until it has been tested in experimental releases of downstream applications and potentially modified in response. # 1.5.1 (June 25, 2020) * `function/stdlib`: The `merge` function will no longer panic if all given maps are empty. ([#58](https://github.com/zclconf/go-cty/pull/58)) * `function/stdlib`: The various set-manipulation functions, like `setunion`, will no longer panic if given an unknown set value. ([#59](https://github.com/zclconf/go-cty/pull/59)) # 1.5.0 (June 11, 2020) * `cty`: New `Value.HasWhollyKnownType` method, for testing whether a value's type could potentially change if any unknown values it was constructed from were to become known. ([#55](https://github.com/zclconf/go-cty/pull/55)) * `convert`: Fix incorrect panic when converting a tuple with a dynamic-typed null member into a list or set, due to overly-liberal type unification. ([#56](https://github.com/zclconf/go-cty/pull/56)) # 1.4.2 (May 29, 2020) * `function/stdlib`: The `jsonencode` function will now correctly accept a null as its argument, and produce the JSON representation `"null"` rather than returning an error. ([#54](https://github.com/zclconf/go-cty/pull/54)) # 1.4.1 (May 18, 2020) * `function/stdlib`: Fix various panics related to sets with unknown element types in the set-manipulation functions. ([#52](https://github.com/zclconf/go-cty/pull/52)) * `convert`: Don't panic when asked to convert a tuple of objects to a list type constraint containing a nested `cty.DynamicPseudoType`. ([#53](https://github.com/zclconf/go-cty/pull/53)) # 1.4.0 (April 7, 2020) * `function/stdlib`: The string functions that partition strings into individual characters (grapheme clusters) now use the appropriate segmentation rules from Unicode 12.0.0, while previous versions used Unicode 9.0.0. * `function/stdlib`: New functions `Replace` and `RegexReplace` for matching and replacing sequences of characters in a given string with another given string. ([#45](https://github.com/zclconf/go-cty/pull/45)) * `function/stdlib`: The function `Substr` will now produce a zero-length string when given a length of zero. Previously it was incorrectly returning the remainder of the string after the given offset. ([#48](https://github.com/zclconf/go-cty/pull/48)) * `function/stdlib`: The `Floor` and `Ceil` functions will now return an infinity if given an infinity, rather than returning the maximum/minimum integer value. ([#51](https://github.com/zclconf/go-cty/pull/51)) * `cty`: Convenience methods for constructing path index steps from normal Go int and string values. ([#50](https://github.com/zclconf/go-cty/pull/50)) # 1.3.1 (March 3, 2020) * `convert`: Fix incorrect conversion rules for maps of maps that were leading to panics. This will now succeed in some more cases that ought to have been valid, and produce a proper error if there is no valid outcome. ([#47](https://github.com/zclconf/go-cty/pull/47)) * `function/stdlib`: Fix an implementation error in the `Contains` function that was introduced in 1.3.0, so it will now produce a correct result rather than failing with a confusing error message. ([#46](https://github.com/zclconf/go-cty/pull/46)) # 1.3.0 (February 19, 2020) * `convert`: There are now conversions from map types to object types, as long as the given map type's element type is convertible to all of the object type's attribute types. ([#42](https://github.com/zclconf/go-cty/pull/42)) * `function/stdlib`: HashiCorp has contributed a number of additional functions to the standard library that were originally implemented directly inside their Terraform codebase: ([#37](https://github.com/zclconf/go-cty/pull/37)) * `Element`: take an element from a list or tuple by index, using modulo wrap-around. * `CoalesceList`: return the first non-empty list argument. * `Compact`: take a list of strings and return a new list of strings with all empty strings removed. * `Contains`: returns true if a given value appears as an element in a list, tuple, or set. * `Distinct`: filters duplicate elements from a list while retaining the order of remaining items. * `ChunkList`: turn a list into a list-of-lists where each top-level list is a "chunk" of a particular size of elements from the input. * `Flatten`: given a sequence that might contain other sequences, eliminate any intermediate sequences to produce a flat sequence. * `Keys`: return a list of keys from a map or object value in lexical order. * `Values`: return a list of values from a map in the same order as `Keys`. * `Lookup`: conditional lookup of an element from a map if it's present, or a fallback value if not. (This one differs from its Terraform equivalent in that the default value argument is _required_.) * `Merge`: given one or more maps or objects, merge them together into a single collection. * `ReverseList`: given a list, return a new list with the same items in the opposite order. * `SetProduct`: compute the cartesian product of one or more sets. * `Slice`: extract a consecutive sub-list from a list. * `Zipmap`: given a pair of lists of the same length, interpret the first as keys and the second as corresponding values to produce a map. * A factory `MakeToFunc` to build functions that each convert to a particular type constraint. * `TimeAdd`: add a duration to a timestamp to produce a new timestamp. * `Ceil` and `Floor`: round a fractional value to the nearest integer, away from or towards zero respectively. * `Log`: computes a logarithm in a given base. * `Pow`: implements exponentiation. * `ParseInt`: parses a string containing digits in a particular base to produce a whole number value. * `Join`: concatenates the elements of a list of strings with a given separator to produce a string. * `Split`: partitions a string by a given separator, returning a list of strings. * `Sort`: sorts a list of strings into lexical order. * `Chomp`: removes one or more newline characters from the end of a given string, producing a new string. * `Indent`: prepends a number of spaces to all lines except the first in a given string, producing a new string. * `Title`: converts a string to "title case". * `TrimSpace`: trims spaces from the start and end of a given string. * `Trim`: generalization of `TrimSpace` that allows user-specified trimming characters. * `TrimPrefix`: like `Trim` but only at the start of the string. * `TrimSuffix`: like `Trim` but only at the end of the string. # 1.2.1 (January 10, 2020) * `cty`: Fixed an infinite recursion bug when working with sets containing nested data structures. ([#35](https://github.com/zclconf/go-cty/pull/35)) # 1.2.0 (December 14, 2019) * `cty`: Applications can now implement a general subset of the `cty` operations when creating a capsule type. For more information, see [Capsule Type Operation Definitions](./docs/capsule-type-operations.md). * `cty`: Values now support a new mechanism called [Value Marks](./docs/marks.md) which can be used to transit additional metadata through expressions by marking the input values and then observing which marks propagated to the result value. This could be used, for example, to detect whether a value was derived from a particular other value in case that is useful for giving extra feedback in an error message. # 1.1.1 (November 26, 2019) * `cty`: Fixed a panic situation when trying to round-trip `cty.Number` values through `encoding/gob`. ([#32](https://github.com/zclconf/go-cty/pull/32)) * `convert`: Invalid string conversions to bool that use incorrect case will now give more actionable feedback. ([#29](https://github.com/zclconf/go-cty/pull/29)) * `function/stdlib`: The `formatlist` function will no longer panic if given an unknown tuple as one of its arguments. # 1.1.0 (July 25, 2019) * New method `Path.Equals` for robustly comparing `cty.Path` values. Previously callers might've used `reflect.DeepEqual` or similar, but that is not correct when a path contains a `cty.Number` index because `reflect.DeepEqual` does not correctly represent equality for number values. ([#25](https://github.com/zclconf/go-cty/pull/25)) # 1.0.0 (June 6, 2019) Initial stable release. go-cty-1.12.1/LICENSE000066400000000000000000000020631433256746400140220ustar00rootroot00000000000000MIT License Copyright (c) 2017-2018 Martin Atkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-cty-1.12.1/README.md000066400000000000000000000063421433256746400143000ustar00rootroot00000000000000# cty `cty` (pronounced "see-tie", emoji: :eyes: :necktie:, [IPA](https://en.wikipedia.org/wiki/International_Phonetic_Alphabet): /si'tʰaɪ/) is a dynamic type system for applications written in Go that need to represent user-supplied values without losing type information. The primary intended use is for implementing configuration languages, but other uses may be possible too. One could think of `cty` as being the reflection API for a language that doesn't exist, or that doesn't exist _yet_. It provides a set of value types and an API for working with values of that type. Fundamentally what `cty` provides is equivalent to an `interface{}` with some dynamic type information attached, but `cty` encapsulates this to ensure that invariants are preserved and to provide a more convenient API. As well as primitive types, basic collection types (lists, maps and sets) and structural types (object, tuple), the `cty` type and value system has some additional, optional features that may be useful to certain applications: * Representation of "unknown" values, which serve as a typed placeholder for a value that has yet to be determined. This can be a useful building-block for a type checker. Unknown values support all of the same operations as known values of their type, but the result will often itself be unknown. * Representation of values whose _types_ aren't even known yet. This can represent, for example, the result of a JSON-decoding function before the JSON data is known. Along with the type system itself, a number of utility packages are provided that build on the basics to help integrate `cty` into calling applications. For example, `cty` values can be automatically converted to other types, converted to and from native Go data structures, or serialized as JSON. For more details, see the following documentation: * [Concepts](./docs/concepts.md) * [Full Description of the `cty` Types](./docs/types.md) * [API Reference](https://godoc.org/github.com/zclconf/go-cty/cty) (godoc) * [Conversion between `cty` types](./docs/convert.md) * [Conversion to and from native Go values](./docs/gocty.md) * [JSON serialization](./docs/json.md) * [`cty` Functions system](./docs/functions.md) --- ## License Copyright 2017 Martin Atkins Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-cty-1.12.1/cty/000077500000000000000000000000001433256746400136135ustar00rootroot00000000000000go-cty-1.12.1/cty/capsule.go000066400000000000000000000102641433256746400156010ustar00rootroot00000000000000package cty import ( "fmt" "reflect" ) type capsuleType struct { typeImplSigil Name string GoType reflect.Type Ops *CapsuleOps } func (t *capsuleType) Equals(other Type) bool { if otherP, ok := other.typeImpl.(*capsuleType); ok { // capsule types compare by pointer identity return otherP == t } return false } func (t *capsuleType) FriendlyName(mode friendlyTypeNameMode) string { return t.Name } func (t *capsuleType) GoString() string { impl := t.Ops.TypeGoString if impl == nil { // To get a useful representation of our native type requires some // shenanigans. victimVal := reflect.Zero(t.GoType) if t.Ops == noCapsuleOps { return fmt.Sprintf("cty.Capsule(%q, reflect.TypeOf(%#v))", t.Name, victimVal.Interface()) } else { // Including the operations in the output will make this _very_ long, // so in practice any capsule type with ops ought to provide a // TypeGoString function to override this with something more // reasonable. return fmt.Sprintf("cty.CapsuleWithOps(%q, reflect.TypeOf(%#v), %#v)", t.Name, victimVal.Interface(), t.Ops) } } return impl(t.GoType) } // Capsule creates a new Capsule type. // // A Capsule type is a special type that can be used to transport arbitrary // Go native values of a given type through the cty type system. A language // that uses cty as its type system might, for example, provide functions // that return capsule-typed values and then other functions that operate // on those values. // // From cty's perspective, Capsule types have a few interesting characteristics, // described in the following paragraphs. // // Each capsule type has an associated Go native type that it is able to // transport. Capsule types compare by identity, so each call to the // Capsule function creates an entirely-distinct cty Type, even if two calls // use the same native type. // // Each capsule-typed value contains a pointer to a value of the given native // type. A capsule-typed value by default supports no operations except // equality, and equality is implemented by pointer identity of the // encapsulated pointer. A capsule type can optionally have its own // implementations of certain operations if it is created with CapsuleWithOps // instead of Capsule. // // The given name is used as the new type's "friendly name". This can be any // string in principle, but will usually be a short, all-lowercase name aimed // at users of the embedding language (i.e. not mention Go-specific details) // and will ideally not create ambiguity with any predefined cty type. // // Capsule types are never introduced by any standard cty operation, so a // calling application opts in to including them within its own type system // by creating them and introducing them via its own functions. At that point, // the application is responsible for dealing with any capsule-typed values // that might be returned. func Capsule(name string, nativeType reflect.Type) Type { return Type{ &capsuleType{ Name: name, GoType: nativeType, Ops: noCapsuleOps, }, } } // CapsuleWithOps is like Capsule except the caller may provide an object // representing some overloaded operation implementations to associate with // the given capsule type. // // All of the other caveats and restrictions for capsule types still apply, but // overloaded operations can potentially help a capsule type participate better // in cty operations. func CapsuleWithOps(name string, nativeType reflect.Type, ops *CapsuleOps) Type { // Copy the operations to make sure the caller can't modify them after // we're constructed. ourOps := *ops ourOps.assertValid() return Type{ &capsuleType{ Name: name, GoType: nativeType, Ops: &ourOps, }, } } // IsCapsuleType returns true if this type is a capsule type, as created // by cty.Capsule . func (t Type) IsCapsuleType() bool { _, ok := t.typeImpl.(*capsuleType) return ok } // EncapsulatedType returns the encapsulated native type of a capsule type, // or panics if the receiver is not a Capsule type. // // Is IsCapsuleType to determine if this method is safe to call. func (t Type) EncapsulatedType() reflect.Type { impl, ok := t.typeImpl.(*capsuleType) if !ok { panic("not a capsule type") } return impl.GoType } go-cty-1.12.1/cty/capsule_ops.go000066400000000000000000000141721433256746400164640ustar00rootroot00000000000000package cty import ( "reflect" ) // CapsuleOps represents a set of overloaded operations for a capsule type. // // Each field is a reference to a function that can either be nil or can be // set to an implementation of the corresponding operation. If an operation // function is nil then it isn't supported for the given capsule type. type CapsuleOps struct { // GoString provides the GoString implementation for values of the // corresponding type. Conventionally this should return a string // representation of an expression that would produce an equivalent // value. GoString func(val interface{}) string // TypeGoString provides the GoString implementation for the corresponding // capsule type itself. TypeGoString func(goTy reflect.Type) string // Equals provides the implementation of the Equals operation. This is // called only with known, non-null values of the corresponding type, // but if the corresponding type is a compound type then it must be // ready to detect and handle nested unknown or null values, usually // by recursively calling Value.Equals on those nested values. // // The result value must always be of type cty.Bool, or the Equals // operation will panic. // // If RawEquals is set without also setting Equals, the RawEquals // implementation will be used as a fallback implementation. That fallback // is appropriate only for leaf types that do not contain any nested // cty.Value that would need to distinguish Equals vs. RawEquals for their // own equality. // // If RawEquals is nil then Equals must also be nil, selecting the default // pointer-identity comparison instead. Equals func(a, b interface{}) Value // RawEquals provides the implementation of the RawEquals operation. // This is called only with known, non-null values of the corresponding // type, but if the corresponding type is a compound type then it must be // ready to detect and handle nested unknown or null values, usually // by recursively calling Value.RawEquals on those nested values. // // If RawEquals is nil, values of the corresponding type are compared by // pointer identity of the encapsulated value. RawEquals func(a, b interface{}) bool // HashKey provides a hashing function for values of the corresponding // capsule type. If defined, cty will use the resulting hashes as part // of the implementation of sets whose element type is or contains the // corresponding capsule type. // // If a capsule type defines HashValue then the function _must_ return // an equal hash value for any two values that would cause Equals or // RawEquals to return true when given those values. If a given type // does not uphold that assumption then sets including this type will // not behave correctly. HashKey func(v interface{}) string // ConversionFrom can provide conversions from the corresponding type to // some other type when values of the corresponding type are used with // the "convert" package. (The main cty package does not use this operation.) // // This function itself returns a function, allowing it to switch its // behavior depending on the given source type. Return nil to indicate // that no such conversion is available. ConversionFrom func(src Type) func(interface{}, Path) (Value, error) // ConversionTo can provide conversions to the corresponding type from // some other type when values of the corresponding type are used with // the "convert" package. (The main cty package does not use this operation.) // // This function itself returns a function, allowing it to switch its // behavior depending on the given destination type. Return nil to indicate // that no such conversion is available. ConversionTo func(dst Type) func(Value, Path) (interface{}, error) // ExtensionData is an extension point for applications that wish to // create their own extension features using capsule types. // // The key argument is any value that can be compared with Go's == // operator, but should be of a named type in a package belonging to the // application defining the key. An ExtensionData implementation must // check to see if the given key is familar to it, and if so return a // suitable value for the key. // // If the given key is unrecognized, the ExtensionData function must // return a nil interface. (Importantly, not an interface containing a nil // pointer of some other type.) // The common implementation of ExtensionData is a single switch statement // over "key" which has a default case returning nil. // // The meaning of any given key is entirely up to the application that // defines it. Applications consuming ExtensionData from capsule types // should do so defensively: if the result of ExtensionData is not valid, // prefer to ignore it or gracefully produce an error rather than causing // a panic. ExtensionData func(key interface{}) interface{} } // noCapsuleOps is a pointer to a CapsuleOps with no functions set, which // is used as the default operations value when a type is created using // the Capsule function. var noCapsuleOps = &CapsuleOps{} func (ops *CapsuleOps) assertValid() { if ops.RawEquals == nil && ops.Equals != nil { panic("Equals cannot be set without RawEquals") } } // CapsuleOps returns a pointer to the CapsuleOps value for a capsule type, // or panics if the receiver is not a capsule type. // // The caller must not modify the CapsuleOps. func (ty Type) CapsuleOps() *CapsuleOps { if !ty.IsCapsuleType() { panic("not a capsule-typed value") } return ty.typeImpl.(*capsuleType).Ops } // CapsuleExtensionData is a convenience interface to the ExtensionData // function that can be optionally implemented for a capsule type. It will // check to see if the underlying type implements ExtensionData and call it // if so. If not, it will return nil to indicate that the given key is not // supported. // // See the documentation for CapsuleOps.ExtensionData for more information // on the purpose of and usage of this mechanism. // // If CapsuleExtensionData is called on a non-capsule type then it will panic. func (ty Type) CapsuleExtensionData(key interface{}) interface{} { ops := ty.CapsuleOps() if ops.ExtensionData == nil { return nil } return ops.ExtensionData(key) } go-cty-1.12.1/cty/capsule_test.go000066400000000000000000000065331433256746400166440ustar00rootroot00000000000000package cty import ( "fmt" "reflect" "testing" "github.com/google/go-cmp/cmp" ) type capsuleTestType1Native struct { name string } type capsuleTestType2Native struct { name string } var capsuleTestType1 = Capsule( "capsule test type 1", reflect.TypeOf(capsuleTestType1Native{}), ) var capsuleTestType2 = Capsule( "capsule test type 2", reflect.TypeOf(capsuleTestType2Native{}), ) func TestCapsuleWithOps(t *testing.T) { var i = 0 var i2 = 0 var i3 = 1 t.Run("with ops", func(t *testing.T) { ty := CapsuleWithOps("with ops", reflect.TypeOf(0), &CapsuleOps{ GoString: func(v interface{}) string { iPtr := v.(*int) return fmt.Sprintf("test.WithOpsVal(%#v)", *iPtr) }, TypeGoString: func(ty reflect.Type) string { return fmt.Sprintf("test.WithOps(%s)", ty) }, Equals: func(a, b interface{}) Value { aPtr := a.(*int) bPtr := b.(*int) return BoolVal(*aPtr == *bPtr) }, RawEquals: func(a, b interface{}) bool { aPtr := a.(*int) bPtr := b.(*int) return *aPtr == *bPtr }, }) v := CapsuleVal(ty, &i) v2 := CapsuleVal(ty, &i2) v3 := CapsuleVal(ty, &i3) got := map[string]interface{}{} got["GoString"] = v.GoString() got["TypeGoString"] = ty.GoString() got["Equals.Yes"] = v.Equals(v2) got["Equals.No"] = v.Equals(v3) want := map[string]interface{}{ "GoString": "test.WithOpsVal(0)", "TypeGoString": "test.WithOps(int)", "Equals.Yes": True, "Equals.No": False, } valCmp := cmp.Comparer(Value.RawEquals) if diff := cmp.Diff(want, got, valCmp); diff != "" { t.Errorf("wrong results\n%s", diff) } }) t.Run("without ops", func(t *testing.T) { ty := Capsule("without ops", reflect.TypeOf(0)) v := CapsuleVal(ty, &i) v2 := CapsuleVal(ty, &i2) got := map[string]interface{}{} got["GoString"] = v.GoString() got["TypeGoString"] = ty.GoString() got["Equals"] = v.Equals(v2) got["RawEquals"] = v.RawEquals(v2) want := map[string]interface{}{ "GoString": fmt.Sprintf(`cty.CapsuleVal(cty.Capsule("without ops", reflect.TypeOf(0)), (*int)(0x%x))`, &i), "TypeGoString": `cty.Capsule("without ops", reflect.TypeOf(0))`, "Equals": False, "RawEquals": false, } valCmp := cmp.Comparer(Value.RawEquals) if diff := cmp.Diff(want, got, valCmp); diff != "" { t.Errorf("wrong results\n%s", diff) } }) } func TestCapsuleExtensionData(t *testing.T) { ty := CapsuleWithOps("with extension data", reflect.TypeOf(0), &CapsuleOps{ ExtensionData: func(key interface{}) interface{} { switch key { // Note that this is a bad example of a key, just using a plain // string for easier testing. Real-world extension keys should // be named types belonging to a package in the application that // is defining them. case "hello": return "world" default: return nil } }, }) got := ty.CapsuleExtensionData("hello") want := interface{}("world") if got != want { t.Errorf("wrong result for 'hello'\ngot: %#v\nwant: %#v", got, want) } got = ty.CapsuleExtensionData("nonexistent") want = nil if got != want { t.Errorf("wrong result for 'nonexistent'\ngot: %#v\nwant: %#v", got, want) } ty2 := Capsule("without extension data", reflect.TypeOf(0)) got = ty2.CapsuleExtensionData("hello") want = nil if got != want { t.Errorf("wrong result for 'hello' without extension data\ngot: %#v\nwant: %#v", got, want) } } go-cty-1.12.1/cty/collection.go000066400000000000000000000017551433256746400163050ustar00rootroot00000000000000package cty import ( "errors" ) type collectionTypeImpl interface { ElementType() Type } // IsCollectionType returns true if the given type supports the operations // that are defined for all collection types. func (t Type) IsCollectionType() bool { _, ok := t.typeImpl.(collectionTypeImpl) return ok } // ElementType returns the element type of the receiver if it is a collection // type, or panics if it is not. Use IsCollectionType first to test whether // this method will succeed. func (t Type) ElementType() Type { if ct, ok := t.typeImpl.(collectionTypeImpl); ok { return ct.ElementType() } panic(errors.New("not a collection type")) } // ElementCallback is a callback type used for iterating over elements of // collections and attributes of objects. // // The types of key and value depend on what type is being iterated over. // Return true to stop iterating after the current element, or false to // continue iterating. type ElementCallback func(key Value, val Value) (stop bool) go-cty-1.12.1/cty/convert/000077500000000000000000000000001433256746400152735ustar00rootroot00000000000000go-cty-1.12.1/cty/convert/compare_types.go000066400000000000000000000101111433256746400204660ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // compareTypes implements a preference order for unification. // // The result of this method is not useful for anything other than unification // preferences, since it assumes that the caller will verify that any suggested // conversion is actually possible and it is thus able to to make certain // optimistic assumptions. func compareTypes(a cty.Type, b cty.Type) int { // DynamicPseudoType always has lowest preference, because anything can // convert to it (it acts as a placeholder for "any type") and we want // to optimistically assume that any dynamics will converge on matching // their neighbors. if a == cty.DynamicPseudoType || b == cty.DynamicPseudoType { if a != cty.DynamicPseudoType { return -1 } if b != cty.DynamicPseudoType { return 1 } return 0 } if a.IsPrimitiveType() && b.IsPrimitiveType() { // String is a supertype of all primitive types, because we can // represent all primitive values as specially-formatted strings. if a == cty.String || b == cty.String { if a != cty.String { return 1 } if b != cty.String { return -1 } return 0 } } if a.IsListType() && b.IsListType() { return compareTypes(a.ElementType(), b.ElementType()) } if a.IsSetType() && b.IsSetType() { return compareTypes(a.ElementType(), b.ElementType()) } if a.IsMapType() && b.IsMapType() { return compareTypes(a.ElementType(), b.ElementType()) } // From this point on we may have swapped the two items in order to // simplify our cases. Therefore any non-zero return after this point // must be multiplied by "swap" to potentially invert the return value // if needed. swap := 1 switch { case a.IsTupleType() && b.IsListType(): fallthrough case a.IsObjectType() && b.IsMapType(): fallthrough case a.IsSetType() && b.IsTupleType(): fallthrough case a.IsSetType() && b.IsListType(): a, b = b, a swap = -1 } if b.IsSetType() && (a.IsTupleType() || a.IsListType()) { // We'll just optimistically assume that the element types are // unifyable/convertible, and let a second recursive pass // figure out how to make that so. return -1 * swap } if a.IsListType() && b.IsTupleType() { // We'll just optimistically assume that the tuple's element types // can be unified into something compatible with the list's element // type. return -1 * swap } if a.IsMapType() && b.IsObjectType() { // We'll just optimistically assume that the object's attribute types // can be unified into something compatible with the map's element // type. return -1 * swap } // For object and tuple types, comparing two types doesn't really tell // the whole story because it may be possible to construct a new type C // that is the supertype of both A and B by unifying each attribute/element // separately. That possibility is handled by Unify as a follow-up if // type sorting is insufficient to produce a valid result. // // Here we will take care of the simple possibilities where no new type // is needed. if a.IsObjectType() && b.IsObjectType() { atysA := a.AttributeTypes() atysB := b.AttributeTypes() if len(atysA) != len(atysB) { return 0 } hasASuper := false hasBSuper := false for k := range atysA { if _, has := atysB[k]; !has { return 0 } cmp := compareTypes(atysA[k], atysB[k]) if cmp < 0 { hasASuper = true } else if cmp > 0 { hasBSuper = true } } switch { case hasASuper && hasBSuper: return 0 case hasASuper: return -1 * swap case hasBSuper: return 1 * swap default: return 0 } } if a.IsTupleType() && b.IsTupleType() { etysA := a.TupleElementTypes() etysB := b.TupleElementTypes() if len(etysA) != len(etysB) { return 0 } hasASuper := false hasBSuper := false for i := range etysA { cmp := compareTypes(etysA[i], etysB[i]) if cmp < 0 { hasASuper = true } else if cmp > 0 { hasBSuper = true } } switch { case hasASuper && hasBSuper: return 0 case hasASuper: return -1 * swap case hasBSuper: return 1 * swap default: return 0 } } return 0 } go-cty-1.12.1/cty/convert/compare_types_test.go000066400000000000000000000117021433256746400215340ustar00rootroot00000000000000package convert import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestCompareTypes(t *testing.T) { tests := []struct { A cty.Type B cty.Type Want int }{ // Primitives { cty.String, cty.String, 0, }, { cty.String, cty.Number, -1, }, { cty.Number, cty.String, 1, }, { cty.String, cty.Bool, -1, }, { cty.Bool, cty.String, 1, }, { cty.Bool, cty.Number, 0, }, { cty.Number, cty.Bool, 0, }, // Lists { cty.List(cty.String), cty.List(cty.String), 0, }, { cty.List(cty.String), cty.List(cty.Number), -1, }, { cty.List(cty.Number), cty.List(cty.String), 1, }, { cty.List(cty.String), cty.String, 0, }, // Sets { cty.Set(cty.String), cty.Set(cty.String), 0, }, { cty.Set(cty.String), cty.Set(cty.Number), -1, }, { cty.Set(cty.Number), cty.Set(cty.String), 1, }, { cty.Set(cty.String), cty.String, 0, }, // Maps { cty.Map(cty.String), cty.Map(cty.String), 0, }, { cty.Map(cty.String), cty.Map(cty.Number), -1, }, { cty.Map(cty.Number), cty.Map(cty.String), 1, }, { cty.Map(cty.String), cty.String, 0, }, // Objects { cty.EmptyObject, cty.EmptyObject, 0, }, { cty.EmptyObject, cty.Object(map[string]cty.Type{ "name": cty.String, }), 0, }, { cty.Object(map[string]cty.Type{ "name": cty.String, }), cty.Object(map[string]cty.Type{ "name": cty.String, }), 0, }, { cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), cty.Object(map[string]cty.Type{ "name": cty.String, }), 0, }, { cty.Object(map[string]cty.Type{ "number": cty.Number, }), cty.Object(map[string]cty.Type{ "name": cty.String, }), 0, }, { cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), 0, }, { cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.String, }), cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), -1, }, { cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.String, }), 1, }, { // This is the tricky case where comparing types doesn't tell // the whole story, because there is a third type C where both // attributes are strings which would be a common base type // of these. cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Number, }), cty.Object(map[string]cty.Type{ "a": cty.Number, "b": cty.String, }), 0, }, // Tuples { cty.EmptyTuple, cty.EmptyTuple, 0, }, { cty.EmptyTuple, cty.Tuple([]cty.Type{cty.String}), 0, }, { cty.Tuple([]cty.Type{cty.String}), cty.Tuple([]cty.Type{cty.String}), 0, }, { cty.Tuple([]cty.Type{cty.String, cty.Number}), cty.Tuple([]cty.Type{cty.String}), 0, }, { cty.Tuple([]cty.Type{cty.String, cty.Number}), cty.Tuple([]cty.Type{cty.String, cty.Number}), 0, }, { cty.Tuple([]cty.Type{cty.String, cty.String}), cty.Tuple([]cty.Type{cty.String, cty.Number}), -1, }, { cty.Tuple([]cty.Type{cty.String, cty.Number}), cty.Tuple([]cty.Type{cty.String, cty.String}), 1, }, { // This is the tricky case where comparing types doesn't tell // the whole story, because there is a third type C where both // elements are strings which would be a common base type // of these. cty.Tuple([]cty.Type{cty.String, cty.Number}), cty.Tuple([]cty.Type{cty.Number, cty.String}), 0, }, // Lists and Sets { cty.Set(cty.String), cty.List(cty.String), 1, }, { cty.List(cty.String), cty.Set(cty.String), -1, }, { cty.List(cty.String), cty.Set(cty.Number), -1, }, { cty.Set(cty.Number), cty.List(cty.String), 1, }, { cty.List(cty.Number), cty.Set(cty.String), -1, }, { cty.Set(cty.String), cty.List(cty.Number), 1, }, // Dynamics { cty.DynamicPseudoType, cty.DynamicPseudoType, 0, }, { cty.DynamicPseudoType, cty.String, 1, }, { cty.String, cty.DynamicPseudoType, -1, }, { cty.Number, cty.DynamicPseudoType, -1, }, { cty.DynamicPseudoType, cty.Number, 1, }, { cty.Bool, cty.DynamicPseudoType, -1, }, { cty.DynamicPseudoType, cty.Bool, 1, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v,%#v", test.A, test.B), func(t *testing.T) { got := compareTypes(test.A, test.B) if got != test.Want { t.Errorf( "wrong result\nA: %#v\nB: %#v\ngot: %#v\nwant: %#v", test.A, test.B, got, test.Want, ) } }) } } go-cty-1.12.1/cty/convert/conversion.go000066400000000000000000000136521433256746400200160ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // conversion is an internal variant of Conversion that carries around // a cty.Path to be used in error responses. type conversion func(cty.Value, cty.Path) (cty.Value, error) func getConversion(in cty.Type, out cty.Type, unsafe bool) conversion { conv := getConversionKnown(in, out, unsafe) if conv == nil { return nil } // Wrap the conversion in some standard checks that we don't want to // have to repeat in every conversion function. var ret conversion ret = func(in cty.Value, path cty.Path) (cty.Value, error) { if in.IsMarked() { // We must unmark during the conversion and then re-apply the // same marks to the result. in, inMarks := in.Unmark() v, err := ret(in, path) if v != cty.NilVal { v = v.WithMarks(inMarks) } return v, err } if out == cty.DynamicPseudoType { // Conversion to DynamicPseudoType always just passes through verbatim. return in, nil } if isKnown, isNull := in.IsKnown(), in.IsNull(); !isKnown || isNull { // Avoid constructing unknown or null values with types which // include optional attributes. Known or non-null object values // will be passed to a conversion function which drops the optional // attributes from the type. Unknown and null pass through values // must do the same to ensure that homogeneous collections have a // single element type. out = out.WithoutOptionalAttributesDeep() if !isKnown { return cty.UnknownVal(dynamicReplace(in.Type(), out)), nil } if isNull { // We'll pass through nulls, albeit type converted, and let // the caller deal with whatever handling they want to do in // case null values are considered valid in some applications. return cty.NullVal(dynamicReplace(in.Type(), out)), nil } } return conv(in, path) } return ret } func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion { switch { case out == cty.DynamicPseudoType: // Conversion *to* DynamicPseudoType means that the caller wishes // to allow any type in this position, so we'll produce a do-nothing // conversion that just passes through the value as-is. return dynamicPassthrough case unsafe && in == cty.DynamicPseudoType: // Conversion *from* DynamicPseudoType means that we have a value // whose type isn't yet known during type checking. For these we will // assume that conversion will succeed and deal with any errors that // result (which is why we can only do this when "unsafe" is set). return dynamicFixup(out) case in.IsPrimitiveType() && out.IsPrimitiveType(): conv := primitiveConversionsSafe[in][out] if conv != nil { return conv } if unsafe { return primitiveConversionsUnsafe[in][out] } return nil case out.IsObjectType() && in.IsObjectType(): return conversionObjectToObject(in, out, unsafe) case out.IsTupleType() && in.IsTupleType(): return conversionTupleToTuple(in, out, unsafe) case out.IsListType() && (in.IsListType() || in.IsSetType()): inEty := in.ElementType() outEty := out.ElementType() if inEty.Equals(outEty) { // This indicates that we're converting from list to set with // the same element type, so we don't need an element converter. return conversionCollectionToList(outEty, nil) } convEty := getConversion(inEty, outEty, unsafe) if convEty == nil { return nil } return conversionCollectionToList(outEty, convEty) case out.IsSetType() && (in.IsListType() || in.IsSetType()): if in.IsListType() && !unsafe { // Conversion from list to map is unsafe because it will lose // information: the ordering will not be preserved, and any // duplicate elements will be conflated. return nil } inEty := in.ElementType() outEty := out.ElementType() convEty := getConversion(inEty, outEty, unsafe) if inEty.Equals(outEty) { // This indicates that we're converting from set to list with // the same element type, so we don't need an element converter. return conversionCollectionToSet(outEty, nil) } if convEty == nil { return nil } return conversionCollectionToSet(outEty, convEty) case out.IsMapType() && in.IsMapType(): inEty := in.ElementType() outEty := out.ElementType() convEty := getConversion(inEty, outEty, unsafe) if convEty == nil { return nil } return conversionCollectionToMap(outEty, convEty) case out.IsListType() && in.IsTupleType(): outEty := out.ElementType() return conversionTupleToList(in, outEty, unsafe) case out.IsSetType() && in.IsTupleType(): outEty := out.ElementType() return conversionTupleToSet(in, outEty, unsafe) case out.IsMapType() && in.IsObjectType(): outEty := out.ElementType() return conversionObjectToMap(in, outEty, unsafe) case out.IsObjectType() && in.IsMapType(): if !unsafe { // Converting a map to an object is an "unsafe" conversion, // because we don't know if all the map keys will correspond to // object attributes. return nil } return conversionMapToObject(in, out, unsafe) case in.IsCapsuleType() || out.IsCapsuleType(): if !unsafe { // Capsule types can only participate in "unsafe" conversions, // because we don't know enough about their conversion behaviors // to be sure that they will always be safe. return nil } if in.Equals(out) { // conversion to self is never allowed return nil } if out.IsCapsuleType() { if fn := out.CapsuleOps().ConversionTo; fn != nil { return conversionToCapsule(in, out, fn) } } if in.IsCapsuleType() { if fn := in.CapsuleOps().ConversionFrom; fn != nil { return conversionFromCapsule(in, out, fn) } } // No conversion operation is available, then. return nil default: return nil } } // retConversion wraps a conversion (internal type) so it can be returned // as a Conversion (public type). func retConversion(conv conversion) Conversion { if conv == nil { return nil } return func(in cty.Value) (cty.Value, error) { return conv(in, cty.Path(nil)) } } go-cty-1.12.1/cty/convert/conversion_capsule.go000066400000000000000000000013611433256746400215240ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) func conversionToCapsule(inTy, outTy cty.Type, fn func(inTy cty.Type) func(cty.Value, cty.Path) (interface{}, error)) conversion { rawConv := fn(inTy) if rawConv == nil { return nil } return func(in cty.Value, path cty.Path) (cty.Value, error) { rawV, err := rawConv(in, path) if err != nil { return cty.NilVal, err } return cty.CapsuleVal(outTy, rawV), nil } } func conversionFromCapsule(inTy, outTy cty.Type, fn func(outTy cty.Type) func(interface{}, cty.Path) (cty.Value, error)) conversion { rawConv := fn(outTy) if rawConv == nil { return nil } return func(in cty.Value, path cty.Path) (cty.Value, error) { return rawConv(in.EncapsulatedValue(), path) } } go-cty-1.12.1/cty/convert/conversion_capsule_test.go000066400000000000000000000053731433256746400225720ustar00rootroot00000000000000package convert import ( "fmt" "reflect" "testing" "github.com/zclconf/go-cty/cty" ) func TestConvertCapsuleType(t *testing.T) { capTy := cty.CapsuleWithOps("test thingy", reflect.TypeOf(""), &cty.CapsuleOps{ GoString: func(rawV interface{}) string { vPtr := rawV.(*string) return fmt.Sprintf("capTy(%q)", *vPtr) }, TypeGoString: func(ty reflect.Type) string { return "capTy" }, RawEquals: func(a, b interface{}) bool { aPtr := a.(*string) bPtr := b.(*string) return *aPtr == *bPtr }, ConversionFrom: func(srcTy cty.Type) func(interface{}, cty.Path) (cty.Value, error) { if !srcTy.Equals(cty.String) { return nil } return func(rawV interface{}, path cty.Path) (cty.Value, error) { vPtr := rawV.(*string) return cty.StringVal(*vPtr), nil } }, ConversionTo: func(dstTy cty.Type) func(cty.Value, cty.Path) (interface{}, error) { if !dstTy.Equals(cty.String) { return nil } return func(from cty.Value, path cty.Path) (interface{}, error) { s := from.AsString() return &s, nil } }, }) capVal := func(s string) cty.Value { return cty.CapsuleVal(capTy, &s) } tests := []struct { From cty.Value To cty.Type Want cty.Value WantErr string }{ { From: capVal("hello"), To: cty.String, Want: cty.StringVal("hello"), }, { From: cty.StringVal("hello"), To: capTy, Want: capVal("hello"), }, { From: cty.True, To: capTy, WantErr: `test thingy required`, }, { From: capVal("hello"), To: cty.Bool, WantErr: `bool required`, }, { From: cty.UnknownVal(capTy), To: cty.String, Want: cty.UnknownVal(cty.String), }, { From: cty.NullVal(capTy), To: cty.String, Want: cty.NullVal(cty.String), }, { From: cty.UnknownVal(cty.Bool), To: capTy, WantErr: `test thingy required`, }, { From: cty.NullVal(cty.Bool), To: capTy, WantErr: `test thingy required`, }, { From: cty.UnknownVal(capTy), To: cty.Bool, WantErr: `bool required`, }, { From: cty.NullVal(capTy), To: cty.Bool, WantErr: `bool required`, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v to %#v", test.From, test.To), func(t *testing.T) { got, err := Convert(test.From, test.To) if test.WantErr == "" { if err != nil { t.Fatalf("wrong error\nwant: \ngot: %s", err) } if !test.Want.RawEquals(got) { t.Errorf("wrong result\nwant: %#v\ngot: %#v", got, test.Want) } } else { if err == nil { t.Fatalf("wrong error\nwant: %s\ngot: ", test.WantErr) } if got, want := err.Error(), test.WantErr; got != want { t.Errorf("wrong error\nwant: %s\ngot: %s", got, want) } } }) } } go-cty-1.12.1/cty/convert/conversion_collection.go000066400000000000000000000442121433256746400222250ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // conversionCollectionToList returns a conversion that will apply the given // conversion to all of the elements of a collection (something that supports // ForEachElement and LengthInt) and then returns the result as a list. // // "conv" can be nil if the elements are expected to already be of the // correct type and just need to be re-wrapped into a list. (For example, // if we're converting from a set into a list of the same element type.) func conversionCollectionToList(ety cty.Type, conv conversion) conversion { return func(val cty.Value, path cty.Path) (cty.Value, error) { if !val.Length().IsKnown() { // If the input collection has an unknown length (which is true // for a set containing unknown values) then our result must be // an unknown list, because we can't predict how many elements // the resulting list should have. return cty.UnknownVal(cty.List(val.Type().ElementType())), nil } elems := make([]cty.Value, 0, val.LengthInt()) i := int64(0) elemPath := append(path.Copy(), nil) it := val.ElementIterator() for it.Next() { _, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.NumberIntVal(i), } if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } if val.IsNull() { val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) } elems = append(elems, val) i++ } if len(elems) == 0 { // Prefer a concrete type over a dynamic type when returning an // empty list if ety == cty.DynamicPseudoType { return cty.ListValEmpty(val.Type().ElementType()), nil } return cty.ListValEmpty(ety.WithoutOptionalAttributesDeep()), nil } if !cty.CanListVal(elems) { return cty.NilVal, path.NewErrorf("element types must all match for conversion to list") } return cty.ListVal(elems), nil } } // conversionCollectionToSet returns a conversion that will apply the given // conversion to all of the elements of a collection (something that supports // ForEachElement and LengthInt) and then returns the result as a set. // // "conv" can be nil if the elements are expected to already be of the // correct type and just need to be re-wrapped into a set. (For example, // if we're converting from a list into a set of the same element type.) func conversionCollectionToSet(ety cty.Type, conv conversion) conversion { return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make([]cty.Value, 0, val.LengthInt()) i := int64(0) elemPath := append(path.Copy(), nil) it := val.ElementIterator() for it.Next() { _, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.NumberIntVal(i), } if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } if val.IsNull() { val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) } elems = append(elems, val) i++ } if len(elems) == 0 { // Prefer a concrete type over a dynamic type when returning an // empty set if ety == cty.DynamicPseudoType { return cty.SetValEmpty(val.Type().ElementType()), nil } return cty.SetValEmpty(ety.WithoutOptionalAttributesDeep()), nil } if !cty.CanSetVal(elems) { return cty.NilVal, path.NewErrorf("element types must all match for conversion to set") } return cty.SetVal(elems), nil } } // conversionCollectionToMap returns a conversion that will apply the given // conversion to all of the elements of a collection (something that supports // ForEachElement and LengthInt) and then returns the result as a map. // // "conv" can be nil if the elements are expected to already be of the // correct type and just need to be re-wrapped into a map. func conversionCollectionToMap(ety cty.Type, conv conversion) conversion { return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make(map[string]cty.Value, 0) elemPath := append(path.Copy(), nil) it := val.ElementIterator() for it.Next() { key, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: key, } keyStr, err := Convert(key, cty.String) if err != nil { // Should never happen, because keys can only be numbers or // strings and both can convert to string. return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName()) } if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } elems[keyStr.AsString()] = val } if len(elems) == 0 { // Prefer a concrete type over a dynamic type when returning an // empty map if ety == cty.DynamicPseudoType { return cty.MapValEmpty(val.Type().ElementType()), nil } return cty.MapValEmpty(ety), nil } if ety.IsCollectionType() || ety.IsObjectType() { var err error if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil { return cty.NilVal, err } } if !cty.CanMapVal(elems) { return cty.NilVal, path.NewErrorf("element types must all match for conversion to map") } return cty.MapVal(elems), nil } } // conversionTupleToSet returns a conversion that will take a value of the // given tuple type and return a set of the given element type. // // Will panic if the given tupleType isn't actually a tuple type. func conversionTupleToSet(tupleType cty.Type, setEty cty.Type, unsafe bool) conversion { tupleEtys := tupleType.TupleElementTypes() if len(tupleEtys) == 0 { // Empty tuple short-circuit return func(val cty.Value, path cty.Path) (cty.Value, error) { return cty.SetValEmpty(setEty.WithoutOptionalAttributesDeep()), nil } } if setEty == cty.DynamicPseudoType { // This is a special case where the caller wants us to find // a suitable single type that all elements can convert to, if // possible. setEty, _ = unify(tupleEtys, unsafe) if setEty == cty.NilType { return nil } // If the set element type after unification is still the dynamic // type, the only way this can result in a valid set is if all values // are of dynamic type if setEty == cty.DynamicPseudoType { for _, tupleEty := range tupleEtys { if !tupleEty.Equals(cty.DynamicPseudoType) { return nil } } } } elemConvs := make([]conversion, len(tupleEtys)) for i, tupleEty := range tupleEtys { if tupleEty.Equals(setEty) { // no conversion required continue } elemConvs[i] = getConversion(tupleEty, setEty, unsafe) if elemConvs[i] == nil { // If any of our element conversions are impossible, then the our // whole conversion is impossible. return nil } } // If we fall out here then a conversion is possible, using the // element conversions in elemConvs return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make([]cty.Value, 0, len(elemConvs)) elemPath := append(path.Copy(), nil) i := int64(0) it := val.ElementIterator() for it.Next() { _, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.NumberIntVal(i), } conv := elemConvs[i] if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } if val.IsNull() { val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) } elems = append(elems, val) i++ } if !cty.CanSetVal(elems) { return cty.NilVal, path.NewErrorf("element types must all match for conversion to set") } return cty.SetVal(elems), nil } } // conversionTupleToList returns a conversion that will take a value of the // given tuple type and return a list of the given element type. // // Will panic if the given tupleType isn't actually a tuple type. func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) conversion { tupleEtys := tupleType.TupleElementTypes() if len(tupleEtys) == 0 { // Empty tuple short-circuit return func(val cty.Value, path cty.Path) (cty.Value, error) { return cty.ListValEmpty(listEty.WithoutOptionalAttributesDeep()), nil } } if listEty == cty.DynamicPseudoType { // This is a special case where the caller wants us to find // a suitable single type that all elements can convert to, if // possible. listEty, _ = unify(tupleEtys, unsafe) if listEty == cty.NilType { return nil } // If the list element type after unification is still the dynamic // type, the only way this can result in a valid list is if all values // are of dynamic type if listEty == cty.DynamicPseudoType { for _, tupleEty := range tupleEtys { if !tupleEty.Equals(cty.DynamicPseudoType) { return nil } } } } elemConvs := make([]conversion, len(tupleEtys)) for i, tupleEty := range tupleEtys { if tupleEty.Equals(listEty) { // no conversion required continue } elemConvs[i] = getConversion(tupleEty, listEty, unsafe) if elemConvs[i] == nil { // If any of our element conversions are impossible, then the our // whole conversion is impossible. return nil } } // If we fall out here then a conversion is possible, using the // element conversions in elemConvs return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make([]cty.Value, 0, len(elemConvs)) elemTys := make([]cty.Type, 0, len(elems)) elemPath := append(path.Copy(), nil) i := int64(0) it := val.ElementIterator() for it.Next() { _, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.NumberIntVal(i), } conv := elemConvs[i] if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } elems = append(elems, val) elemTys = append(elemTys, val.Type()) i++ } elems, err := conversionUnifyListElements(elems, elemPath, unsafe) if err != nil { return cty.NilVal, err } if !cty.CanListVal(elems) { return cty.NilVal, path.NewErrorf("element types must all match for conversion to list") } return cty.ListVal(elems), nil } } // conversionObjectToMap returns a conversion that will take a value of the // given object type and return a map of the given element type. // // Will panic if the given objectType isn't actually an object type. func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) conversion { objectAtys := objectType.AttributeTypes() if len(objectAtys) == 0 { // Empty object short-circuit return func(val cty.Value, path cty.Path) (cty.Value, error) { return cty.MapValEmpty(mapEty.WithoutOptionalAttributesDeep()), nil } } if mapEty == cty.DynamicPseudoType { // This is a special case where the caller wants us to find // a suitable single type that all elements can convert to, if // possible. objectAtysList := make([]cty.Type, 0, len(objectAtys)) for _, aty := range objectAtys { objectAtysList = append(objectAtysList, aty) } mapEty, _ = unify(objectAtysList, unsafe) if mapEty == cty.NilType { return nil } } elemConvs := make(map[string]conversion, len(objectAtys)) for name, objectAty := range objectAtys { if objectAty.Equals(mapEty) { // no conversion required continue } elemConvs[name] = getConversion(objectAty, mapEty, unsafe) if elemConvs[name] == nil { // If any of our element conversions are impossible, then the our // whole conversion is impossible. return nil } } // If we fall out here then a conversion is possible, using the // element conversions in elemConvs return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make(map[string]cty.Value, len(elemConvs)) elemPath := append(path.Copy(), nil) it := val.ElementIterator() for it.Next() { name, val := it.Element() var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: name, } conv := elemConvs[name.AsString()] if conv != nil { val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } elems[name.AsString()] = val } if mapEty.IsCollectionType() || mapEty.IsObjectType() { var err error if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil { return cty.NilVal, err } } if !cty.CanMapVal(elems) { return cty.NilVal, path.NewErrorf("attribute types must all match for conversion to map") } return cty.MapVal(elems), nil } } // conversionMapToObject returns a conversion that will take a value of the // given map type and return an object of the given type. The object attribute // types must all be compatible with the map element type. // // Will panic if the given mapType and objType are not maps and objects // respectively. func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion { objectAtys := objType.AttributeTypes() mapEty := mapType.ElementType() elemConvs := make(map[string]conversion, len(objectAtys)) for name, objectAty := range objectAtys { if objectAty.Equals(mapEty) { // no conversion required continue } elemConvs[name] = getConversion(mapEty, objectAty, unsafe) if elemConvs[name] == nil { // This means that this conversion is impossible. Typically, we // would give up at this point and declare the whole conversion // impossible. But, if this attribute is optional then maybe we will // be able to do this conversion anyway provided the actual concrete // map doesn't have this value set. // // We only do this in "unsafe" mode, because we cannot guarantee // that the returned conversion will actually succeed once applied. if objType.AttributeOptional(name) && unsafe { // This attribute is optional, so let's leave this conversion in // as a nil, and we can error later if we actually have to // convert this. continue } // Otherwise, give up. This conversion is impossible as we have a // required attribute that doesn't match the map's inner type. return nil } } // If we fall out here then a conversion may be possible, using the // element conversions in elemConvs return func(val cty.Value, path cty.Path) (cty.Value, error) { elems := make(map[string]cty.Value, len(elemConvs)) elemPath := append(path.Copy(), nil) it := val.ElementIterator() for it.Next() { name, val := it.Element() // if there is no corresponding attribute, we skip this key if _, ok := objectAtys[name.AsString()]; !ok { continue } var err error elemPath[len(elemPath)-1] = cty.IndexStep{ Key: name, } // There are 3 cases here: // 1. This attribute is not in elemConvs // 2. This attribute is in elemConvs and is not nil // 3. This attribute is in elemConvs and is nil. // In case 1, we do not enter any of the branches below. This case // means the attribute type is the same between the map and the // object, and we don't need to do any conversion. if conv, ok := elemConvs[name.AsString()]; conv != nil { // This is case 2. The attribute type is different between the // map and the object, and we know how to convert between them. // So, we reset val to be the converted value and carry on. val, err = conv(val, elemPath) if err != nil { return cty.NilVal, err } } else if ok { // This is case 3 and it is an error. The attribute types are // different between the map and the object, but we cannot // convert between them. // // Now typically, this would be picked earlier on when we were // building elemConvs. However, in the case of optional // attributes there was a chance we could still convert the // overall object even if this particular attribute was not // convertable. This is because it could have not been set in // the map, and we could skip over it here and set a null value. // // Since we reached this branch, we know that map did actually // contain a non-convertable optional attribute. This means we // error. return cty.NilVal, path.NewErrorf("map element type is incompatible with attribute %q: %s", name.AsString(), MismatchMessage(val.Type(), objType.AttributeType(name.AsString()))) } if val.IsNull() { val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) } elems[name.AsString()] = val } for name, aty := range objectAtys { if _, exists := elems[name]; !exists { if optional := objType.AttributeOptional(name); optional { elems[name] = cty.NullVal(aty) } else { return cty.NilVal, path.NewErrorf("map has no element for required attribute %q", name) } } } return cty.ObjectVal(elems), nil } } func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) { elemTypes := make([]cty.Type, 0, len(elems)) for _, elem := range elems { elemTypes = append(elemTypes, elem.Type()) } unifiedType, _ := unify(elemTypes, unsafe) if unifiedType == cty.NilType { return nil, path.NewErrorf("cannot find a common base type for all elements") } unifiedElems := make(map[string]cty.Value) elemPath := append(path.Copy(), nil) for name, elem := range elems { if elem.Type().Equals(unifiedType) { unifiedElems[name] = elem continue } conv := getConversion(elem.Type(), unifiedType, unsafe) if conv == nil { } elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.StringVal(name), } val, err := conv(elem, elemPath) if err != nil { return nil, err } unifiedElems[name] = val } return unifiedElems, nil } func conversionUnifyListElements(elems []cty.Value, path cty.Path, unsafe bool) ([]cty.Value, error) { elemTypes := make([]cty.Type, len(elems)) for i, elem := range elems { elemTypes[i] = elem.Type() } unifiedType, _ := unify(elemTypes, unsafe) if unifiedType == cty.NilType { return nil, path.NewErrorf("cannot find a common base type for all elements") } ret := make([]cty.Value, len(elems)) elemPath := append(path.Copy(), nil) for i, elem := range elems { if elem.Type().Equals(unifiedType) { ret[i] = elem continue } conv := getConversion(elem.Type(), unifiedType, unsafe) if conv == nil { } elemPath[len(elemPath)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } val, err := conv(elem, elemPath) if err != nil { return nil, err } ret[i] = val } return ret, nil } go-cty-1.12.1/cty/convert/conversion_dynamic.go000066400000000000000000000106071433256746400215170ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // dynamicFixup deals with just-in-time conversions of values that were // input-typed as cty.DynamicPseudoType during analysis, ensuring that // we end up with the desired output type once the value is known, or // failing with an error if that is not possible. // // This is in the spirit of the cty philosophy of optimistically assuming that // DynamicPseudoType values will become the intended value eventually, and // dealing with any inconsistencies during final evaluation. func dynamicFixup(wantType cty.Type) conversion { return func(in cty.Value, path cty.Path) (cty.Value, error) { ret, err := Convert(in, wantType) if err != nil { // Re-wrap this error so that the returned path is relative // to the caller's original value, rather than relative to our // conversion value here. return cty.NilVal, path.NewError(err) } return ret, nil } } // dynamicPassthrough is an identity conversion that is used when the // target type is DynamicPseudoType, indicating that the caller doesn't care // which type is returned. func dynamicPassthrough(in cty.Value, path cty.Path) (cty.Value, error) { return in, nil } // dynamicReplace aims to return the out type unchanged, but if it finds a // dynamic type either directly or in any descendent elements it replaces them // with the equivalent type from in. // // This function assumes that in and out are compatible from a Convert // perspective, and will panic if it finds that they are not. For example if // in is an object and out is a map, this function will still attempt to iterate // through both as if they were the same. func dynamicReplace(in, out cty.Type) cty.Type { if in == cty.DynamicPseudoType || in == cty.NilType { // Short circuit this case, there's no point worrying about this if in // is a dynamic type or a nil type. Out is the best we can do. return out } switch { case out == cty.DynamicPseudoType: // So replace out with in. return in case out.IsPrimitiveType(), out.IsCapsuleType(): // out is not dynamic and it doesn't contain descendent elements so just // return it unchanged. return out case out.IsMapType(): var elemType cty.Type // Maps are compatible with other maps or objects. if in.IsMapType() { elemType = dynamicReplace(in.ElementType(), out.ElementType()) } if in.IsObjectType() { var types []cty.Type for _, t := range in.AttributeTypes() { types = append(types, t) } unifiedType, _ := unify(types, true) elemType = dynamicReplace(unifiedType, out.ElementType()) } return cty.Map(elemType) case out.IsObjectType(): // Objects are compatible with other objects and maps. outTypes := map[string]cty.Type{} if in.IsMapType() { for attr, attrType := range out.AttributeTypes() { outTypes[attr] = dynamicReplace(in.ElementType(), attrType) } } if in.IsObjectType() { for attr, attrType := range out.AttributeTypes() { if !in.HasAttribute(attr) { // If in does not have this attribute, then it is an // optional attribute and there is nothing we can do except // to return the type from out even if it is dynamic. outTypes[attr] = attrType continue } outTypes[attr] = dynamicReplace(in.AttributeType(attr), attrType) } } return cty.Object(outTypes) case out.IsSetType(): var elemType cty.Type // Sets are compatible with other sets, lists, tuples. if in.IsSetType() || in.IsListType() { elemType = dynamicReplace(in.ElementType(), out.ElementType()) } if in.IsTupleType() { unifiedType, _ := unify(in.TupleElementTypes(), true) elemType = dynamicReplace(unifiedType, out.ElementType()) } return cty.Set(elemType) case out.IsListType(): var elemType cty.Type // Lists are compatible with other lists, sets, and tuples. if in.IsSetType() || in.IsListType() { elemType = dynamicReplace(in.ElementType(), out.ElementType()) } if in.IsTupleType() { unifiedType, _ := unify(in.TupleElementTypes(), true) elemType = dynamicReplace(unifiedType, out.ElementType()) } return cty.List(elemType) case out.IsTupleType(): // Tuples are only compatible with other tuples var types []cty.Type for ix := 0; ix < len(out.TupleElementTypes()); ix++ { types = append(types, dynamicReplace(in.TupleElementType(ix), out.TupleElementType(ix))) } return cty.Tuple(types) default: panic("unrecognized type " + out.FriendlyName()) } } go-cty-1.12.1/cty/convert/conversion_object.go000066400000000000000000000055161433256746400213440ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // conversionObjectToObject returns a conversion that will make the input // object type conform to the output object type, if possible. // // Conversion is possible only if the output type is a subset of the input // type, meaning that each attribute of the output type has a corresponding // attribute in the input type where a recursive conversion is available. // // If the "out" type has any optional attributes, those attributes may be // absent in the "in" type, in which case null values will be used in their // place in the result. // // Shallow object conversions work the same for both safe and unsafe modes, // but the safety flag is passed on to recursive conversions and may thus // limit the above definition of "subset". func conversionObjectToObject(in, out cty.Type, unsafe bool) conversion { inAtys := in.AttributeTypes() outAtys := out.AttributeTypes() outOptionals := out.OptionalAttributes() attrConvs := make(map[string]conversion) for name, outAty := range outAtys { inAty, exists := inAtys[name] if !exists { if _, optional := outOptionals[name]; optional { // If it's optional then we'll skip inserting an // attribute conversion and then deal with inserting // the default value in our overall conversion logic // later. continue } // No conversion is available, then. return nil } if inAty.Equals(outAty) { // No conversion needed, but we'll still record the attribute // in our map for later reference. attrConvs[name] = nil continue } attrConvs[name] = getConversion(inAty, outAty, unsafe) if attrConvs[name] == nil { // If a recursive conversion isn't available, then our top-level // configuration is impossible too. return nil } } // If we get here then a conversion is possible, using the attribute // conversions given in attrConvs. return func(val cty.Value, path cty.Path) (cty.Value, error) { attrVals := make(map[string]cty.Value, len(attrConvs)) path = append(path, nil) pathStep := &path[len(path)-1] for it := val.ElementIterator(); it.Next(); { nameVal, val := it.Element() var err error name := nameVal.AsString() *pathStep = cty.GetAttrStep{ Name: name, } conv, exists := attrConvs[name] if !exists { continue } if conv != nil { val, err = conv(val, path) if err != nil { return cty.NilVal, err } } if val.IsNull() { // Strip optional attributes out of the embedded type for null // values. val = cty.NullVal(val.Type().WithoutOptionalAttributesDeep()) } attrVals[name] = val } for name := range outOptionals { if _, exists := attrVals[name]; !exists { wantTy := outAtys[name] attrVals[name] = cty.NullVal(wantTy.WithoutOptionalAttributesDeep()) } } return cty.ObjectVal(attrVals), nil } } go-cty-1.12.1/cty/convert/conversion_primitive.go000066400000000000000000000027131433256746400221020ustar00rootroot00000000000000package convert import ( "strings" "github.com/zclconf/go-cty/cty" ) var stringTrue = cty.StringVal("true") var stringFalse = cty.StringVal("false") var primitiveConversionsSafe = map[cty.Type]map[cty.Type]conversion{ cty.Number: { cty.String: func(val cty.Value, path cty.Path) (cty.Value, error) { f := val.AsBigFloat() return cty.StringVal(f.Text('f', -1)), nil }, }, cty.Bool: { cty.String: func(val cty.Value, path cty.Path) (cty.Value, error) { if val.True() { return stringTrue, nil } else { return stringFalse, nil } }, }, } var primitiveConversionsUnsafe = map[cty.Type]map[cty.Type]conversion{ cty.String: { cty.Number: func(val cty.Value, path cty.Path) (cty.Value, error) { v, err := cty.ParseNumberVal(val.AsString()) if err != nil { return cty.NilVal, path.NewErrorf("a number is required") } return v, nil }, cty.Bool: func(val cty.Value, path cty.Path) (cty.Value, error) { switch val.AsString() { case "true", "1": return cty.True, nil case "false", "0": return cty.False, nil default: switch strings.ToLower(val.AsString()) { case "true": return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"true\"") case "false": return cty.NilVal, path.NewErrorf("a bool is required; to convert from string, use lowercase \"false\"") default: return cty.NilVal, path.NewErrorf("a bool is required") } } }, }, } go-cty-1.12.1/cty/convert/conversion_tuple.go000066400000000000000000000034711433256746400212250ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // conversionTupleToTuple returns a conversion that will make the input // tuple type conform to the output tuple type, if possible. // // Conversion is possible only if the two tuple types have the same number // of elements and the corresponding elements by index can be converted. // // Shallow tuple conversions work the same for both safe and unsafe modes, // but the safety flag is passed on to recursive conversions and may thus // limit which element type conversions are possible. func conversionTupleToTuple(in, out cty.Type, unsafe bool) conversion { inEtys := in.TupleElementTypes() outEtys := out.TupleElementTypes() if len(inEtys) != len(outEtys) { return nil // no conversion is possible } elemConvs := make([]conversion, len(inEtys)) for i, outEty := range outEtys { inEty := inEtys[i] if inEty.Equals(outEty) { // No conversion needed, so we can leave this one nil. continue } elemConvs[i] = getConversion(inEty, outEty, unsafe) if elemConvs[i] == nil { // If a recursive conversion isn't available, then our top-level // configuration is impossible too. return nil } } // If we get here then a conversion is possible, using the element // conversions given in elemConvs. return func(val cty.Value, path cty.Path) (cty.Value, error) { elemVals := make([]cty.Value, len(elemConvs)) path = append(path, nil) pathStep := &path[len(path)-1] i := 0 for it := val.ElementIterator(); it.Next(); i++ { _, val := it.Element() var err error *pathStep = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } conv := elemConvs[i] if conv != nil { val, err = conv(val, path) if err != nil { return cty.NilVal, err } } elemVals[i] = val } return cty.TupleVal(elemVals), nil } } go-cty-1.12.1/cty/convert/doc.go000066400000000000000000000015551433256746400163750ustar00rootroot00000000000000// Package convert contains some routines for converting between cty types. // The intent of providing this package is to encourage applications using // cty to have consistent type conversion behavior for maximal interoperability // when Values pass from one application to another. // // The conversions are categorized into two categories. "Safe" conversions are // ones that are guaranteed to succeed if given a non-null value of the // appropriate source type. "Unsafe" conversions, on the other hand, are valid // for only a subset of input values, and thus may fail with an error when // called for values outside of that valid subset. // // The functions whose names end in Unsafe support all of the conversions that // are supported by the corresponding functions whose names do not have that // suffix, and then additional unsafe conversions as well. package convert go-cty-1.12.1/cty/convert/mismatch_msg.go000066400000000000000000000177701433256746400203110ustar00rootroot00000000000000package convert import ( "bytes" "fmt" "sort" "github.com/zclconf/go-cty/cty" ) // MismatchMessage is a helper to return an English-language description of // the differences between got and want, phrased as a reason why got does // not conform to want. // // This function does not itself attempt conversion, and so it should generally // be used only after a conversion has failed, to report the conversion failure // to an English-speaking user. The result will be confusing got is actually // conforming to or convertable to want. // // The shorthand helper function Convert uses this function internally to // produce its error messages, so callers of that function do not need to // also use MismatchMessage. // // This function is similar to Type.TestConformance, but it is tailored to // describing conversion failures and so the messages it generates relate // specifically to the conversion rules implemented in this package. func MismatchMessage(got, want cty.Type) string { switch { case got.IsObjectType() && want.IsObjectType(): // If both types are object types then we may be able to say something // about their respective attributes. return mismatchMessageObjects(got, want) case got.IsTupleType() && want.IsListType() && want.ElementType() == cty.DynamicPseudoType: // If conversion from tuple to list failed then it's because we couldn't // find a common type to convert all of the tuple elements to. return "all list elements must have the same type" case got.IsTupleType() && want.IsSetType() && want.ElementType() == cty.DynamicPseudoType: // If conversion from tuple to set failed then it's because we couldn't // find a common type to convert all of the tuple elements to. return "all set elements must have the same type" case got.IsObjectType() && want.IsMapType() && want.ElementType() == cty.DynamicPseudoType: // If conversion from object to map failed then it's because we couldn't // find a common type to convert all of the object attributes to. return "all map elements must have the same type" case (got.IsTupleType() || got.IsObjectType()) && want.IsCollectionType(): return mismatchMessageCollectionsFromStructural(got, want) case got.IsCollectionType() && want.IsCollectionType(): return mismatchMessageCollectionsFromCollections(got, want) default: // If we have nothing better to say, we'll just state what was required. return want.FriendlyNameForConstraint() + " required" } } func mismatchMessageObjects(got, want cty.Type) string { // Per our conversion rules, "got" is allowed to be a superset of "want", // and so we'll produce error messages here under that assumption. gotAtys := got.AttributeTypes() wantAtys := want.AttributeTypes() // If we find missing attributes then we'll report those in preference, // but if not then we will report a maximum of one non-conforming // attribute, just to keep our messages relatively terse. // We'll also prefer to report a recursive type error from an _unsafe_ // conversion over a safe one, because these are subjectively more // "serious". var missingAttrs []string var unsafeMismatchAttr string var safeMismatchAttr string for name, wantAty := range wantAtys { gotAty, exists := gotAtys[name] if !exists { if !want.AttributeOptional(name) { missingAttrs = append(missingAttrs, name) } continue } if gotAty.Equals(wantAty) { continue // exact match, so no problem } // We'll now try to convert these attributes in isolation and // see if we have a nested conversion error to report. // We'll try an unsafe conversion first, and then fall back on // safe if unsafe is possible. // If we already have an unsafe mismatch attr error then we won't bother // hunting for another one. if unsafeMismatchAttr != "" { continue } if conv := GetConversionUnsafe(gotAty, wantAty); conv == nil { unsafeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty)) } // If we already have a safe mismatch attr error then we won't bother // hunting for another one. if safeMismatchAttr != "" { continue } if conv := GetConversion(gotAty, wantAty); conv == nil { safeMismatchAttr = fmt.Sprintf("attribute %q: %s", name, MismatchMessage(gotAty, wantAty)) } } // We should now have collected at least one problem. If we have more than // one then we'll use our preference order to decide what is most important // to report. switch { case len(missingAttrs) != 0: sort.Strings(missingAttrs) switch len(missingAttrs) { case 1: return fmt.Sprintf("attribute %q is required", missingAttrs[0]) case 2: return fmt.Sprintf("attributes %q and %q are required", missingAttrs[0], missingAttrs[1]) default: sort.Strings(missingAttrs) var buf bytes.Buffer for _, name := range missingAttrs[:len(missingAttrs)-1] { fmt.Fprintf(&buf, "%q, ", name) } fmt.Fprintf(&buf, "and %q", missingAttrs[len(missingAttrs)-1]) return fmt.Sprintf("attributes %s are required", buf.Bytes()) } case unsafeMismatchAttr != "": return unsafeMismatchAttr case safeMismatchAttr != "": return safeMismatchAttr default: // We should never get here, but if we do then we'll return // just a generic message. return "incorrect object attributes" } } func mismatchMessageCollectionsFromStructural(got, want cty.Type) string { // First some straightforward cases where the kind is just altogether wrong. switch { case want.IsListType() && !got.IsTupleType(): return want.FriendlyNameForConstraint() + " required" case want.IsSetType() && !got.IsTupleType(): return want.FriendlyNameForConstraint() + " required" case want.IsMapType() && !got.IsObjectType(): return want.FriendlyNameForConstraint() + " required" } // If the kinds are matched well enough then we'll move on to checking // individual elements. wantEty := want.ElementType() switch { case got.IsTupleType(): for i, gotEty := range got.TupleElementTypes() { if gotEty.Equals(wantEty) { continue // exact match, so no problem } if conv := getConversion(gotEty, wantEty, true); conv != nil { continue // conversion is available, so no problem } return fmt.Sprintf("element %d: %s", i, MismatchMessage(gotEty, wantEty)) } // If we get down here then something weird is going on but we'll // return a reasonable fallback message anyway. return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint()) case got.IsObjectType(): for name, gotAty := range got.AttributeTypes() { if gotAty.Equals(wantEty) { continue // exact match, so no problem } if conv := getConversion(gotAty, wantEty, true); conv != nil { continue // conversion is available, so no problem } return fmt.Sprintf("element %q: %s", name, MismatchMessage(gotAty, wantEty)) } // If we get down here then something weird is going on but we'll // return a reasonable fallback message anyway. return fmt.Sprintf("all elements must be %s", wantEty.FriendlyNameForConstraint()) default: // Should not be possible to get here since we only call this function // with got as structural types, but... return want.FriendlyNameForConstraint() + " required" } } func mismatchMessageCollectionsFromCollections(got, want cty.Type) string { // First some straightforward cases where the kind is just altogether wrong. switch { case want.IsListType() && !(got.IsListType() || got.IsSetType()): return want.FriendlyNameForConstraint() + " required" case want.IsSetType() && !(got.IsListType() || got.IsSetType()): return want.FriendlyNameForConstraint() + " required" case want.IsMapType() && !got.IsMapType(): return want.FriendlyNameForConstraint() + " required" } // If the kinds are matched well enough then we'll check the element types. gotEty := got.ElementType() wantEty := want.ElementType() noun := "element type" switch { case want.IsListType(): noun = "list element type" case want.IsSetType(): noun = "set element type" case want.IsMapType(): noun = "map element type" } return fmt.Sprintf("incorrect %s: %s", noun, MismatchMessage(gotEty, wantEty)) } go-cty-1.12.1/cty/convert/mismatch_msg_test.go000066400000000000000000000060071433256746400213370ustar00rootroot00000000000000package convert import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestMismatchMessage(t *testing.T) { tests := []struct { GotType, WantType cty.Type WantMsg string }{ { cty.Bool, cty.Number, `number required`, }, { cty.EmptyObject, cty.Object(map[string]cty.Type{ "foo": cty.String, }), `attribute "foo" is required`, }, { cty.EmptyObject, cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }), `attributes "bar" and "foo" are required`, }, { cty.EmptyObject, cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, "baz": cty.String, }), `attributes "bar", "baz", and "foo" are required`, }, { cty.EmptyObject, cty.List(cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, "baz": cty.String, })), `list of object required`, }, { cty.List(cty.String), cty.List(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `incorrect list element type: object required`, }, { cty.List(cty.EmptyObject), cty.List(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `incorrect list element type: attribute "foo" is required`, }, { cty.Tuple([]cty.Type{cty.EmptyObject}), cty.List(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `element 0: attribute "foo" is required`, }, { cty.List(cty.EmptyObject), cty.Set(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `incorrect set element type: attribute "foo" is required`, }, { cty.Tuple([]cty.Type{cty.EmptyObject}), cty.Set(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `element 0: attribute "foo" is required`, }, { cty.Map(cty.EmptyObject), cty.Map(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `incorrect map element type: attribute "foo" is required`, }, { cty.Object(map[string]cty.Type{"boop": cty.EmptyObject}), cty.Map(cty.Object(map[string]cty.Type{ "foo": cty.String, })), `element "boop": attribute "foo" is required`, }, { cty.Tuple([]cty.Type{cty.EmptyObject, cty.EmptyTuple}), cty.List(cty.DynamicPseudoType), `all list elements must have the same type`, }, { cty.Object(map[string]cty.Type{ "foo": cty.Bool, "bar": cty.String, "baz": cty.Object(map[string]cty.Type{ "boop": cty.Number, }), }), cty.Object(map[string]cty.Type{ "foo": cty.Bool, "bar": cty.String, "baz": cty.Object(map[string]cty.Type{ "boop": cty.Number, "beep": cty.Bool, }), }), `attribute "baz": attribute "beep" is required`, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v but want %#v", test.GotType, test.WantType), func(t *testing.T) { got := MismatchMessage(test.GotType, test.WantType) if got != test.WantMsg { t.Errorf("wrong message\ngot type: %#v\nwant type: %#v\ngot message: %s\nwant message: %s", test.GotType, test.WantType, got, test.WantMsg) } }) } } go-cty-1.12.1/cty/convert/public.go000066400000000000000000000070021433256746400170770ustar00rootroot00000000000000package convert import ( "errors" "github.com/zclconf/go-cty/cty" ) // This file contains the public interface of this package, which is intended // to be a small, convenient interface designed for easy integration into // a hypothetical language type checker and interpreter. // Conversion is a named function type representing a conversion from a // value of one type to a value of another type. // // The source type for a conversion is always the source type given to // the function that returned the Conversion, but there is no way to recover // that from a Conversion value itself. If a Conversion is given a value // that is not of its expected type (with the exception of DynamicPseudoType, // which is always supported) then the function may panic or produce undefined // results. type Conversion func(in cty.Value) (out cty.Value, err error) // GetConversion returns a Conversion between the given in and out Types if // a safe one is available, or returns nil otherwise. func GetConversion(in cty.Type, out cty.Type) Conversion { return retConversion(getConversion(in, out, false)) } // GetConversionUnsafe returns a Conversion between the given in and out Types // if either a safe or unsafe one is available, or returns nil otherwise. func GetConversionUnsafe(in cty.Type, out cty.Type) Conversion { return retConversion(getConversion(in, out, true)) } // Convert returns the result of converting the given value to the given type // if an safe or unsafe conversion is available, or returns an error if such a // conversion is impossible. // // This is a convenience wrapper around calling GetConversionUnsafe and then // immediately passing the given value to the resulting function. func Convert(in cty.Value, want cty.Type) (cty.Value, error) { if in.Type().Equals(want.WithoutOptionalAttributesDeep()) { return in, nil } conv := GetConversionUnsafe(in.Type(), want) if conv == nil { return cty.NilVal, errors.New(MismatchMessage(in.Type(), want)) } return conv(in) } // Unify attempts to find the most general type that can be converted from // all of the given types. If this is possible, that type is returned along // with a slice of necessary conversions for some of the given types. // // If no common supertype can be found, this function returns cty.NilType and // a nil slice. // // If a common supertype *can* be found, the returned slice will always be // non-nil and will contain a non-nil conversion for each given type that // needs to be converted, with indices corresponding to the input slice. // Any given type that does *not* need conversion (because it is already of // the appropriate type) will have a nil Conversion. // // cty.DynamicPseudoType is, as usual, a special case. If the given type list // contains a mixture of dynamic and non-dynamic types, the dynamic types are // disregarded for type selection and a conversion is returned for them that // will attempt a late conversion of the given value to the target type, // failing with a conversion error if the eventual concrete type is not // compatible. If *all* given types are DynamicPseudoType, or in the // degenerate case of an empty slice of types, the returned type is itself // cty.DynamicPseudoType and no conversions are attempted. func Unify(types []cty.Type) (cty.Type, []Conversion) { return unify(types, false) } // UnifyUnsafe is the same as Unify except that it may return unsafe // conversions in situations where a safe conversion isn't also available. func UnifyUnsafe(types []cty.Type) (cty.Type, []Conversion) { return unify(types, true) } go-cty-1.12.1/cty/convert/public_test.go000066400000000000000000001254451433256746400201520ustar00rootroot00000000000000package convert import ( "fmt" "math/big" "strings" "testing" "github.com/zclconf/go-cty/cty" ) func TestConvert(t *testing.T) { tests := []struct { Value cty.Value Type cty.Type Want cty.Value WantError string }{ { Value: cty.StringVal("hello"), Type: cty.String, Want: cty.StringVal("hello"), }, { Value: cty.StringVal("1"), Type: cty.Number, Want: cty.NumberIntVal(1), }, { Value: cty.StringVal("1.5"), Type: cty.Number, Want: cty.NumberFloatVal(1.5), }, { Value: cty.StringVal("hello"), Type: cty.Number, WantError: `a number is required`, }, { Value: cty.StringVal("true"), Type: cty.Bool, Want: cty.True, }, { Value: cty.StringVal("1"), Type: cty.Bool, Want: cty.True, }, { Value: cty.StringVal("false"), Type: cty.Bool, Want: cty.False, }, { Value: cty.StringVal("0"), Type: cty.Bool, Want: cty.False, }, { Value: cty.StringVal("hello"), Type: cty.Bool, WantError: `a bool is required`, }, { Value: cty.NumberIntVal(4), Type: cty.String, Want: cty.StringVal("4"), }, { Value: cty.NumberFloatVal(3.14159265359), Type: cty.String, Want: cty.StringVal("3.14159265359"), }, { Value: cty.True, Type: cty.String, Want: cty.StringVal("true"), }, { Value: cty.False, Type: cty.String, Want: cty.StringVal("false"), }, { Value: cty.UnknownVal(cty.String), Type: cty.Number, Want: cty.UnknownVal(cty.Number), }, { Value: cty.UnknownVal(cty.Number), Type: cty.String, Want: cty.UnknownVal(cty.String), }, { Value: cty.DynamicVal, Type: cty.String, Want: cty.UnknownVal(cty.String), }, { Value: cty.StringVal("hello"), Type: cty.DynamicPseudoType, Want: cty.StringVal("hello"), }, { Value: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), Type: cty.List(cty.String), Want: cty.ListVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("10"), }), }, { Value: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), }, { Value: cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "type": cty.StringVal("ingress"), "from_port": cty.NumberIntVal(-1), "to_port": cty.NumberIntVal(-1), "protocol": cty.StringVal("icmp"), "description": cty.StringVal("ICMP in"), "cidr": cty.TupleVal([]cty.Value{cty.StringVal("0.0.0.0/0")}), }), cty.ObjectVal(map[string]cty.Value{ "type": cty.StringVal("ingress"), "from_port": cty.NumberIntVal(22), "to_port": cty.NumberIntVal(22), "protocol": cty.StringVal("tcp"), "description": cty.StringVal("SSH from Bastion"), "source_sg": cty.StringVal("sg-abc123"), }), }), Type: cty.List(cty.DynamicPseudoType), WantError: `all list elements must have the same type`, }, { Value: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.UnknownVal(cty.String), }), Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{cty.NumberIntVal(5), cty.UnknownVal(cty.Number)}), }, { Value: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("10"), }), Type: cty.List(cty.String), Want: cty.ListVal([]cty.Value{ // NOTE: This results depends on the traversal order of the // set, which may change if the set implementation changes. cty.StringVal("10"), cty.StringVal("5"), }), }, { Value: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("10"), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ // NOTE: This results depends on the traversal order of the // set, which may change if the set implementation changes. cty.StringVal("10"), cty.StringVal("5"), }), }, { Value: cty.SetVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), Type: cty.List(cty.String), Want: cty.ListVal([]cty.Value{ // NOTE: This results depends on the traversal order of the // set, which may change if the set implementation changes. cty.StringVal("5"), cty.StringVal("10"), }), }, { Value: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.UnknownVal(cty.String), }), Type: cty.List(cty.String), Want: cty.UnknownVal(cty.List(cty.String)), }, { Value: cty.SetVal([]cty.Value{ cty.UnknownVal(cty.String), }), Type: cty.List(cty.String), // We get a known list value this time because even though we // don't know the single value that's in the list, we _do_ know // that there are no other values in the set for it to coalesce // with. Want: cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), }), }, { Value: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), cty.NumberIntVal(10), }), Type: cty.Set(cty.String), Want: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("10"), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NumberIntVal(5), cty.StringVal("hello"), }), Type: cty.List(cty.String), Want: cty.ListVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("hello"), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NumberIntVal(5), cty.StringVal("12"), }), Type: cty.List(cty.Number), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(12), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(10), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NumberIntVal(5), cty.StringVal("hello"), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("hello"), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NumberIntVal(5), cty.StringVal("hello"), }), Type: cty.Set(cty.DynamicPseudoType), Want: cty.SetVal([]cty.Value{ cty.StringVal("5"), cty.StringVal("hello"), }), }, { Value: cty.ListValEmpty(cty.String), Type: cty.Set(cty.DynamicPseudoType), Want: cty.SetValEmpty(cty.String), }, { Value: cty.SetValEmpty(cty.String), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListValEmpty(cty.String), }, { Value: cty.ObjectVal(map[string]cty.Value{ "num": cty.NumberIntVal(5), "str": cty.StringVal("hello"), }), Type: cty.Map(cty.String), Want: cty.MapVal(map[string]cty.Value{ "num": cty.StringVal("5"), "str": cty.StringVal("hello"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "num": cty.NumberIntVal(5), "str": cty.StringVal("12"), }), Type: cty.Map(cty.Number), Want: cty.MapVal(map[string]cty.Value{ "num": cty.NumberIntVal(5), "str": cty.NumberIntVal(12), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "num1": cty.NumberIntVal(5), "num2": cty.NumberIntVal(10), }), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{ "num1": cty.NumberIntVal(5), "num2": cty.NumberIntVal(10), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "num": cty.NumberIntVal(5), "str": cty.StringVal("hello"), }), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{ "num": cty.StringVal("5"), "str": cty.StringVal("hello"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "list": cty.ListValEmpty(cty.Bool), "tuple": cty.EmptyTupleVal, }), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{ "list": cty.ListValEmpty(cty.Bool), "tuple": cty.ListValEmpty(cty.Bool), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "map": cty.MapValEmpty(cty.String), "obj": cty.EmptyObjectVal, }), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{ "map": cty.MapValEmpty(cty.String), "obj": cty.MapValEmpty(cty.String), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "num": cty.NumberIntVal(5), "bool": cty.True, }), Type: cty.Map(cty.DynamicPseudoType), WantError: `all map elements must have the same type`, }, { Value: cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), }, { Value: cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), Type: cty.Object(map[string]cty.Type{ "greeting": cty.String, "name": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), }, { Value: cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), Type: cty.Object(map[string]cty.Type{ "greeting": cty.List(cty.String), "name": cty.String, }), WantError: `object required`, // FIXME: should be something like "attribute greeting: must be a list" }, { Value: cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("Hello"), "name": cty.StringVal("John"), }), Type: cty.Object(map[string]cty.Type{ "name": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("John"), }), }, { Value: cty.MapVal(map[string]cty.Value{ "name": cty.StringVal("John"), }), Type: cty.Object(map[string]cty.Type{ "name": cty.String, "greeting": cty.String, }), WantError: `map has no element for required attribute "greeting"`, }, { Value: cty.MapVal(map[string]cty.Value{ "name": cty.StringVal("John"), }), Type: cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "name": cty.String, "greeting": cty.String, }, []string{"greeting"}, ), Want: cty.ObjectVal(map[string]cty.Value{ "greeting": cty.NullVal(cty.String), "name": cty.StringVal("John"), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.NumberIntVal(2), "b": cty.NumberIntVal(5), }), Type: cty.Map(cty.String), Want: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("2"), "b": cty.StringVal("5"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("foo value"), "bar": cty.StringVal("bar value"), }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("foo value"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.True, }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("true"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.DynamicVal, }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.UnknownVal(cty.String), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.NullVal(cty.String), }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.NullVal(cty.String), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.True, }), Type: cty.Object(map[string]cty.Type{ "foo": cty.DynamicPseudoType, }), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.True, }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bar value"), }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, }), WantError: `attribute "foo" is required`, }, { Value: cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bar value"), }), Type: cty.Object(map[string]cty.Type{ "foo": cty.String, "baz": cty.String, }), WantError: `attributes "baz" and "foo" are required`, }, { Value: cty.EmptyObjectVal, Type: cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, "baz": cty.String, }), WantError: `attributes "bar", "baz", and "foo" are required`, }, { Value: cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bar value"), }), Type: cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }, []string{"foo"}, ), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.NullVal(cty.String), "bar": cty.StringVal("bar value"), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("foo value"), "bar": cty.StringVal("bar value"), }), Type: cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }, []string{"foo"}, ), Want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("foo value"), "bar": cty.StringVal("bar value"), }), }, { Value: cty.EmptyObjectVal, Type: cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }, []string{"foo"}, ), WantError: `attribute "bar" is required`, }, { Value: cty.NullVal(cty.DynamicPseudoType), Type: cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }, []string{"foo"}, ), Want: cty.NullVal(cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, })), }, { Value: cty.ListVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bar value"), }), }), Type: cty.List(cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "foo": cty.String, "bar": cty.String, }, []string{"foo"}, )), Want: cty.ListVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "foo": cty.String, "bar": cty.String, })), cty.ObjectVal(map[string]cty.Value{ "foo": cty.NullVal(cty.String), "bar": cty.StringVal("bar value"), }), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.True, }), Type: cty.Object(map[string]cty.Type{ "foo": cty.Number, }), WantError: `attribute "foo": number required`, }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.UnknownVal(cty.Bool), }), Type: cty.Object(map[string]cty.Type{ "foo": cty.Number, }), WantError: `attribute "foo": number required`, }, { Value: cty.NullVal(cty.String), Type: cty.DynamicPseudoType, Want: cty.NullVal(cty.String), }, { Value: cty.UnknownVal(cty.String), Type: cty.DynamicPseudoType, Want: cty.UnknownVal(cty.String), }, { Value: cty.TupleVal([]cty.Value{ cty.StringVal("hello"), }), Type: cty.Tuple([]cty.Type{ cty.String, }), Want: cty.TupleVal([]cty.Value{ cty.StringVal("hello"), }), }, { Value: cty.TupleVal([]cty.Value{ cty.True, }), Type: cty.Tuple([]cty.Type{ cty.String, }), Want: cty.TupleVal([]cty.Value{ cty.StringVal("true"), }), }, { Value: cty.TupleVal([]cty.Value{ cty.True, }), Type: cty.EmptyTuple, WantError: `tuple required`, // FIXME: this error is not descriptive enough }, { Value: cty.EmptyTupleVal, Type: cty.Tuple([]cty.Type{ cty.String, }), WantError: `tuple required`, // FIXME: this error is not descriptive enough }, { Value: cty.EmptyTupleVal, Type: cty.Set(cty.String), Want: cty.SetValEmpty(cty.String), }, // Marks on values should propagate, even deeply. { Value: cty.StringVal("hello").Mark(1), Type: cty.String, Want: cty.StringVal("hello").Mark(1), }, { Value: cty.StringVal("true").Mark(1), Type: cty.Bool, Want: cty.True.Mark(1), }, { Value: cty.TupleVal([]cty.Value{cty.StringVal("hello").Mark(1)}), Type: cty.List(cty.String), Want: cty.ListVal([]cty.Value{cty.StringVal("hello").Mark(1)}), }, { Value: cty.SetVal([]cty.Value{ cty.StringVal("hello").Mark(1), cty.StringVal("hello").Mark(2), }), Type: cty.Set(cty.String), Want: cty.SetVal([]cty.Value{cty.StringVal("hello")}).WithMarks(cty.NewValueMarks(1, 2)), }, { Value: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello").Mark(1)}), Type: cty.Map(cty.String), Want: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello").Mark(1)}), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello").Mark(1), "bar": cty.StringVal("world").Mark(1), }), Type: cty.Object(map[string]cty.Type{"foo": cty.String}), Want: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello").Mark(1)}), }, { Value: cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello"), "bar": cty.StringVal("world").Mark(1), }), Type: cty.Object(map[string]cty.Type{"foo": cty.String}), Want: cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello")}), }, // reduction of https://github.com/hashicorp/terraform/issues/23804 { Value: cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "x": cty.TupleVal([]cty.Value{cty.StringVal("foo")}), }), "b": cty.ObjectVal(map[string]cty.Value{ "x": cty.TupleVal([]cty.Value{cty.StringVal("bar")}), }), "c": cty.ObjectVal(map[string]cty.Value{ "x": cty.TupleVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), }), }), Type: cty.Map(cty.Map(cty.DynamicPseudoType)), Want: cty.MapVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "x": cty.ListVal([]cty.Value{cty.StringVal("foo")}), }), "b": cty.MapVal(map[string]cty.Value{ "x": cty.ListVal([]cty.Value{cty.StringVal("bar")}), }), "c": cty.MapVal(map[string]cty.Value{ "x": cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar")}), }), }), }, // reduction of https://github.com/hashicorp/terraform/issues/24167 { Value: cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "x": cty.NullVal(cty.DynamicPseudoType), }), "b": cty.ObjectVal(map[string]cty.Value{ "x": cty.ObjectVal(map[string]cty.Value{ "c": cty.NumberIntVal(1), "d": cty.NumberIntVal(2), }), }), }), Type: cty.Map( cty.Map( cty.Object(map[string]cty.Type{ "x": cty.Map(cty.DynamicPseudoType), }), ), ), WantError: `element "b": element "x": attribute "x" is required`, }, // reduction of https://github.com/hashicorp/terraform/issues/23431 { Value: cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "x": cty.StringVal("foo"), }), "b": cty.MapValEmpty(cty.DynamicPseudoType), }), Type: cty.Map(cty.Map(cty.DynamicPseudoType)), Want: cty.MapVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "x": cty.StringVal("foo"), }), "b": cty.MapValEmpty(cty.String), }), }, // reduction of https://github.com/hashicorp/terraform/issues/27269 { Value: cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.DynamicPseudoType), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "b": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }), }), }), }), Type: cty.List(cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "b": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "c": cty.String, "d": cty.String, }, []string{"d"})), }), })), Want: cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.Object(map[string]cty.Type{ "b": cty.List(cty.Object(map[string]cty.Type{ "c": cty.String, "d": cty.String, })), })), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "b": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "c": cty.StringVal("d"), "d": cty.NullVal(cty.String), }), }), }), }), }), }, // When converting null values into nested types which include objects // with optional attributes, we expect the resulting value to be of a // recursively concretized type. { Value: cty.NullVal(cty.DynamicPseudoType), Type: cty.Object( map[string]cty.Type{ "foo": cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "bar": cty.String, }, []string{"bar"}, ), }, ), Want: cty.NullVal(cty.Object(map[string]cty.Type{ "foo": cty.Object(map[string]cty.Type{ "bar": cty.String, }), })), }, // The same nested optional attributes flattening should happen for // unknown values, too. { Value: cty.UnknownVal(cty.DynamicPseudoType), Type: cty.Object( map[string]cty.Type{ "foo": cty.ObjectWithOptionalAttrs( map[string]cty.Type{ "bar": cty.String, }, []string{"bar"}, ), }, ), Want: cty.UnknownVal(cty.Object(map[string]cty.Type{ "foo": cty.Object(map[string]cty.Type{ "bar": cty.String, }), })), }, // https://github.com/hashicorp/terraform/issues/21588: { Value: cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.EmptyObjectVal, "b": cty.NumberIntVal(2), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{"var1": cty.StringVal("val1")}), "b": cty.StringVal("2"), }), }), Type: cty.List(cty.Object(map[string]cty.Type{ "a": cty.DynamicPseudoType, "b": cty.String, })), Want: cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.MapValEmpty(cty.String), "b": cty.StringVal("2"), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{"var1": cty.StringVal("val1")}), "b": cty.StringVal("2"), }), }), }, // https://github.com/hashicorp/terraform/issues/24377: { Value: cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("a")}), cty.StringVal("b"), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.Set(cty.DynamicPseudoType), WantError: `all set elements must have the same type`, }, { Value: cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("a")}), cty.StringVal("b"), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.List(cty.DynamicPseudoType), WantError: `all list elements must have the same type`, }, { Value: cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("a")}), cty.StringVal("b"), }), Type: cty.Set(cty.DynamicPseudoType), WantError: `all set elements must have the same type`, }, { Value: cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("a")}), cty.StringVal("b"), }), Type: cty.List(cty.DynamicPseudoType), WantError: `all list elements must have the same type`, }, { Value: cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.NumberIntVal(9), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.Set(cty.DynamicPseudoType), Want: cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("9"), cty.NullVal(cty.DynamicPseudoType), }), }, { Value: cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.NumberIntVal(9), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("9"), cty.NullVal(cty.DynamicPseudoType), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.Set(cty.DynamicPseudoType), Want: cty.SetVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), }), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), cty.NullVal(cty.DynamicPseudoType), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("boop"), // It's okay to use a map of string to convert to this // target type as long as the source map does not include // any of the optional attributes that cannot be assigned // from a string. }), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.String, "c": cty.Object(map[string]cty.Type{ "d": cty.String, }), }, []string{"b", "c"}), Want: cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("boop"), "b": cty.NullVal(cty.String), "c": cty.NullVal(cty.Object(map[string]cty.Type{ "d": cty.String, })), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("boop"), }), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.String, "c": cty.Object(map[string]cty.Type{ "d": cty.DynamicPseudoType, }), }, []string{"b", "c"}), Want: cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("boop"), "b": cty.NullVal(cty.String), "c": cty.NullVal(cty.Object(map[string]cty.Type{ "d": cty.DynamicPseudoType, })), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("boop"), }), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.String, "c": cty.DynamicPseudoType, }, []string{"b", "c"}), Want: cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("boop"), "b": cty.NullVal(cty.String), "c": cty.NullVal(cty.DynamicPseudoType), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("boop"), // This case is invalid, because an element of a map of // string cannot be assigned to an object-typed attribute. "c": cty.StringVal("foobar"), }), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.String, "c": cty.Object(map[string]cty.Type{ "d": cty.String, }), }, []string{"b", "c"}), WantError: `map element type is incompatible with attribute "c": object required`, }, { Value: cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(10)), "c": cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("foo"), "b": cty.BoolVal(true), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(5)), "c": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.Bool, }, []string{"b"})), }), }), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "c": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.Bool, }, []string{"b"}), "d": cty.Number, }, []string{"c"})), Want: cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(10)), "c": cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("foo"), "b": cty.BoolVal(true), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(5)), "c": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Bool, })), }), }), }, { Value: cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(10)), "c": cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("foo"), "b": cty.BoolVal(true), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(5)), }), }), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "c": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.Bool, }, []string{"b"}), "d": cty.Number, }, []string{"c"})), Want: cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(10)), "c": cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("foo"), "b": cty.BoolVal(true), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberVal(big.NewFloat(5)), "c": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Bool, })), }), }), }, { Value: cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("boop"), }), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, "b": cty.String, "c": cty.Object(map[string]cty.Type{ "d": cty.String, }), }, []string{"b", "c"}), Want: cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("boop"), "b": cty.NullVal(cty.String), "c": cty.NullVal(cty.Object(map[string]cty.Type{ "d": cty.String, })), }), }, { Value: cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "xs": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), cty.ObjectVal(map[string]cty.Value{ "xs": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), Type: cty.List(cty.Object(map[string]cty.Type{ "xs": cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "x": cty.Number, }, []string{"x"})), })), Want: cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "xs": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), cty.ObjectVal(map[string]cty.Value{ "xs": cty.ListValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), }, { Value: cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "xs": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), cty.ObjectVal(map[string]cty.Value{ "xs": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), Type: cty.Set(cty.Object(map[string]cty.Type{ "xs": cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "x": cty.Number, }, []string{"x"})), })), Want: cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "xs": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), cty.ObjectVal(map[string]cty.Value{ "xs": cty.SetValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), }, { Value: cty.MapVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "xs": cty.MapVal(map[string]cty.Value{ "nested_foo": cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), "bar": cty.ObjectVal(map[string]cty.Value{ "xs": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), Type: cty.Map(cty.Object(map[string]cty.Type{ "xs": cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "x": cty.Number, }, []string{"x"})), })), Want: cty.MapVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "xs": cty.MapVal(map[string]cty.Value{ "nested_foo": cty.ObjectVal(map[string]cty.Value{ "x": cty.NumberVal(big.NewFloat(1234)), }), }), }), "bar": cty.ObjectVal(map[string]cty.Value{ "xs": cty.MapValEmpty(cty.Object(map[string]cty.Type{ "x": cty.Number, })), })}, ), }, // We should strip optional attributes out of empty sets, maps, lists, // and tuples. { Value: cty.ListValEmpty(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.SetValEmpty(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, { Value: cty.EmptyTupleVal, Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.SetValEmpty(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, { Value: cty.SetValEmpty(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.ListValEmpty(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, { Value: cty.EmptyTupleVal, Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.ListValEmpty(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, { Value: cty.EmptyObjectVal, Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.MapValEmpty(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, { Value: cty.MapValEmpty(cty.String), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}), Want: cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.String), }), }, // We should strip optional attributes out of null sets, maps, lists, // and tuples. { Value: cty.NullVal(cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}))), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ "a": cty.String, }))), }, { Value: cty.NullVal(cty.EmptyTuple), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{ "a": cty.String, }))), }, { Value: cty.NullVal(cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}))), Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ "a": cty.String, }))), }, { Value: cty.NullVal(cty.EmptyTuple), Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.NullVal(cty.List(cty.Object(map[string]cty.Type{ "a": cty.String, }))), }, { Value: cty.NullVal(cty.EmptyObject), Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ "a": cty.String, }))), }, { Value: cty.NullVal(cty.Map(cty.String)), Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}), Want: cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, // We should strip optional attributes out of null values in sets, maps, // lists and tuples. { Value: cty.ListVal([]cty.Value{ cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.SetVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.Set(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.SetVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.SetVal([]cty.Value{ cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.ListVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.ListVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.ObjectVal(map[string]cty.Value{ "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.Object(map[string]cty.Type{ "object": cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}), }), Want: cty.ObjectVal(map[string]cty.Value{ "object": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.Number, }, []string{"a"})), }), Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, { Value: cty.TupleVal([]cty.Value{ cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.Number, }, []string{"a"})), }), Type: cty.Tuple([]cty.Type{ cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"}), }), Want: cty.TupleVal([]cty.Value{ cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, // Collections should prefer concrete types over dynamic types. { Value: cty.ListValEmpty(cty.Number), Type: cty.List(cty.DynamicPseudoType), Want: cty.ListValEmpty(cty.Number), }, { Value: cty.NullVal(cty.List(cty.Number)), Type: cty.List(cty.DynamicPseudoType), Want: cty.NullVal(cty.List(cty.Number)), }, { Value: cty.NullVal(cty.List(cty.Number)), Type: cty.Set(cty.DynamicPseudoType), Want: cty.NullVal(cty.Set(cty.Number)), }, { Value: cty.MapValEmpty(cty.Number), Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapValEmpty(cty.Number), }, { Value: cty.NullVal(cty.Map(cty.Number)), Type: cty.Map(cty.DynamicPseudoType), Want: cty.NullVal(cty.Map(cty.Number)), }, { Value: cty.NullVal(cty.Map(cty.Number)), Type: cty.Object(map[string]cty.Type{ "a": cty.DynamicPseudoType, }), Want: cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.Number, })), }, { Value: cty.SetValEmpty(cty.Number), Type: cty.Set(cty.DynamicPseudoType), Want: cty.SetValEmpty(cty.Number), }, { Value: cty.NullVal(cty.Set(cty.Number)), Type: cty.Set(cty.DynamicPseudoType), Want: cty.NullVal(cty.Set(cty.Number)), }, { Value: cty.NullVal(cty.Set(cty.Number)), Type: cty.List(cty.DynamicPseudoType), Want: cty.NullVal(cty.List(cty.Number)), }, { Value: cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), Type: cty.Map(cty.DynamicPseudoType), Want: cty.NullVal(cty.Map(cty.String)), }, { Value: cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "b": cty.String, }), })), Type: cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "b": cty.DynamicPseudoType, }), }), Want: cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "b": cty.String, }), })), }, { Value: cty.NullVal(cty.Tuple([]cty.Type{ cty.String, })), Type: cty.Tuple([]cty.Type{ cty.DynamicPseudoType, }), Want: cty.NullVal(cty.Tuple([]cty.Type{ cty.String, })), }, // We should strip optional attributes out of types even if they match. { Value: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), }), Type: cty.Map(cty.ObjectWithOptionalAttrs(map[string]cty.Type{ "a": cty.String, }, []string{"a"})), Want: cty.MapVal(map[string]cty.Value{ "object": cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.String, })), }), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v to %#v", test.Value, test.Type), func(t *testing.T) { got, err := Convert(test.Value, test.Type) switch { case test.WantError != "": if err == nil { t.Errorf("conversion succeeded with %#v; want error", got) } else { if got, want := errorStrForTesting(err), test.WantError; got != want { t.Errorf("wrong error\ngot: %s\nwant: %s", got, want) } } default: if err != nil { t.Fatalf("conversion failed: %s", err) } if !got.RawEquals(test.Want) { t.Errorf( "wrong result\nvalue: %#v\ntype: %#v\ngot: %#v\nwant: %#v", test.Value, test.Type, got, test.Want, ) } } }) } } func errorStrForTesting(err error) string { switch err := err.(type) { case cty.PathError: if pathStr := pathStrForTesting(err.Path); pathStr != "" { return pathStr + ": " + err.Error() } return err.Error() default: return err.Error() } } func pathStrForTesting(path cty.Path) string { if len(path) == 0 { return "" } var buf strings.Builder for _, step := range path { switch step := step.(type) { case cty.GetAttrStep: fmt.Fprintf(&buf, ".%s", step.Name) case cty.IndexStep: fmt.Fprintf(&buf, "[%#v]", step.Key) default: fmt.Fprintf(&buf, "", step) } } return buf.String() } go-cty-1.12.1/cty/convert/sort_types.go000066400000000000000000000034361433256746400200430ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // sortTypes produces an ordering of the given types that serves as a // preference order for the result of unification of the given types. // The return value is a slice of indices into the given slice, and will // thus always be the same length as the given slice. // // The goal is that the most general of the given types will appear first // in the ordering. If there are uncomparable pairs of types in the list // then they will appear in an undefined order, and the unification pass // will presumably then fail. func sortTypes(tys []cty.Type) []int { l := len(tys) // First we build a graph whose edges represent "more general than", // which we will then do a topological sort of. edges := make([][]int, l) for i := 0; i < (l - 1); i++ { for j := i + 1; j < l; j++ { cmp := compareTypes(tys[i], tys[j]) switch { case cmp < 0: edges[i] = append(edges[i], j) case cmp > 0: edges[j] = append(edges[j], i) } } } // Compute the in-degree of each node inDegree := make([]int, l) for _, outs := range edges { for _, j := range outs { inDegree[j]++ } } // The array backing our result will double as our queue for visiting // the nodes, with the queue slice moving along this array until it // is empty and positioned at the end of the array. Thus our visiting // order is also our result order. result := make([]int, l) queue := result[0:0] // Initialize the queue with any item of in-degree 0, preserving // their relative order. for i, n := range inDegree { if n == 0 { queue = append(queue, i) } } for len(queue) != 0 { i := queue[0] queue = queue[1:] for _, j := range edges[i] { inDegree[j]-- if inDegree[j] == 0 { queue = append(queue, j) } } } return result } go-cty-1.12.1/cty/convert/sort_types_test.go000066400000000000000000000042351433256746400211000ustar00rootroot00000000000000package convert import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestSortTypes(t *testing.T) { tests := []struct { Input []cty.Type Want []cty.Type }{ { []cty.Type{}, []cty.Type{}, }, { []cty.Type{cty.String, cty.Number}, []cty.Type{cty.String, cty.Number}, }, { []cty.Type{cty.Number, cty.String}, []cty.Type{cty.String, cty.Number}, }, { []cty.Type{cty.String, cty.Bool}, []cty.Type{cty.String, cty.Bool}, }, { []cty.Type{cty.Bool, cty.String}, []cty.Type{cty.String, cty.Bool}, }, { []cty.Type{cty.Bool, cty.String, cty.Number}, []cty.Type{cty.String, cty.Bool, cty.Number}, }, { []cty.Type{cty.Number, cty.String, cty.Bool}, []cty.Type{cty.String, cty.Number, cty.Bool}, }, { []cty.Type{cty.String, cty.String}, []cty.Type{cty.String, cty.String}, }, { []cty.Type{cty.Number, cty.String, cty.Number}, []cty.Type{cty.String, cty.Number, cty.Number}, }, { []cty.Type{cty.String, cty.List(cty.String)}, []cty.Type{cty.String, cty.List(cty.String)}, }, { []cty.Type{cty.List(cty.String), cty.String}, []cty.Type{cty.List(cty.String), cty.String}, }, { // This result is somewhat arbitrary, but the important thing // is that it is consistent. []cty.Type{cty.Bool, cty.List(cty.String), cty.String}, []cty.Type{cty.List(cty.String), cty.String, cty.Bool}, }, { []cty.Type{cty.String, cty.DynamicPseudoType}, []cty.Type{cty.String, cty.DynamicPseudoType}, }, { []cty.Type{cty.DynamicPseudoType, cty.String}, []cty.Type{cty.String, cty.DynamicPseudoType}, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { idxs := sortTypes(test.Input) if len(idxs) != len(test.Input) { t.Fatalf("wrong number of indexes %q; want %q", len(idxs), len(test.Input)) } got := make([]cty.Type, len(idxs)) for i, idx := range idxs { got[i] = test.Input[idx] } for i := range test.Want { if !got[i].Equals(test.Want[i]) { t.Errorf( "wrong order\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) break } } }) } } go-cty-1.12.1/cty/convert/unify.go000066400000000000000000000353111433256746400167570ustar00rootroot00000000000000package convert import ( "github.com/zclconf/go-cty/cty" ) // The current unify implementation is somewhat inefficient, but we accept this // under the assumption that it will generally be used with small numbers of // types and with types of reasonable complexity. However, it does have a // "happy path" where all of the given types are equal. // // This function is likely to have poor performance in cases where any given // types are very complex (lots of deeply-nested structures) or if the list // of types itself is very large. In particular, it will walk the nested type // structure under the given types several times, especially when given a // list of types for which unification is not possible, since each permutation // will be tried to determine that result. func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { if len(types) == 0 { // Degenerate case return cty.NilType, nil } // If all of the given types are of the same structural kind, we may be // able to construct a new type that they can all be unified to, even if // that is not one of the given types. We must try this before the general // behavior below because in unsafe mode we can convert an object type to // a subset of that type, which would be a much less useful conversion for // unification purposes. { mapCt := 0 listCt := 0 setCt := 0 objectCt := 0 tupleCt := 0 dynamicCt := 0 for _, ty := range types { switch { case ty.IsMapType(): mapCt++ case ty.IsListType(): listCt++ case ty.IsSetType(): setCt++ case ty.IsObjectType(): objectCt++ case ty.IsTupleType(): tupleCt++ case ty == cty.DynamicPseudoType: dynamicCt++ default: break } } switch { case mapCt > 0 && (mapCt+dynamicCt) == len(types): return unifyCollectionTypes(cty.Map, types, unsafe, dynamicCt > 0) case mapCt > 0 && (mapCt+objectCt+dynamicCt) == len(types): // Objects often contain map data, but are not directly typed as // such due to language constructs or function types. Try to unify // them as maps first before falling back to heterogeneous type // conversion. ty, convs := unifyObjectsAsMaps(types, unsafe) // If we got a map back, we know the unification was successful. if ty.IsMapType() { return ty, convs } case listCt > 0 && (listCt+dynamicCt) == len(types): return unifyCollectionTypes(cty.List, types, unsafe, dynamicCt > 0) case listCt > 0 && (listCt+tupleCt+dynamicCt) == len(types): // Tuples are often lists in disguise, and we may be able to // unify them as such. ty, convs := unifyTuplesAsList(types, unsafe) // if we got a list back, we know the unification was successful. // Otherwise we will fall back to the heterogeneous type codepath. if ty.IsListType() { return ty, convs } case setCt > 0 && (setCt+dynamicCt) == len(types): return unifyCollectionTypes(cty.Set, types, unsafe, dynamicCt > 0) case objectCt > 0 && (objectCt+dynamicCt) == len(types): return unifyObjectTypes(types, unsafe, dynamicCt > 0) case tupleCt > 0 && (tupleCt+dynamicCt) == len(types): return unifyTupleTypes(types, unsafe, dynamicCt > 0) case objectCt > 0 && tupleCt > 0: // Can never unify object and tuple types since they have incompatible kinds return cty.NilType, nil } } prefOrder := sortTypes(types) // sortTypes gives us an order where earlier items are preferable as // our result type. We'll now walk through these and choose the first // one we encounter for which conversions exist for all source types. conversions := make([]Conversion, len(types)) Preferences: for _, wantTypeIdx := range prefOrder { wantType := types[wantTypeIdx] for i, tryType := range types { if i == wantTypeIdx { // Don't need to convert our wanted type to itself conversions[i] = nil continue } if tryType.Equals(wantType) { conversions[i] = nil continue } if unsafe { conversions[i] = GetConversionUnsafe(tryType, wantType) } else { conversions[i] = GetConversion(tryType, wantType) } if conversions[i] == nil { // wantType is not a suitable unification type, so we'll // try the next one in our preference order. continue Preferences } } return wantType, conversions } // If we fall out here, no unification is possible return cty.NilType, nil } // unifyTuplesAsList attempts to first see if the tuples unify as lists, then // re-unifies the given types with the list in place of the tuples. func unifyTuplesAsList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { var tuples []cty.Type var tupleIdxs []int for i, t := range types { if t.IsTupleType() { tuples = append(tuples, t) tupleIdxs = append(tupleIdxs, i) } } ty, tupleConvs := unifyTupleTypesToList(tuples, unsafe) if !ty.IsListType() { return cty.NilType, nil } // the tuples themselves unified as a list, get the overall // unification with this list type instead of the tuple. // make a copy of the types, so we can fallback to the standard // codepath if something went wrong listed := make([]cty.Type, len(types)) copy(listed, types) for _, idx := range tupleIdxs { listed[idx] = ty } newTy, convs := unify(listed, unsafe) if !newTy.IsListType() { return cty.NilType, nil } // we have a good conversion, wrap the nested tuple conversions. // We know the tuple conversion is not nil, because we went from tuple to // list for i, idx := range tupleIdxs { listConv := convs[idx] tupleConv := tupleConvs[i] if listConv == nil { convs[idx] = tupleConv continue } convs[idx] = func(in cty.Value) (out cty.Value, err error) { out, err = tupleConv(in) if err != nil { return out, err } return listConv(in) } } return newTy, convs } // unifyObjectsAsMaps attempts to first see if the objects unify as maps, then // re-unifies the given types with the map in place of the objects. func unifyObjectsAsMaps(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { var objs []cty.Type var objIdxs []int for i, t := range types { if t.IsObjectType() { objs = append(objs, t) objIdxs = append(objIdxs, i) } } ty, objConvs := unifyObjectTypesToMap(objs, unsafe) if !ty.IsMapType() { return cty.NilType, nil } // the objects themselves unified as a map, get the overall // unification with this map type instead of the object. // Make a copy of the types, so we can fallback to the standard codepath if // something went wrong without changing the original types. mapped := make([]cty.Type, len(types)) copy(mapped, types) for _, idx := range objIdxs { mapped[idx] = ty } newTy, convs := unify(mapped, unsafe) if !newTy.IsMapType() { return cty.NilType, nil } // we have a good conversion, so wrap the nested object conversions. // We know the object conversion is not nil, because we went from object to // map. for i, idx := range objIdxs { mapConv := convs[idx] objConv := objConvs[i] if mapConv == nil { convs[idx] = objConv continue } convs[idx] = func(in cty.Value) (out cty.Value, err error) { out, err = objConv(in) if err != nil { return out, err } return mapConv(in) } } return newTy, convs } func unifyCollectionTypes(collectionType func(cty.Type) cty.Type, types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) { // If we had any dynamic types in the input here then we can't predict // what path we'll take through here once these become known types, so // we'll conservatively produce DynamicVal for these. if hasDynamic { return unifyAllAsDynamic(types) } elemTypes := make([]cty.Type, 0, len(types)) for _, ty := range types { elemTypes = append(elemTypes, ty.ElementType()) } retElemType, _ := unify(elemTypes, unsafe) if retElemType == cty.NilType { return cty.NilType, nil } retTy := collectionType(retElemType) conversions := make([]Conversion, len(types)) for i, ty := range types { if ty.Equals(retTy) { continue } if unsafe { conversions[i] = GetConversionUnsafe(ty, retTy) } else { conversions[i] = GetConversion(ty, retTy) } if conversions[i] == nil { // Shouldn't be reachable, since we were able to unify return cty.NilType, nil } } return retTy, conversions } func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) { // If we had any dynamic types in the input here then we can't predict // what path we'll take through here once these become known types, so // we'll conservatively produce DynamicVal for these. if hasDynamic { return unifyAllAsDynamic(types) } // There are two different ways we can succeed here: // - If all of the given object types have the same set of attribute names // and the corresponding types are all unifyable, then we construct that // type. // - If the given object types have different attribute names or their // corresponding types are not unifyable, we'll instead try to unify // all of the attribute types together to produce a map type. // // Our unification behavior is intentionally stricter than our conversion // behavior for subset object types because user intent is different with // unification use-cases: it makes sense to allow {"foo":true} to convert // to emptyobjectval, but unifying an object with an attribute with the // empty object type should be an error because unifying to the empty // object type would be suprising and useless. firstAttrs := types[0].AttributeTypes() for _, ty := range types[1:] { thisAttrs := ty.AttributeTypes() if len(thisAttrs) != len(firstAttrs) { // If number of attributes is different then there can be no // object type in common. return unifyObjectTypesToMap(types, unsafe) } for name := range thisAttrs { if _, ok := firstAttrs[name]; !ok { // If attribute names don't exactly match then there can be // no object type in common. return unifyObjectTypesToMap(types, unsafe) } } } // If we get here then we've proven that all of the given object types // have exactly the same set of attribute names, though the types may // differ. retAtys := make(map[string]cty.Type) atysAcross := make([]cty.Type, len(types)) for name := range firstAttrs { for i, ty := range types { atysAcross[i] = ty.AttributeType(name) } retAtys[name], _ = unify(atysAcross, unsafe) if retAtys[name] == cty.NilType { // Cannot unify this attribute alone, which means that unification // of everything down to a map type can't be possible either. return cty.NilType, nil } } retTy := cty.Object(retAtys) conversions := make([]Conversion, len(types)) for i, ty := range types { if ty.Equals(retTy) { continue } if unsafe { conversions[i] = GetConversionUnsafe(ty, retTy) } else { conversions[i] = GetConversion(ty, retTy) } if conversions[i] == nil { // Shouldn't be reachable, since we were able to unify return unifyObjectTypesToMap(types, unsafe) } } return retTy, conversions } func unifyObjectTypesToMap(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { // This is our fallback case for unifyObjectTypes, where we see if we can // construct a map type that can accept all of the attribute types. var atys []cty.Type for _, ty := range types { for _, aty := range ty.AttributeTypes() { atys = append(atys, aty) } } ety, _ := unify(atys, unsafe) if ety == cty.NilType { return cty.NilType, nil } retTy := cty.Map(ety) conversions := make([]Conversion, len(types)) for i, ty := range types { if ty.Equals(retTy) { continue } if unsafe { conversions[i] = GetConversionUnsafe(ty, retTy) } else { conversions[i] = GetConversion(ty, retTy) } if conversions[i] == nil { return cty.NilType, nil } } return retTy, conversions } func unifyTupleTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) { // If we had any dynamic types in the input here then we can't predict // what path we'll take through here once these become known types, so // we'll conservatively produce DynamicVal for these. if hasDynamic { return unifyAllAsDynamic(types) } // There are two different ways we can succeed here: // - If all of the given tuple types have the same sequence of element types // and the corresponding types are all unifyable, then we construct that // type. // - If the given tuple types have different element types or their // corresponding types are not unifyable, we'll instead try to unify // all of the elements types together to produce a list type. firstEtys := types[0].TupleElementTypes() for _, ty := range types[1:] { thisEtys := ty.TupleElementTypes() if len(thisEtys) != len(firstEtys) { // If number of elements is different then there can be no // tuple type in common. return unifyTupleTypesToList(types, unsafe) } } // If we get here then we've proven that all of the given tuple types // have the same number of elements, though the types may differ. retEtys := make([]cty.Type, len(firstEtys)) atysAcross := make([]cty.Type, len(types)) for idx := range firstEtys { for tyI, ty := range types { atysAcross[tyI] = ty.TupleElementTypes()[idx] } retEtys[idx], _ = unify(atysAcross, unsafe) if retEtys[idx] == cty.NilType { // Cannot unify this element alone, which means that unification // of everything down to a map type can't be possible either. return cty.NilType, nil } } retTy := cty.Tuple(retEtys) conversions := make([]Conversion, len(types)) for i, ty := range types { if ty.Equals(retTy) { continue } if unsafe { conversions[i] = GetConversionUnsafe(ty, retTy) } else { conversions[i] = GetConversion(ty, retTy) } if conversions[i] == nil { return unifyTupleTypesToList(types, unsafe) } } return retTy, conversions } func unifyTupleTypesToList(types []cty.Type, unsafe bool) (cty.Type, []Conversion) { // This is our fallback case for unifyTupleTypes, where we see if we can // construct a list type that can accept all of the element types. var etys []cty.Type for _, ty := range types { for _, ety := range ty.TupleElementTypes() { etys = append(etys, ety) } } ety, _ := unify(etys, unsafe) if ety == cty.NilType { return cty.NilType, nil } retTy := cty.List(ety) conversions := make([]Conversion, len(types)) for i, ty := range types { if ty.Equals(retTy) { continue } if unsafe { conversions[i] = GetConversionUnsafe(ty, retTy) } else { conversions[i] = GetConversion(ty, retTy) } if conversions[i] == nil { // no conversion was found return cty.NilType, nil } } return retTy, conversions } func unifyAllAsDynamic(types []cty.Type) (cty.Type, []Conversion) { conversions := make([]Conversion, len(types)) for i := range conversions { conversions[i] = func(cty.Value) (cty.Value, error) { return cty.DynamicVal, nil } } return cty.DynamicPseudoType, conversions } go-cty-1.12.1/cty/convert/unify_test.go000066400000000000000000000266741433256746400200320ustar00rootroot00000000000000package convert import ( "fmt" "reflect" "testing" "github.com/zclconf/go-cty/cty" ) func TestUnify(t *testing.T) { tests := []struct { Input []cty.Type WantType cty.Type WantConversions []bool }{ { []cty.Type{}, cty.NilType, nil, }, { []cty.Type{cty.String}, cty.String, []bool{false}, }, { []cty.Type{cty.Number}, cty.Number, []bool{false}, }, { []cty.Type{cty.Number, cty.Number}, cty.Number, []bool{false, false}, }, { []cty.Type{cty.Number, cty.String}, cty.String, []bool{true, false}, }, { []cty.Type{cty.String, cty.Number}, cty.String, []bool{false, true}, }, { []cty.Type{cty.Bool, cty.String, cty.Number}, cty.String, []bool{true, false, true}, }, { []cty.Type{cty.Bool, cty.Number}, cty.NilType, nil, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.String}), cty.Object(map[string]cty.Type{"foo": cty.String}), }, cty.Object(map[string]cty.Type{"foo": cty.String}), []bool{false, false}, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.String}), cty.Object(map[string]cty.Type{"foo": cty.Number}), }, cty.Object(map[string]cty.Type{"foo": cty.String}), []bool{false, true}, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.String}), cty.Object(map[string]cty.Type{"bar": cty.Number}), }, cty.Map(cty.String), []bool{true, true}, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.String}), cty.EmptyObject, }, cty.Map(cty.String), []bool{true, true}, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.Bool}), cty.Object(map[string]cty.Type{"bar": cty.Number}), }, cty.NilType, nil, }, { []cty.Type{ cty.Object(map[string]cty.Type{"foo": cty.Bool}), cty.Object(map[string]cty.Type{"foo": cty.Number}), }, cty.NilType, nil, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.String}), cty.Tuple([]cty.Type{cty.String}), }, cty.Tuple([]cty.Type{cty.String}), []bool{false, false}, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.String}), cty.Tuple([]cty.Type{cty.Number}), }, cty.Tuple([]cty.Type{cty.String}), []bool{false, true}, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.String}), cty.Tuple([]cty.Type{cty.String, cty.Number}), }, cty.List(cty.String), []bool{true, true}, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.String}), cty.EmptyTuple, }, cty.List(cty.String), []bool{true, true}, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.Bool}), cty.Tuple([]cty.Type{cty.Number}), }, cty.NilType, nil, }, { // objects can unify as map(string) within the tuples []cty.Type{ cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, }), cty.Object(map[string]cty.Type{ "a": cty.String, }), }), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }), }, cty.List(cty.Map(cty.String)), []bool{true, true}, }, { // The second tuple value could be anything, so we can't unify // these as a list. // FIXME: While a unification is possible, we get a NilType for // now until we can handle more complex recursive unification. []cty.Type{ cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, }), cty.DynamicPseudoType, }), cty.List(cty.DynamicPseudoType), }, cty.NilType, nil, }, { // unifies to the same result as above, since the only difference // is the addition of a list []cty.Type{ cty.List(cty.Object(map[string]cty.Type{ "a": cty.String, })), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), cty.Object(map[string]cty.Type{ "c": cty.String, "d": cty.String, }), }), }, cty.List(cty.Map(cty.String)), []bool{true, true, true}, }, { // Ensure the map does not change the unification process []cty.Type{ cty.List(cty.Object(map[string]cty.Type{ "a": cty.String, })), cty.List(cty.Map(cty.String)), cty.Tuple([]cty.Type{ cty.Map(cty.String), cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }), }, cty.List(cty.Map(cty.String)), []bool{true, false, true}, }, { // different tuple lengths unify as a list, and the objects can // unify as maps []cty.Type{ cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Number, }), cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Number, }), }), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, }), }), }, cty.List(cty.Map(cty.String)), []bool{true, true}, }, { // the equivalent tuple lengths still unify as a tuple, though the // objects are unified as a map []cty.Type{ cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.Number, }), }), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, }), }), }, cty.Tuple([]cty.Type{cty.Map(cty.String)}), []bool{true, true}, }, { // This should unify to like the tuple above []cty.Type{ cty.List( cty.Object(map[string]cty.Type{ "a": cty.Number, "b": cty.String, }), ), cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.String, }), }), }, cty.List(cty.Map(cty.String)), []bool{true, true}, }, { // This should also unify like the previous 2 examples []cty.Type{ cty.List( cty.Object(map[string]cty.Type{ "a": cty.Number, "b": cty.String, }), ), cty.List(cty.Object(map[string]cty.Type{ "a": cty.String, })), }, cty.List(cty.Map(cty.String)), []bool{true, true}, }, { // Objects and maps should unify along with the surrounding lists // and tuples. []cty.Type{ cty.List(cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), "b": cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), })), cty.List(cty.Map( cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), )), }, cty.List(cty.Map(cty.Map(cty.String))), []bool{true, true}, }, { // objects can unify as maps within objects []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), }), cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }), }, cty.Object(map[string]cty.Type{ "a": cty.Map(cty.String), }), []bool{true, true}, }, { // nested objects can unify as maps []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), "b": cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }), cty.Map( cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), ), }, cty.Map(cty.Map(cty.String)), []bool{true, true}, }, { // nested tuples and lists can unify along with the surrounding // objects and maps []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.List(cty.String), }), "b": cty.Object(map[string]cty.Type{ "a": cty.Tuple([]cty.Type{ cty.String, }), "b": cty.List(cty.String), }), }), cty.Map( cty.Object(map[string]cty.Type{ "a": cty.List(cty.String), "b": cty.List(cty.String), }), ), }, cty.Map(cty.Map(cty.List(cty.String))), []bool{true, true}, }, { // objects can unify as maps containing objects when all attributes // match []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), "b": cty.Object(map[string]cty.Type{ "a": cty.String, }), }), cty.Map( cty.Object(map[string]cty.Type{ "a": cty.String, }), ), }, cty.Map( cty.Object(map[string]cty.Type{ "a": cty.String, }), ), []bool{true, false}, }, { // objects can unify as maps with dynamic types []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), "b": cty.Object(map[string]cty.Type{ "a": cty.String, }), }), cty.Map(cty.DynamicPseudoType), cty.Map( cty.Object(map[string]cty.Type{ "a": cty.String, }), ), }, cty.Map(cty.DynamicPseudoType), []bool{true, false, true}, }, { // deeply nested objects and maps can unify []cty.Type{ cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), }), "b": cty.Object(map[string]cty.Type{ "c": cty.Object(map[string]cty.Type{ "d": cty.String, }), }), }), cty.Map(cty.Map(cty.Map(cty.String))), }, cty.Map(cty.Map(cty.Map(cty.String))), []bool{true, false}, }, { // deeply nested objects with maps can unify as maps []cty.Type{ cty.Map(cty.Map(cty.Map(cty.String))), cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.Object(map[string]cty.Type{ "a": cty.String, }), "b": cty.Map(cty.String), }), "b": cty.Map(cty.Map(cty.String)), }), }, cty.Map(cty.Map(cty.Map(cty.String))), []bool{false, true}, }, { []cty.Type{ cty.DynamicPseudoType, cty.Tuple([]cty.Type{cty.Number}), }, cty.DynamicPseudoType, []bool{true, true}, }, { []cty.Type{ cty.DynamicPseudoType, cty.Object(map[string]cty.Type{"num": cty.Number}), }, cty.DynamicPseudoType, []bool{true, true}, }, { []cty.Type{ cty.Tuple([]cty.Type{cty.Number}), cty.DynamicPseudoType, cty.Object(map[string]cty.Type{"num": cty.Number}), }, cty.NilType, nil, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { gotType, gotConvs := Unify(test.Input) if gotType == cty.NilType && test.WantType == cty.NilType { // okay! } else if ((gotType == cty.NilType) != (test.WantType == cty.NilType)) || !test.WantType.Equals(gotType) { t.Errorf("wrong result type\ngot: %#v\nwant: %#v", gotType, test.WantType) } gotConvsNil := gotConvs == nil wantConvsNil := test.WantConversions == nil if gotConvsNil && wantConvsNil { // Success! return } if gotConvsNil != wantConvsNil { if gotConvsNil { t.Fatalf("got nil conversions; want %#v", test.WantConversions) } else { t.Fatalf("got conversions; want nil") } } gotConvsBool := make([]bool, len(gotConvs)) for i, f := range gotConvs { gotConvsBool[i] = f != nil } if !reflect.DeepEqual(gotConvsBool, test.WantConversions) { t.Fatalf( "wrong conversions\ngot: %#v\nwant: %#v", gotConvsBool, test.WantConversions, ) } }) } } go-cty-1.12.1/cty/doc.go000066400000000000000000000016731433256746400147160ustar00rootroot00000000000000// Package cty (pronounced see-tie) provides some infrastructure for a type // system that might be useful for applications that need to represent // configuration values provided by the user whose types are not known // at compile time, particularly if the calling application also allows // such values to be used in expressions. // // The type system consists of primitive types Number, String and Bool, as // well as List and Map collection types and Object types that can have // arbitrarily-typed sets of attributes. // // A set of operations is defined on these types, which is accessible via // the wrapper struct Value, which annotates the raw, internal representation // of a value with its corresponding type. // // This package is oriented towards being a building block for configuration // languages used to bootstrap an application. It is not optimized for use // in tight loops where CPU time or memory pressure are a concern. package cty go-cty-1.12.1/cty/element_iterator.go000066400000000000000000000075641433256746400175200ustar00rootroot00000000000000package cty import ( "sort" "github.com/zclconf/go-cty/cty/set" ) // ElementIterator is the interface type returned by Value.ElementIterator to // allow the caller to iterate over elements of a collection-typed value. // // Its usage pattern is as follows: // // it := val.ElementIterator() // for it.Next() { // key, val := it.Element() // // ... // } type ElementIterator interface { Next() bool Element() (key Value, value Value) } func canElementIterator(val Value) bool { switch { case val.IsMarked(): return false case val.ty.IsListType(): return true case val.ty.IsMapType(): return true case val.ty.IsSetType(): return true case val.ty.IsTupleType(): return true case val.ty.IsObjectType(): return true default: return false } } func elementIterator(val Value) ElementIterator { val.assertUnmarked() switch { case val.ty.IsListType(): return &listElementIterator{ ety: val.ty.ElementType(), vals: val.v.([]interface{}), idx: -1, } case val.ty.IsMapType(): // We iterate the keys in a predictable lexicographical order so // that results will always be stable given the same input map. rawMap := val.v.(map[string]interface{}) keys := make([]string, 0, len(rawMap)) for key := range rawMap { keys = append(keys, key) } sort.Strings(keys) return &mapElementIterator{ ety: val.ty.ElementType(), vals: rawMap, keys: keys, idx: -1, } case val.ty.IsSetType(): rawSet := val.v.(set.Set[interface{}]) return &setElementIterator{ ety: val.ty.ElementType(), setIt: rawSet.Iterator(), } case val.ty.IsTupleType(): return &tupleElementIterator{ etys: val.ty.TupleElementTypes(), vals: val.v.([]interface{}), idx: -1, } case val.ty.IsObjectType(): // We iterate the keys in a predictable lexicographical order so // that results will always be stable given the same object type. atys := val.ty.AttributeTypes() keys := make([]string, 0, len(atys)) for key := range atys { keys = append(keys, key) } sort.Strings(keys) return &objectElementIterator{ atys: atys, vals: val.v.(map[string]interface{}), attrNames: keys, idx: -1, } default: panic("attempt to iterate on non-collection, non-tuple type") } } type listElementIterator struct { ety Type vals []interface{} idx int } func (it *listElementIterator) Element() (Value, Value) { i := it.idx return NumberIntVal(int64(i)), Value{ ty: it.ety, v: it.vals[i], } } func (it *listElementIterator) Next() bool { it.idx++ return it.idx < len(it.vals) } type mapElementIterator struct { ety Type vals map[string]interface{} keys []string idx int } func (it *mapElementIterator) Element() (Value, Value) { key := it.keys[it.idx] return StringVal(key), Value{ ty: it.ety, v: it.vals[key], } } func (it *mapElementIterator) Next() bool { it.idx++ return it.idx < len(it.keys) } type setElementIterator struct { ety Type setIt *set.Iterator[interface{}] } func (it *setElementIterator) Element() (Value, Value) { val := Value{ ty: it.ety, v: it.setIt.Value(), } return val, val } func (it *setElementIterator) Next() bool { return it.setIt.Next() } type tupleElementIterator struct { etys []Type vals []interface{} idx int } func (it *tupleElementIterator) Element() (Value, Value) { i := it.idx return NumberIntVal(int64(i)), Value{ ty: it.etys[i], v: it.vals[i], } } func (it *tupleElementIterator) Next() bool { it.idx++ return it.idx < len(it.vals) } type objectElementIterator struct { atys map[string]Type vals map[string]interface{} attrNames []string idx int } func (it *objectElementIterator) Element() (Value, Value) { key := it.attrNames[it.idx] return StringVal(key), Value{ ty: it.atys[key], v: it.vals[key], } } func (it *objectElementIterator) Next() bool { it.idx++ return it.idx < len(it.attrNames) } go-cty-1.12.1/cty/error.go000066400000000000000000000026161433256746400153000ustar00rootroot00000000000000package cty import ( "fmt" ) // PathError is a specialization of error that represents where in a // potentially-deep data structure an error occured, using a Path. type PathError struct { error Path Path } func errorf(path Path, f string, args ...interface{}) error { // We need to copy the Path because often our caller builds it by // continually mutating the same underlying buffer. sPath := make(Path, len(path)) copy(sPath, path) return PathError{ error: fmt.Errorf(f, args...), Path: sPath, } } // NewErrorf creates a new PathError for the current path by passing the // given format and arguments to fmt.Errorf and then wrapping the result // similarly to NewError. func (p Path) NewErrorf(f string, args ...interface{}) error { return errorf(p, f, args...) } // NewError creates a new PathError for the current path, wrapping the given // error. func (p Path) NewError(err error) error { // if we're being asked to wrap an existing PathError then our new // PathError will be the concatenation of the two paths, ensuring // that we still get a single flat PathError that's thus easier for // callers to deal with. perr, wrappingPath := err.(PathError) pathLen := len(p) if wrappingPath { pathLen = pathLen + len(perr.Path) } sPath := make(Path, pathLen) copy(sPath, p) if wrappingPath { copy(sPath[len(p):], perr.Path) } return PathError{ error: err, Path: sPath, } } go-cty-1.12.1/cty/function/000077500000000000000000000000001433256746400154405ustar00rootroot00000000000000go-cty-1.12.1/cty/function/argument.go000066400000000000000000000064261433256746400176210ustar00rootroot00000000000000package function import ( "github.com/zclconf/go-cty/cty" ) // Parameter represents a parameter to a function. type Parameter struct { // Name is an optional name for the argument. This package ignores this // value, but callers may use it for documentation, etc. Name string // Description is an optional description for the argument. Description string // A type that any argument for this parameter must conform to. // cty.DynamicPseudoType can be used, either at top-level or nested // in a parameterized type, to indicate that any type should be // permitted, to allow the definition of type-generic functions. Type cty.Type // If AllowNull is set then null values may be passed into this // argument's slot in both the type-check function and the implementation // function. If not set, such values are rejected by the built-in // checking rules. AllowNull bool // If AllowUnknown is set then unknown values may be passed into this // argument's slot in the implementation function. If not set, any // unknown values will cause the function to immediately return // an unkonwn value without calling the implementation function, thus // freeing the function implementer from dealing with this case. AllowUnknown bool // If AllowDynamicType is set then DynamicVal may be passed into this // argument's slot in the implementation function. If not set, any // dynamic values will cause the function to immediately return // DynamicVal value without calling the implementation function, thus // freeing the function implementer from dealing with this case. // // Note that DynamicVal is also unknown, so in order to receive dynamic // *values* it is also necessary to set AllowUnknown. // // However, it is valid to set AllowDynamicType without AllowUnknown, in // which case a dynamic value may be passed to the type checking function // but will not make it to the *implementation* function. Instead, an // unknown value of the type returned by the type-check function will be // returned. This is suggested for functions that have a static return // type since it allows the return value to be typed even if the input // values are not, thus improving the type-check accuracy of derived // values. AllowDynamicType bool // If AllowMarked is set then marked values may be passed into this // argument's slot in the implementation function. If not set, any // marked value will be unmarked before calling and then the markings // from that value will be applied automatically to the function result, // ensuring that the marks get propagated in a simplistic way even if // a function is unable to handle them. // // For any argument whose parameter has AllowMarked set, it's the // function implementation's responsibility to Unmark the given value // and propagate the marks appropriatedly to the result in order to // avoid losing the marks. Application-specific functions might use // special rules to selectively propagate particular marks. // // The automatic unmarking of values applies only to the main // implementation function. In an application that uses marked values, // the Type implementation for a function must always be prepared to accept // marked values, which is easy to achieve by consulting only the type // and ignoring the value itself. AllowMarked bool } go-cty-1.12.1/cty/function/doc.go000066400000000000000000000004301433256746400165310ustar00rootroot00000000000000// Package function builds on the functionality of cty by modeling functions // that operate on cty Values. // // Functions are, at their core, Go anonymous functions. However, this package // wraps around them utility functions for parameter type checking, etc. package function go-cty-1.12.1/cty/function/error.go000066400000000000000000000022701433256746400171210ustar00rootroot00000000000000package function import ( "fmt" "runtime/debug" ) // ArgError represents an error with one of the arguments in a call. The // attribute Index represents the zero-based index of the argument in question. // // Its error *may* be a cty.PathError, in which case the error actually // pertains to a nested value within the data structure passed as the argument. type ArgError struct { error Index int } func NewArgErrorf(i int, f string, args ...interface{}) error { return ArgError{ error: fmt.Errorf(f, args...), Index: i, } } func NewArgError(i int, err error) error { return ArgError{ error: err, Index: i, } } // PanicError indicates that a panic occurred while executing either a // function's type or implementation function. This is captured and wrapped // into a normal error so that callers (expected to be language runtimes) // are freed from having to deal with panics in buggy functions. type PanicError struct { Value interface{} Stack []byte } func errorForPanic(val interface{}) error { return PanicError{ Value: val, Stack: debug.Stack(), } } func (e PanicError) Error() string { return fmt.Sprintf("panic in function implementation: %s\n%s", e.Value, e.Stack) } go-cty-1.12.1/cty/function/function.go000066400000000000000000000332441433256746400176220ustar00rootroot00000000000000package function import ( "fmt" "github.com/zclconf/go-cty/cty" ) // Function represents a function. This is the main type in this package. type Function struct { spec *Spec } // Spec is the specification of a function, used to instantiate // a new Function. type Spec struct { // Description is an optional description for the function specification. Description string // Params is a description of the positional parameters for the function. // The standard checking logic rejects any calls that do not provide // arguments conforming to this definition, freeing the function // implementer from dealing with such inconsistencies. Params []Parameter // VarParam is an optional specification of additional "varargs" the // function accepts. If this is non-nil then callers may provide an // arbitrary number of additional arguments (after those matching with // the fixed parameters in Params) that conform to the given specification, // which will appear as additional values in the slices of values // provided to the type and implementation functions. VarParam *Parameter // Type is the TypeFunc that decides the return type of the function // given its arguments, which may be Unknown. See the documentation // of TypeFunc for more information. // // Use StaticReturnType if the function's return type does not vary // depending on its arguments. Type TypeFunc // Impl is the ImplFunc that implements the function's behavior. // // Functions are expected to behave as pure functions, and not create // any visible side-effects. // // If a TypeFunc is also provided, the value returned from Impl *must* // conform to the type it returns, or a call to the function will panic. Impl ImplFunc } // New creates a new function with the given specification. // // After passing a Spec to this function, the caller must no longer read from // or mutate it. func New(spec *Spec) Function { f := Function{ spec: spec, } return f } // TypeFunc is a callback type for determining the return type of a function // given its arguments. // // Any of the values passed to this function may be unknown, even if the // parameters are not configured to accept unknowns. // // If any of the given values are *not* unknown, the TypeFunc may use the // values for pre-validation and for choosing the return type. For example, // a hypothetical JSON-unmarshalling function could return // cty.DynamicPseudoType if the given JSON string is unknown, but return // a concrete type based on the JSON structure if the JSON string is already // known. type TypeFunc func(args []cty.Value) (cty.Type, error) // ImplFunc is a callback type for the main implementation of a function. // // "args" are the values for the arguments, and this slice will always be at // least as long as the argument definition slice for the function. // // "retType" is the type returned from the Type callback, included as a // convenience to avoid the need to re-compute the return type for generic // functions whose return type is a function of the arguments. type ImplFunc func(args []cty.Value, retType cty.Type) (cty.Value, error) // StaticReturnType returns a TypeFunc that always returns the given type. // // This is provided as a convenience for defining a function whose return // type does not depend on the argument types. func StaticReturnType(ty cty.Type) TypeFunc { return func([]cty.Value) (cty.Type, error) { return ty, nil } } // ReturnType returns the return type of a function given a set of candidate // argument types, or returns an error if the given types are unacceptable. // // If the caller already knows values for at least some of the arguments // it can be better to call ReturnTypeForValues, since certain functions may // determine their return types from their values and return DynamicVal if // the values are unknown. func (f Function) ReturnType(argTypes []cty.Type) (cty.Type, error) { vals := make([]cty.Value, len(argTypes)) for i, ty := range argTypes { vals[i] = cty.UnknownVal(ty) } return f.ReturnTypeForValues(vals) } // ReturnTypeForValues is similar to ReturnType but can be used if the caller // already knows the values of some or all of the arguments, in which case // the function may be able to determine a more definite result if its // return type depends on the argument *values*. // // For any arguments whose values are not known, pass an Unknown value of // the appropriate type. func (f Function) ReturnTypeForValues(args []cty.Value) (ty cty.Type, err error) { var posArgs []cty.Value var varArgs []cty.Value if f.spec.VarParam == nil { if len(args) != len(f.spec.Params) { return cty.Type{}, fmt.Errorf( "wrong number of arguments (%d required; %d given)", len(f.spec.Params), len(args), ) } posArgs = args varArgs = nil } else { if len(args) < len(f.spec.Params) { return cty.Type{}, fmt.Errorf( "wrong number of arguments (at least %d required; %d given)", len(f.spec.Params), len(args), ) } posArgs = args[0:len(f.spec.Params)] varArgs = args[len(f.spec.Params):] } for i, spec := range f.spec.Params { val := posArgs[i] if val.ContainsMarked() && !spec.AllowMarked { // During type checking we just unmark values and discard their // marks, under the assumption that during actual execution of // the function we'll do similarly and then re-apply the marks // afterwards. Note that this does mean that a function that // inspects values (rather than just types) in its Type // implementation can potentially fail to take into account marks, // unless it specifically opts in to seeing them. unmarked, _ := val.UnmarkDeep() newArgs := make([]cty.Value, len(args)) copy(newArgs, args) newArgs[i] = unmarked args = newArgs } if val.IsNull() && !spec.AllowNull { return cty.Type{}, NewArgErrorf(i, "argument must not be null") } // AllowUnknown is ignored for type-checking, since we expect to be // able to type check with unknown values. We *do* still need to deal // with DynamicPseudoType here though, since the Type function might // not be ready to deal with that. if val.Type() == cty.DynamicPseudoType { if !spec.AllowDynamicType { return cty.DynamicPseudoType, nil } } else if errs := val.Type().TestConformance(spec.Type); errs != nil { // For now we'll just return the first error in the set, since // we don't have a good way to return the whole list here. // Would be good to do something better at some point... return cty.Type{}, NewArgError(i, errs[0]) } } if varArgs != nil { spec := f.spec.VarParam for i, val := range varArgs { realI := i + len(posArgs) if val.ContainsMarked() && !spec.AllowMarked { // See the similar block in the loop above for what's going on here. unmarked, _ := val.UnmarkDeep() newArgs := make([]cty.Value, len(args)) copy(newArgs, args) newArgs[realI] = unmarked args = newArgs } if val.IsNull() && !spec.AllowNull { return cty.Type{}, NewArgErrorf(realI, "argument must not be null") } if val.Type() == cty.DynamicPseudoType { if !spec.AllowDynamicType { return cty.DynamicPseudoType, nil } } else if errs := val.Type().TestConformance(spec.Type); errs != nil { // For now we'll just return the first error in the set, since // we don't have a good way to return the whole list here. // Would be good to do something better at some point... return cty.Type{}, NewArgError(i, errs[0]) } } } // Intercept any panics from the function and return them as normal errors, // so a calling language runtime doesn't need to deal with panics. defer func() { if r := recover(); r != nil { ty = cty.NilType err = errorForPanic(r) } }() return f.spec.Type(args) } // Call actually calls the function with the given arguments, which must // conform to the function's parameter specification or an error will be // returned. func (f Function) Call(args []cty.Value) (val cty.Value, err error) { expectedType, err := f.ReturnTypeForValues(args) if err != nil { return cty.NilVal, err } // Type checking already dealt with most situations relating to our // parameter specification, but we still need to deal with unknown // values and marked values. posArgs := args[:len(f.spec.Params)] varArgs := args[len(f.spec.Params):] var resultMarks []cty.ValueMarks for i, spec := range f.spec.Params { val := posArgs[i] if !val.IsKnown() && !spec.AllowUnknown { return cty.UnknownVal(expectedType), nil } if !spec.AllowMarked { unwrappedVal, marks := val.UnmarkDeep() if len(marks) > 0 { // In order to avoid additional overhead on applications that // are not using marked values, we copy the given args only // if we encounter a marked value we need to unmark. However, // as a consequence we end up doing redundant copying if multiple // marked values need to be unwrapped. That seems okay because // argument lists are generally small. newArgs := make([]cty.Value, len(args)) copy(newArgs, args) newArgs[i] = unwrappedVal resultMarks = append(resultMarks, marks) args = newArgs } } } if f.spec.VarParam != nil { spec := f.spec.VarParam for i, val := range varArgs { if !val.IsKnown() && !spec.AllowUnknown { return cty.UnknownVal(expectedType), nil } if !spec.AllowMarked { unwrappedVal, marks := val.UnmarkDeep() if len(marks) > 0 { newArgs := make([]cty.Value, len(args)) copy(newArgs, args) newArgs[len(posArgs)+i] = unwrappedVal resultMarks = append(resultMarks, marks) args = newArgs } } } } var retVal cty.Value { // Intercept any panics from the function and return them as normal errors, // so a calling language runtime doesn't need to deal with panics. defer func() { if r := recover(); r != nil { val = cty.NilVal err = errorForPanic(r) } }() retVal, err = f.spec.Impl(args, expectedType) if err != nil { return cty.NilVal, err } if len(resultMarks) > 0 { retVal = retVal.WithMarks(resultMarks...) } } // Returned value must conform to what the Type function expected, to // protect callers from having to deal with inconsistencies. if errs := retVal.Type().TestConformance(expectedType); errs != nil { panic(fmt.Errorf( "returned value %#v does not conform to expected return type %#v: %s", retVal, expectedType, errs[0], )) } return retVal, nil } // ProxyFunc the type returned by the method Function.Proxy. type ProxyFunc func(args ...cty.Value) (cty.Value, error) // Proxy returns a function that can be called with cty.Value arguments // to run the function. This is provided as a convenience for when using // a function directly within Go code. func (f Function) Proxy() ProxyFunc { return func(args ...cty.Value) (cty.Value, error) { return f.Call(args) } } // Params returns information about the function's fixed positional parameters. // This does not include information about any variadic arguments accepted; // for that, call VarParam. func (f Function) Params() []Parameter { new := make([]Parameter, len(f.spec.Params)) copy(new, f.spec.Params) return new } // VarParam returns information about the variadic arguments the function // expects, or nil if the function is not variadic. func (f Function) VarParam() *Parameter { if f.spec.VarParam == nil { return nil } ret := *f.spec.VarParam return &ret } // Description returns a human-readable description of the function. func (f Function) Description() string { return f.spec.Description } // WithNewDescriptions returns a new function that has the same signature // and implementation as the receiver but has the function description and // the parameter descriptions replaced with those given in the arguments. // // All descriptions may be given as an empty string to specify that there // should be no description at all. // // The paramDescs argument must match the number of parameters // the reciever expects, or this function will panic. If the function has a // VarParam then that counts as one parameter for the sake of this rule. The // given descriptions will be assigned in order starting with the positional // arguments in their declared order, followed by the variadic parameter if // any. // // As a special case, WithNewDescriptions will accept a paramDescs which // does not cover the reciever's variadic parameter (if any), so that it's // possible to add a variadic parameter to a function which didn't previously // have one without that being a breaking change for an existing caller using // WithNewDescriptions against that function. In this case the base description // of the variadic parameter will be preserved. func (f Function) WithNewDescriptions(funcDesc string, paramDescs []string) Function { retSpec := *f.spec // shallow copy of the reciever retSpec.Description = funcDesc retSpec.Params = make([]Parameter, len(f.spec.Params)) copy(retSpec.Params, f.spec.Params) // shallow copy of positional parameters if f.spec.VarParam != nil { retVarParam := *f.spec.VarParam // shallow copy of variadic parameter retSpec.VarParam = &retVarParam } if retSpec.VarParam != nil { if with, without := len(retSpec.Params)+1, len(retSpec.Params); len(paramDescs) != with && len(paramDescs) != without { panic(fmt.Sprintf("paramDescs must have length of either %d or %d", with, without)) } } else { if want := len(retSpec.Params); len(paramDescs) != want { panic(fmt.Sprintf("paramDescs must have length %d", want)) } } posParamDescs := paramDescs[:len(retSpec.Params)] varParamDescs := paramDescs[len(retSpec.Params):] // guaranteed to be zero or one elements because of the rules above for i, desc := range posParamDescs { retSpec.Params[i].Description = desc } for _, desc := range varParamDescs { retSpec.VarParam.Description = desc } return New(&retSpec) } go-cty-1.12.1/cty/function/function_test.go000066400000000000000000000325641433256746400206650ustar00rootroot00000000000000package function import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestReturnTypeForValues(t *testing.T) { tests := []struct { Spec *Spec Args []cty.Value WantType cty.Type WantErr bool }{ { Spec: &Spec{ Params: []Parameter{}, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{}, WantType: cty.Number, }, { Spec: &Spec{ Params: []Parameter{}, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.NumberIntVal(2)}, WantErr: true, }, { Spec: &Spec{ Params: []Parameter{}, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.UnknownVal(cty.Number)}, WantErr: true, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.NumberIntVal(2)}, WantType: cty.Number, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.UnknownVal(cty.Number)}, WantType: cty.Number, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.DynamicVal}, WantType: cty.DynamicPseudoType, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, AllowDynamicType: true, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.DynamicVal}, WantType: cty.Number, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, AllowDynamicType: true, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.UnknownVal(cty.String)}, WantErr: true, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.Number, AllowDynamicType: true, }, }, Type: StaticReturnType(cty.Number), Impl: stubImpl, }, Args: []cty.Value{cty.StringVal("hello")}, WantErr: true, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.List(cty.DynamicPseudoType), }, }, Type: func(args []cty.Value) (cty.Type, error) { ty := cty.Number for i, arg := range args { if arg.ContainsMarked() { return ty, fmt.Errorf("arg %d %#v contains marks", i, arg) } } return ty, nil }, Impl: stubImpl, }, Args: []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("ok").Mark("marked"), }), }, WantType: cty.Number, }, { Spec: &Spec{ Params: []Parameter{ { Type: cty.List(cty.String), }, }, VarParam: &Parameter{ Type: cty.List(cty.String), }, Type: func(args []cty.Value) (cty.Type, error) { ty := cty.Number for i, arg := range args { if arg.ContainsMarked() { return ty, fmt.Errorf("arg %d %#v contains marks", i, arg) } } return ty, nil }, Impl: stubImpl, }, Args: []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("one"), }), cty.ListVal([]cty.Value{ cty.StringVal("two").Mark("marked"), }), }, WantType: cty.Number, }, } for i, test := range tests { t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { f := New(test.Spec) gotType, gotErr := f.ReturnTypeForValues(test.Args) if test.WantErr { if gotErr == nil { t.Errorf("succeeded with %#v; want error", gotType) } } else { if gotErr != nil { t.Fatalf("unexpected error\nspec: %#v\nargs: %#v\nerr: %s\nwant: %#v", test.Spec, test.Args, gotErr, test.WantType) } if gotType == cty.NilType { t.Fatalf("returned type is invalid") } if !gotType.Equals(test.WantType) { t.Errorf("wrong return type\nspec: %#v\nargs: %#v\ngot: %#v\nwant: %#v", test.Spec, test.Args, gotType, test.WantType) } } }) } } func TestFunctionWithNewDescriptions(t *testing.T) { t.Run("no params", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", Params: []Parameter{}, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", nil, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } }) t.Run("one pos param", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", Params: []Parameter{ { Name: "a", Description: "old a", }, }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", []string{"new a"}, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 1; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 1; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.Params()[0].Description, "old a"; got != want { t.Errorf("wrong original param a description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Params()[0].Description, "new a"; got != want { t.Errorf("wrong updated param a description\ngot: %s\nwant: %s", got, want) } }) t.Run("two pos params", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", Params: []Parameter{ { Name: "a", Description: "old a", }, { Name: "b", Description: "old b", }, }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", []string{"new a", "new b"}, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 2; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 2; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.Params()[0].Description, "old a"; got != want { t.Errorf("wrong original param a description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Params()[0].Description, "new a"; got != want { t.Errorf("wrong updated param a description\ngot: %s\nwant: %s", got, want) } if got, want := f1.Params()[1].Description, "old b"; got != want { t.Errorf("wrong original param b description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Params()[1].Description, "new b"; got != want { t.Errorf("wrong updated param b description\ngot: %s\nwant: %s", got, want) } }) t.Run("varparam overridden", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", Params: []Parameter{ { Name: "a", Description: "old a", }, }, VarParam: &Parameter{ Name: "b", Description: "old b", }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", []string{"new a", "new b"}, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 1; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 1; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.Params()[0].Description, "old a"; got != want { t.Errorf("wrong original param a description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Params()[0].Description, "new a"; got != want { t.Errorf("wrong updated param a description\ngot: %s\nwant: %s", got, want) } if got, want := f1.VarParam().Description, "old b"; got != want { t.Errorf("wrong original param b description\ngot: %s\nwant: %s", got, want) } if got, want := f2.VarParam().Description, "new b"; got != want { t.Errorf("wrong updated param b description\ngot: %s\nwant: %s", got, want) } }) t.Run("varparam not overridden", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", Params: []Parameter{ { Name: "a", Description: "old a", }, }, VarParam: &Parameter{ Name: "b", Description: "old b", }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", []string{"new a"}, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 1; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 1; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.Params()[0].Description, "old a"; got != want { t.Errorf("wrong original param a description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Params()[0].Description, "new a"; got != want { t.Errorf("wrong updated param a description\ngot: %s\nwant: %s", got, want) } if got, want := f1.VarParam().Description, "old b"; got != want { t.Errorf("wrong original param b description\ngot: %s\nwant: %s", got, want) } if got, want := f2.VarParam().Description, "old b"; got != want { // This is the one case where we allow the caller to leave one of // the param descriptions unchanged, because we want to allow // a function to grow a variadic parameter later without it being // a breaking change for existing callers that might be overriding // descriptions. t.Errorf("wrong updated param b description\ngot: %s\nwant: %s", got, want) } }) t.Run("solo varparam overridden", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", VarParam: &Parameter{ Name: "a", Description: "old a", }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", []string{"new a"}, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 0; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 0; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.VarParam().Description, "old a"; got != want { t.Errorf("wrong original param b description\ngot: %s\nwant: %s", got, want) } if got, want := f2.VarParam().Description, "new a"; got != want { t.Errorf("wrong updated param b description\ngot: %s\nwant: %s", got, want) } }) t.Run("solo varparam not overridden", func(t *testing.T) { f1 := New(&Spec{ Description: "old func", VarParam: &Parameter{ Name: "a", Description: "old a", }, Type: stubType, Impl: stubImpl, }) f2 := f1.WithNewDescriptions( "new func", nil, ) if got, want := f1.Description(), "old func"; got != want { t.Errorf("wrong original func description\ngot: %s\nwant: %s", got, want) } if got, want := f2.Description(), "new func"; got != want { t.Errorf("wrong updated func description\ngot: %s\nwant: %s", got, want) } if got, want := len(f1.Params()), 0; got != want { t.Fatalf("wrong original param count\ngot: %d\nwant: %d", got, want) } if got, want := len(f2.Params()), 0; got != want { t.Fatalf("wrong updated param count\ngot: %d\nwant: %d", got, want) } if got, want := f1.VarParam().Description, "old a"; got != want { t.Errorf("wrong original param b description\ngot: %s\nwant: %s", got, want) } if got, want := f2.VarParam().Description, "old a"; got != want { // This is the one case where we allow the caller to leave one of // the param descriptions unchanged, because we want to allow // a function to grow a variadic parameter later without it being // a breaking change for existing callers that might be overriding // descriptions. t.Errorf("wrong updated param b description\ngot: %s\nwant: %s", got, want) } }) } func stubType([]cty.Value) (cty.Type, error) { return cty.NilType, fmt.Errorf("should not be called") } func stubImpl([]cty.Value, cty.Type) (cty.Value, error) { return cty.NilVal, fmt.Errorf("should not be called") } go-cty-1.12.1/cty/function/stdlib/000077500000000000000000000000001433256746400167215ustar00rootroot00000000000000go-cty-1.12.1/cty/function/stdlib/bool.go000066400000000000000000000040751433256746400202110ustar00rootroot00000000000000package stdlib import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) var NotFunc = function.New(&function.Spec{ Description: `Applies the logical NOT operation to the given boolean value.`, Params: []function.Parameter{ { Name: "val", Type: cty.Bool, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return args[0].Not(), nil }, }) var AndFunc = function.New(&function.Spec{ Description: `Applies the logical AND operation to the given boolean values.`, Params: []function.Parameter{ { Name: "a", Type: cty.Bool, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Bool, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return args[0].And(args[1]), nil }, }) var OrFunc = function.New(&function.Spec{ Description: `Applies the logical OR operation to the given boolean values.`, Params: []function.Parameter{ { Name: "a", Type: cty.Bool, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Bool, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return args[0].Or(args[1]), nil }, }) // Not returns the logical complement of the given boolean value. func Not(num cty.Value) (cty.Value, error) { return NotFunc.Call([]cty.Value{num}) } // And returns true if and only if both of the given boolean values are true. func And(a, b cty.Value) (cty.Value, error) { return AndFunc.Call([]cty.Value{a, b}) } // Or returns true if either of the given boolean values are true. func Or(a, b cty.Value) (cty.Value, error) { return OrFunc.Call([]cty.Value{a, b}) } go-cty-1.12.1/cty/function/stdlib/bool_test.go000066400000000000000000000051141433256746400212430ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestNot(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.True, cty.False, }, { cty.False, cty.True, }, { cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.True.Mark(1), cty.False.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("Not(%#v)", test.Input), func(t *testing.T) { got, err := Not(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestAnd(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.False, cty.False, cty.False, }, { cty.False, cty.True, cty.False, }, { cty.True, cty.False, cty.False, }, { cty.True, cty.True, cty.True, }, { cty.True, cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), }, { cty.True, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("And(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := And(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestOr(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.False, cty.False, cty.False, }, { cty.False, cty.True, cty.True, }, { cty.True, cty.False, cty.True, }, { cty.True, cty.True, cty.True, }, { cty.True, cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), cty.UnknownVal(cty.Bool), }, { cty.True, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("Or(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Or(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/bytes.go000066400000000000000000000056741433256746400204120ustar00rootroot00000000000000package stdlib import ( "fmt" "reflect" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" ) // Bytes is a capsule type that can be used with the binary functions to // support applications that need to support raw buffers in addition to // UTF-8 strings. var Bytes = cty.Capsule("bytes", reflect.TypeOf([]byte(nil))) // BytesVal creates a new Bytes value from the given buffer, which must be // non-nil or this function will panic. // // Once a byte slice has been wrapped in a Bytes capsule, its underlying array // must be considered immutable. func BytesVal(buf []byte) cty.Value { if buf == nil { panic("can't make Bytes value from nil slice") } return cty.CapsuleVal(Bytes, &buf) } // BytesLen is a Function that returns the length of the buffer encapsulated // in a Bytes value. var BytesLenFunc = function.New(&function.Spec{ Description: `Returns the total number of bytes in the given buffer.`, Params: []function.Parameter{ { Name: "buf", Type: Bytes, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { bufPtr := args[0].EncapsulatedValue().(*[]byte) return cty.NumberIntVal(int64(len(*bufPtr))), nil }, }) // BytesSlice is a Function that returns a slice of the given Bytes value. var BytesSliceFunc = function.New(&function.Spec{ Description: `Extracts a subslice from the given buffer.`, Params: []function.Parameter{ { Name: "buf", Type: Bytes, AllowDynamicType: true, }, { Name: "offset", Type: cty.Number, AllowDynamicType: true, }, { Name: "length", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(Bytes), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { bufPtr := args[0].EncapsulatedValue().(*[]byte) var offset, length int var err error err = gocty.FromCtyValue(args[1], &offset) if err != nil { return cty.NilVal, err } err = gocty.FromCtyValue(args[2], &length) if err != nil { return cty.NilVal, err } if offset < 0 || length < 0 { return cty.NilVal, fmt.Errorf("offset and length must be non-negative") } if offset > len(*bufPtr) { return cty.NilVal, fmt.Errorf( "offset %d is greater than total buffer length %d", offset, len(*bufPtr), ) } end := offset + length if end > len(*bufPtr) { return cty.NilVal, fmt.Errorf( "offset %d + length %d is greater than total buffer length %d", offset, length, len(*bufPtr), ) } return BytesVal((*bufPtr)[offset:end]), nil }, }) func BytesLen(buf cty.Value) (cty.Value, error) { return BytesLenFunc.Call([]cty.Value{buf}) } func BytesSlice(buf cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) { return BytesSliceFunc.Call([]cty.Value{buf, offset, length}) } go-cty-1.12.1/cty/function/stdlib/bytes_test.go000066400000000000000000000036111433256746400214360ustar00rootroot00000000000000package stdlib import ( "reflect" "testing" "github.com/zclconf/go-cty/cty" ) func TestBytesLen(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { BytesVal([]byte{}), cty.NumberIntVal(0), }, { BytesVal([]byte{'a'}), cty.NumberIntVal(1), }, { BytesVal([]byte{'a', 'b', 'c'}), cty.NumberIntVal(3), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := BytesLen(test.Input) if err != nil { t.Fatal(err) } if !got.RawEquals(test.Want) { t.Errorf( "wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) } }) } } func TestBytesSlice(t *testing.T) { tests := []struct { Input cty.Value Offset cty.Value Length cty.Value Want cty.Value }{ { BytesVal([]byte{}), cty.NumberIntVal(0), cty.NumberIntVal(0), BytesVal([]byte{}), }, { BytesVal([]byte{'a'}), cty.NumberIntVal(0), cty.NumberIntVal(1), BytesVal([]byte{'a'}), }, { BytesVal([]byte{'a', 'b', 'c'}), cty.NumberIntVal(0), cty.NumberIntVal(2), BytesVal([]byte{'a', 'b'}), }, { BytesVal([]byte{'a', 'b', 'c'}), cty.NumberIntVal(1), cty.NumberIntVal(2), BytesVal([]byte{'b', 'c'}), }, { BytesVal([]byte{'a', 'b', 'c'}), cty.NumberIntVal(0), cty.NumberIntVal(3), BytesVal([]byte{'a', 'b', 'c'}), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := BytesSlice(test.Input, test.Offset, test.Length) if err != nil { t.Fatal(err) } gotBytes := *(got.EncapsulatedValue().(*[]byte)) wantBytes := *(test.Want.EncapsulatedValue().(*[]byte)) if !reflect.DeepEqual(gotBytes, wantBytes) { t.Errorf( "wrong result\ninput: %#v, %#v, %#v\ngot: %#v\nwant: %#v", test.Input, test.Offset, test.Length, got, test.Want, ) } }) } } go-cty-1.12.1/cty/function/stdlib/collection.go000066400000000000000000001306371433256746400214150ustar00rootroot00000000000000package stdlib import ( "errors" "fmt" "sort" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" ) var HasIndexFunc = function.New(&function.Spec{ Description: `Returns true if if the given collection can be indexed with the given key without producing an error, or false otherwise.`, Params: []function.Parameter{ { Name: "collection", Type: cty.DynamicPseudoType, AllowDynamicType: true, }, { Name: "key", Type: cty.DynamicPseudoType, AllowDynamicType: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { collTy := args[0].Type() if !(collTy.IsTupleType() || collTy.IsListType() || collTy.IsMapType() || collTy == cty.DynamicPseudoType) { return cty.NilType, fmt.Errorf("collection must be a list, a map or a tuple") } return cty.Bool, nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].HasIndex(args[1]), nil }, }) var IndexFunc = function.New(&function.Spec{ Description: `Returns the element with the given key from the given collection, or raises an error if there is no such element.`, Params: []function.Parameter{ { Name: "collection", Type: cty.DynamicPseudoType, }, { Name: "key", Type: cty.DynamicPseudoType, AllowDynamicType: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { collTy := args[0].Type() key := args[1] keyTy := key.Type() switch { case collTy.IsTupleType(): if keyTy != cty.Number && keyTy != cty.DynamicPseudoType { return cty.NilType, fmt.Errorf("key for tuple must be number") } if !key.IsKnown() { return cty.DynamicPseudoType, nil } var idx int err := gocty.FromCtyValue(key, &idx) if err != nil { return cty.NilType, fmt.Errorf("invalid key for tuple: %s", err) } etys := collTy.TupleElementTypes() if idx >= len(etys) || idx < 0 { return cty.NilType, fmt.Errorf("key must be between 0 and %d inclusive", len(etys)) } return etys[idx], nil case collTy.IsListType(): if keyTy != cty.Number && keyTy != cty.DynamicPseudoType { return cty.NilType, fmt.Errorf("key for list must be number") } return collTy.ElementType(), nil case collTy.IsMapType(): if keyTy != cty.String && keyTy != cty.DynamicPseudoType { return cty.NilType, fmt.Errorf("key for map must be string") } return collTy.ElementType(), nil default: return cty.NilType, fmt.Errorf("collection must be a list, a map or a tuple") } }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { has, err := HasIndex(args[0], args[1]) if err != nil { return cty.NilVal, err } if has.False() { // safe because collection and key are guaranteed known here return cty.NilVal, fmt.Errorf("invalid index") } return args[0].Index(args[1]), nil }, }) var LengthFunc = function.New(&function.Spec{ Description: `Returns the number of elements in the given collection.`, Params: []function.Parameter{ { Name: "collection", Type: cty.DynamicPseudoType, AllowDynamicType: true, AllowMarked: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { collTy := args[0].Type() if !(collTy.IsTupleType() || collTy.IsListType() || collTy.IsMapType() || collTy.IsSetType() || collTy == cty.DynamicPseudoType) { return cty.NilType, fmt.Errorf("collection must be a list, a map or a tuple") } return cty.Number, nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].Length(), nil }, }) var ElementFunc = function.New(&function.Spec{ Description: `Returns the element with the given index from the given list or tuple, applying the modulo operation to the given index if it's greater than the number of elements.`, Params: []function.Parameter{ { Name: "list", Type: cty.DynamicPseudoType, AllowMarked: true, }, { Name: "index", Type: cty.Number, }, }, Type: func(args []cty.Value) (cty.Type, error) { list := args[0] index := args[1] if index.IsKnown() { if index.LessThan(cty.NumberIntVal(0)).True() { return cty.DynamicPseudoType, fmt.Errorf("cannot use element function with a negative index") } } listTy := list.Type() switch { case listTy.IsListType(): return listTy.ElementType(), nil case listTy.IsTupleType(): if !args[1].IsKnown() { // If the index isn't known yet then we can't predict the // result type since each tuple element can have its own type. return cty.DynamicPseudoType, nil } etys := listTy.TupleElementTypes() var index int err := gocty.FromCtyValue(args[1], &index) if err != nil { // e.g. fractional number where whole number is required return cty.DynamicPseudoType, fmt.Errorf("invalid index: %s", err) } if len(etys) == 0 { return cty.DynamicPseudoType, errors.New("cannot use element function with an empty list") } index = index % len(etys) return etys[index], nil default: return cty.DynamicPseudoType, fmt.Errorf("cannot read elements from %s", listTy.FriendlyName()) } }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { var index int err := gocty.FromCtyValue(args[1], &index) if err != nil { // can't happen because we checked this in the Type function above return cty.DynamicVal, fmt.Errorf("invalid index: %s", err) } if args[1].LessThan(cty.NumberIntVal(0)).True() { return cty.DynamicVal, fmt.Errorf("cannot use element function with a negative index") } input, marks := args[0].Unmark() if !input.IsKnown() { return cty.UnknownVal(retType), nil } l := input.LengthInt() if l == 0 { return cty.DynamicVal, errors.New("cannot use element function with an empty list") } index = index % l // We did all the necessary type checks in the type function above, // so this is guaranteed not to fail. return input.Index(cty.NumberIntVal(int64(index))).WithMarks(marks), nil }, }) // CoalesceListFunc is a function that takes any number of list arguments // and returns the first one that isn't empty. var CoalesceListFunc = function.New(&function.Spec{ Description: `Returns the first of the given sequences that has a length greater than zero.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "vals", Description: `List or tuple values to test in the given order.`, Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, Type: func(args []cty.Value) (ret cty.Type, err error) { if len(args) == 0 { return cty.NilType, errors.New("at least one argument is required") } argTypes := make([]cty.Type, len(args)) for i, arg := range args { // if any argument is unknown, we can't be certain know which type we will return if !arg.IsKnown() { return cty.DynamicPseudoType, nil } ty := arg.Type() if !ty.IsListType() && !ty.IsTupleType() { return cty.NilType, errors.New("coalescelist arguments must be lists or tuples") } argTypes[i] = arg.Type() } last := argTypes[0] // If there are mixed types, we have to return a dynamic type. for _, next := range argTypes[1:] { if !next.Equals(last) { return cty.DynamicPseudoType, nil } } return last, nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { for _, arg := range args { if !arg.IsKnown() { // If we run into an unknown list at some point, we can't // predict the final result yet. (If there's a known, non-empty // arg before this then we won't get here.) return cty.UnknownVal(retType), nil } if arg.IsNull() { continue } if arg.LengthInt() > 0 { return arg, nil } } return cty.NilVal, errors.New("no non-null arguments") }, }) // CompactFunc is a function that takes a list of strings and returns a new list // with any empty string elements removed. var CompactFunc = function.New(&function.Spec{ Description: `Removes all empty string elements from the given list of strings.`, Params: []function.Parameter{ { Name: "list", Type: cty.List(cty.String), }, }, Type: function.StaticReturnType(cty.List(cty.String)), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { listVal := args[0] if !listVal.IsWhollyKnown() { // If some of the element values aren't known yet then we // can't yet return a compacted list return cty.UnknownVal(retType), nil } var outputList []cty.Value for it := listVal.ElementIterator(); it.Next(); { _, v := it.Element() if v.IsNull() || v.AsString() == "" { continue } outputList = append(outputList, v) } if len(outputList) == 0 { return cty.ListValEmpty(cty.String), nil } return cty.ListVal(outputList), nil }, }) // ContainsFunc is a function that determines whether a given list or // set contains a given single value as one of its elements. var ContainsFunc = function.New(&function.Spec{ Description: `Returns true if the given value is a value in the given list, tuple, or set, or false otherwise.`, Params: []function.Parameter{ { Name: "list", Type: cty.DynamicPseudoType, }, { Name: "value", Type: cty.DynamicPseudoType, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { arg := args[0] ty := arg.Type() if !ty.IsListType() && !ty.IsTupleType() && !ty.IsSetType() { return cty.NilVal, errors.New("argument must be list, tuple, or set") } if args[0].IsNull() { return cty.NilVal, errors.New("cannot search a nil list or set") } if args[0].LengthInt() == 0 { return cty.False, nil } if !args[0].IsKnown() || !args[1].IsKnown() { return cty.UnknownVal(cty.Bool), nil } containsUnknown := false for it := args[0].ElementIterator(); it.Next(); { _, v := it.Element() eq := args[1].Equals(v) if !eq.IsKnown() { // We may have an unknown value which could match later, but we // first need to continue checking all values for an exact // match. containsUnknown = true continue } if eq.True() { return cty.True, nil } } if containsUnknown { return cty.UnknownVal(cty.Bool), nil } return cty.False, nil }, }) // DistinctFunc is a function that takes a list and returns a new list // with any duplicate elements removed. var DistinctFunc = function.New(&function.Spec{ Description: `Removes any duplicate values from the given list, preserving the order of remaining elements.`, Params: []function.Parameter{ { Name: "list", Type: cty.List(cty.DynamicPseudoType), }, }, Type: func(args []cty.Value) (cty.Type, error) { return args[0].Type(), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { listVal := args[0] if !listVal.IsWhollyKnown() { return cty.UnknownVal(retType), nil } var list []cty.Value for it := listVal.ElementIterator(); it.Next(); { _, v := it.Element() list, err = appendIfMissing(list, v) if err != nil { return cty.NilVal, err } } if len(list) == 0 { return cty.ListValEmpty(retType.ElementType()), nil } return cty.ListVal(list), nil }, }) // ChunklistFunc is a function that splits a single list into fixed-size chunks, // returning a list of lists. var ChunklistFunc = function.New(&function.Spec{ Description: `Splits a single list into multiple lists where each has at most the given number of elements.`, Params: []function.Parameter{ { Name: "list", Description: `The list to split into chunks.`, Type: cty.List(cty.DynamicPseudoType), AllowMarked: true, }, { Name: "size", Description: `The maximum length of each chunk. All but the last element of the result is guaranteed to be of exactly this size.`, Type: cty.Number, AllowMarked: true, }, }, Type: func(args []cty.Value) (cty.Type, error) { return cty.List(args[0].Type()), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { listVal := args[0] sizeVal := args[1] listVal, listMarks := listVal.Unmark() sizeVal, sizeMarks := sizeVal.Unmark() // All return paths below must include .WithMarks(retMarks) to propagate // the top-level marks into the return value. Deep marks inside the // list will just propagate naturally because we treat those values // as opaque here. retMarks := cty.NewValueMarks(listMarks, sizeMarks) var size int err = gocty.FromCtyValue(sizeVal, &size) if err != nil { return cty.NilVal, fmt.Errorf("invalid size: %s", err) } if size < 0 { return cty.NilVal, errors.New("the size argument must be positive") } if listVal.LengthInt() == 0 { return cty.ListValEmpty(listVal.Type()).WithMarks(retMarks), nil } output := make([]cty.Value, 0) // if size is 0, returns a list made of the initial list if size == 0 { output = append(output, listVal) return cty.ListVal(output).WithMarks(retMarks), nil } chunk := make([]cty.Value, 0) l := listVal.LengthInt() i := 0 for it := listVal.ElementIterator(); it.Next(); { _, v := it.Element() chunk = append(chunk, v) // Chunk when index isn't 0, or when reaching the values's length if (i+1)%size == 0 || (i+1) == l { output = append(output, cty.ListVal(chunk)) chunk = make([]cty.Value, 0) } i++ } return cty.ListVal(output).WithMarks(retMarks), nil }, }) // FlattenFunc is a function that takes a list and replaces any elements // that are lists with a flattened sequence of the list contents. var FlattenFunc = function.New(&function.Spec{ Description: `Transforms a list, set, or tuple value into a tuple by replacing any given elements that are themselves sequences with a flattened tuple of all of the nested elements concatenated together.`, Params: []function.Parameter{ { Name: "list", Type: cty.DynamicPseudoType, AllowMarked: true, }, }, Type: func(args []cty.Value) (cty.Type, error) { if !args[0].IsWhollyKnown() { return cty.DynamicPseudoType, nil } argTy := args[0].Type() if !argTy.IsListType() && !argTy.IsSetType() && !argTy.IsTupleType() { return cty.NilType, errors.New("can only flatten lists, sets and tuples") } // marks are attached to values, so ignore while determining type retVal, _, known := flattener(args[0]) if !known { return cty.DynamicPseudoType, nil } tys := make([]cty.Type, len(retVal)) for i, ty := range retVal { tys[i] = ty.Type() } return cty.Tuple(tys), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { inputList := args[0] if unmarked, marks := inputList.Unmark(); unmarked.LengthInt() == 0 { return cty.EmptyTupleVal.WithMarks(marks), nil } out, markses, known := flattener(inputList) if !known { return cty.UnknownVal(retType).WithMarks(markses...), nil } return cty.TupleVal(out).WithMarks(markses...), nil }, }) // Flatten until it's not a cty.List, and return whether the value is known. // We can flatten lists with unknown values, as long as they are not // lists themselves. func flattener(flattenList cty.Value) ([]cty.Value, []cty.ValueMarks, bool) { var markses []cty.ValueMarks flattenList, flattenListMarks := flattenList.Unmark() if len(flattenListMarks) > 0 { markses = append(markses, flattenListMarks) } if !flattenList.Length().IsKnown() { // If we don't know the length of what we're flattening then we can't // predict the length of our result yet either. return nil, markses, false } out := make([]cty.Value, 0) isKnown := true for it := flattenList.ElementIterator(); it.Next(); { _, val := it.Element() // Any dynamic types could result in more collections that need to be // flattened, so the type cannot be known. if val == cty.DynamicVal { isKnown = false } if !val.IsNull() && (val.Type().IsListType() || val.Type().IsSetType() || val.Type().IsTupleType()) { if !val.IsKnown() { isKnown = false _, unknownMarks := val.Unmark() markses = append(markses, unknownMarks) continue } res, resMarks, known := flattener(val) markses = append(markses, resMarks...) if known { out = append(out, res...) } else { isKnown = false } } else { out = append(out, val) } } return out, markses, isKnown } // KeysFunc is a function that takes a map and returns a sorted list of the map keys. var KeysFunc = function.New(&function.Spec{ Description: `Returns a list of the keys of the given map in lexicographical order.`, Params: []function.Parameter{ { Name: "inputMap", Description: `The map to extract keys from. May instead be an object-typed value, in which case the result is a tuple of the object attributes.`, Type: cty.DynamicPseudoType, AllowUnknown: true, AllowMarked: true, }, }, Type: func(args []cty.Value) (cty.Type, error) { ty := args[0].Type() switch { case ty.IsMapType(): return cty.List(cty.String), nil case ty.IsObjectType(): atys := ty.AttributeTypes() if len(atys) == 0 { return cty.EmptyTuple, nil } // All of our result elements will be strings, and atys just // decides how many there are. etys := make([]cty.Type, len(atys)) for i := range etys { etys[i] = cty.String } return cty.Tuple(etys), nil default: return cty.DynamicPseudoType, function.NewArgErrorf(0, "must have map or object type") } }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { // We must unmark the value before we can use ElementIterator on it, and // then re-apply the same marks (possibly none) when we return. Since we // don't mark map keys, we can throw away any nested marks, which would // only apply to values. m, marks := args[0].Unmark() var keys []cty.Value switch { case m.Type().IsObjectType(): // In this case we allow unknown values so we must work only with // the attribute _types_, not with the value itself. var names []string for name := range m.Type().AttributeTypes() { names = append(names, name) } sort.Strings(names) // same ordering guaranteed by cty's ElementIterator if len(names) == 0 { return cty.EmptyTupleVal.WithMarks(marks), nil } keys = make([]cty.Value, len(names)) for i, name := range names { keys[i] = cty.StringVal(name) } return cty.TupleVal(keys).WithMarks(marks), nil default: if !m.IsKnown() { return cty.UnknownVal(retType).WithMarks(marks), nil } // cty guarantees that ElementIterator will iterate in lexicographical // order by key. for it := m.ElementIterator(); it.Next(); { k, _ := it.Element() keys = append(keys, k) } if len(keys) == 0 { return cty.ListValEmpty(cty.String).WithMarks(marks), nil } return cty.ListVal(keys).WithMarks(marks), nil } }, }) // LookupFunc is a function that performs dynamic lookups of map types. var LookupFunc = function.New(&function.Spec{ Description: `Returns the value of the element with the given key from the given map, or returns the default value if there is no such element.`, Params: []function.Parameter{ { Name: "inputMap", Type: cty.DynamicPseudoType, AllowMarked: true, }, { Name: "key", Type: cty.String, AllowMarked: true, }, { Name: "default", Type: cty.DynamicPseudoType, AllowMarked: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { ty := args[0].Type() switch { case ty.IsObjectType(): if !args[1].IsKnown() { return cty.DynamicPseudoType, nil } keyVal, _ := args[1].Unmark() key := keyVal.AsString() if ty.HasAttribute(key) { return args[0].GetAttr(key).Type(), nil } else if len(args) == 3 { // if the key isn't found but a default is provided, // return the default type return args[2].Type(), nil } return cty.DynamicPseudoType, function.NewArgErrorf(0, "the given object has no attribute %q", key) case ty.IsMapType(): if len(args) == 3 { _, err = convert.Convert(args[2], ty.ElementType()) if err != nil { return cty.NilType, function.NewArgErrorf(2, "the default value must have the same type as the map elements") } } return ty.ElementType(), nil default: return cty.NilType, function.NewArgErrorf(0, "lookup() requires a map as the first argument") } }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // leave default value marked defaultVal := args[2] var markses []cty.ValueMarks // unmark collection, retain marks to reapply later mapVar, mapMarks := args[0].Unmark() markses = append(markses, mapMarks) // include marks on the key in the result keyVal, keyMarks := args[1].Unmark() if len(keyMarks) > 0 { markses = append(markses, keyMarks) } lookupKey := keyVal.AsString() if !mapVar.IsWhollyKnown() { return cty.UnknownVal(retType).WithMarks(markses...), nil } if mapVar.Type().IsObjectType() { if mapVar.Type().HasAttribute(lookupKey) { return mapVar.GetAttr(lookupKey).WithMarks(markses...), nil } } else if mapVar.HasIndex(cty.StringVal(lookupKey)) == cty.True { return mapVar.Index(cty.StringVal(lookupKey)).WithMarks(markses...), nil } defaultVal, err = convert.Convert(defaultVal, retType) if err != nil { return cty.NilVal, err } return defaultVal.WithMarks(markses...), nil }, }) // MergeFunc constructs a function that takes an arbitrary number of maps or // objects, and returns a single value that contains a merged set of keys and // values from all of the inputs. // // If more than one given map or object defines the same key then the one that // is later in the argument sequence takes precedence. var MergeFunc = function.New(&function.Spec{ Description: `Merges all of the elements from the given maps into a single map, or the attributes from given objects into a single object.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "maps", Type: cty.DynamicPseudoType, AllowDynamicType: true, AllowNull: true, AllowMarked: true, }, Type: func(args []cty.Value) (cty.Type, error) { // empty args is accepted, so assume an empty object since we have no // key-value types. if len(args) == 0 { return cty.EmptyObject, nil } // collect the possible object attrs attrs := map[string]cty.Type{} first := cty.NilType matching := true attrsKnown := true for i, arg := range args { ty := arg.Type() // any dynamic args mean we can't compute a type if ty.Equals(cty.DynamicPseudoType) { return cty.DynamicPseudoType, nil } // check for invalid arguments if !ty.IsMapType() && !ty.IsObjectType() { return cty.NilType, fmt.Errorf("arguments must be maps or objects, got %#v", ty.FriendlyName()) } // marks are attached to values, so ignore while determining type arg, _ = arg.Unmark() switch { case ty.IsObjectType() && !arg.IsNull(): for attr, aty := range ty.AttributeTypes() { attrs[attr] = aty } case ty.IsMapType(): switch { case arg.IsNull(): // pass, nothing to add case arg.IsKnown(): ety := arg.Type().ElementType() for it := arg.ElementIterator(); it.Next(); { attr, _ := it.Element() attrs[attr.AsString()] = ety } default: // any unknown maps means we don't know all possible attrs // for the return type attrsKnown = false } } // record the first argument type for comparison if i == 0 { first = arg.Type() continue } if !ty.Equals(first) && matching { matching = false } } // the types all match, so use the first argument type if matching { return first, nil } // We had a mix of unknown maps and objects, so we can't predict the // attributes if !attrsKnown { return cty.DynamicPseudoType, nil } return cty.Object(attrs), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { outputMap := make(map[string]cty.Value) var markses []cty.ValueMarks // remember any marked maps/objects we find for _, arg := range args { if arg.IsNull() { continue } arg, argMarks := arg.Unmark() if len(argMarks) > 0 { markses = append(markses, argMarks) } for it := arg.ElementIterator(); it.Next(); { k, v := it.Element() outputMap[k.AsString()] = v } } switch { case retType.IsMapType(): if len(outputMap) == 0 { return cty.MapValEmpty(retType.ElementType()).WithMarks(markses...), nil } return cty.MapVal(outputMap).WithMarks(markses...), nil case retType.IsObjectType(), retType.Equals(cty.DynamicPseudoType): return cty.ObjectVal(outputMap).WithMarks(markses...), nil default: panic(fmt.Sprintf("unexpected return type: %#v", retType)) } }, }) // ReverseListFunc takes a sequence and produces a new sequence of the same length // with all of the same elements as the given sequence but in reverse order. var ReverseListFunc = function.New(&function.Spec{ Description: `Returns the given list with its elements in reverse order.`, Params: []function.Parameter{ { Name: "list", Type: cty.DynamicPseudoType, AllowMarked: true, }, }, Type: func(args []cty.Value) (cty.Type, error) { argTy := args[0].Type() switch { case argTy.IsTupleType(): argTys := argTy.TupleElementTypes() retTys := make([]cty.Type, len(argTys)) for i, ty := range argTys { retTys[len(retTys)-i-1] = ty } return cty.Tuple(retTys), nil case argTy.IsListType(), argTy.IsSetType(): // We accept sets here to mimic the usual behavior of auto-converting to list return cty.List(argTy.ElementType()), nil default: return cty.NilType, function.NewArgErrorf(0, "can only reverse list or tuple values, not %s", argTy.FriendlyName()) } }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { in, marks := args[0].Unmark() inVals := in.AsValueSlice() outVals := make([]cty.Value, len(inVals)) for i, v := range inVals { outVals[len(outVals)-i-1] = v } switch { case retType.IsTupleType(): return cty.TupleVal(outVals).WithMarks(marks), nil default: if len(outVals) == 0 { return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil } return cty.ListVal(outVals).WithMarks(marks), nil } }, }) // SetProductFunc calculates the Cartesian product of two or more sets or // sequences. If the arguments are all lists then the result is a list of tuples, // preserving the ordering of all of the input lists. Otherwise the result is a // set of tuples. var SetProductFunc = function.New(&function.Spec{ Description: `Calculates the cartesian product of two or more sets.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "sets", Description: "The sets to consider. Also accepts lists and tuples, and if all arguments are of list or tuple type then the result will preserve the input ordering", Type: cty.DynamicPseudoType, AllowMarked: true, }, Type: func(args []cty.Value) (retType cty.Type, err error) { if len(args) < 2 { return cty.NilType, errors.New("at least two arguments are required") } listCount := 0 elemTys := make([]cty.Type, len(args)) for i, arg := range args { aty := arg.Type() switch { case aty.IsSetType(): elemTys[i] = aty.ElementType() case aty.IsListType(): elemTys[i] = aty.ElementType() listCount++ case aty.IsTupleType(): // We can accept a tuple type only if there's some common type // that all of its elements can be converted to. allEtys := aty.TupleElementTypes() if len(allEtys) == 0 { elemTys[i] = cty.DynamicPseudoType listCount++ break } ety, _ := convert.UnifyUnsafe(allEtys) if ety == cty.NilType { return cty.NilType, function.NewArgErrorf(i, "all elements must be of the same type") } elemTys[i] = ety listCount++ default: return cty.NilType, function.NewArgErrorf(i, "a set or a list is required") } } if listCount == len(args) { return cty.List(cty.Tuple(elemTys)), nil } return cty.Set(cty.Tuple(elemTys)), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { ety := retType.ElementType() var retMarks cty.ValueMarks total := 1 var hasUnknownLength bool for _, arg := range args { arg, marks := arg.Unmark() retMarks = cty.NewValueMarks(retMarks, marks) // Continue processing after we find an argument with unknown // length to ensure that we cover all the marks if !arg.Length().IsKnown() { hasUnknownLength = true continue } // Because of our type checking function, we are guaranteed that // all of the arguments are known, non-null values of types that // support LengthInt. total *= arg.LengthInt() } if hasUnknownLength { return cty.UnknownVal(retType).WithMarks(retMarks), nil } if total == 0 { // If any of the arguments was an empty collection then our result // is also an empty collection, which we'll short-circuit here. if retType.IsListType() { return cty.ListValEmpty(ety).WithMarks(retMarks), nil } return cty.SetValEmpty(ety).WithMarks(retMarks), nil } subEtys := ety.TupleElementTypes() product := make([][]cty.Value, total) b := make([]cty.Value, total*len(args)) n := make([]int, len(args)) s := 0 argVals := make([][]cty.Value, len(args)) for i, arg := range args { // We've already stored the marks in retMarks arg, _ := arg.Unmark() argVals[i] = arg.AsValueSlice() } for i := range product { e := s + len(args) pi := b[s:e] product[i] = pi s = e for j, n := range n { val := argVals[j][n] ty := subEtys[j] if !val.Type().Equals(ty) { var err error val, err = convert.Convert(val, ty) if err != nil { // Should never happen since we checked this in our // type-checking function. return cty.NilVal, fmt.Errorf("failed to convert argVals[%d][%d] to %s; this is a bug in cty", j, n, ty.FriendlyName()) } } pi[j] = val } for j := len(n) - 1; j >= 0; j-- { n[j]++ if n[j] < len(argVals[j]) { break } n[j] = 0 } } productVals := make([]cty.Value, total) for i, vals := range product { productVals[i] = cty.TupleVal(vals) } if retType.IsListType() { return cty.ListVal(productVals).WithMarks(retMarks), nil } return cty.SetVal(productVals).WithMarks(retMarks), nil }, }) // SliceFunc is a function that extracts some consecutive elements // from within a list. var SliceFunc = function.New(&function.Spec{ Description: `Extracts a subslice of the given list or tuple value.`, Params: []function.Parameter{ { Name: "list", Type: cty.DynamicPseudoType, AllowMarked: true, }, { Name: "start_index", Type: cty.Number, }, { Name: "end_index", Type: cty.Number, }, }, Type: func(args []cty.Value) (cty.Type, error) { arg := args[0] argTy := arg.Type() if argTy.IsSetType() { return cty.NilType, function.NewArgErrorf(0, "cannot slice a set, because its elements do not have indices; explicitly convert to a list if the ordering of the result is not important") } if !argTy.IsListType() && !argTy.IsTupleType() { return cty.NilType, function.NewArgErrorf(0, "must be a list or tuple value") } startIndex, endIndex, idxsKnown, err := sliceIndexes(args) if err != nil { return cty.NilType, err } if argTy.IsListType() { return argTy, nil } if !idxsKnown { // If we don't know our start/end indices then we can't predict // the result type if we're planning to return a tuple. return cty.DynamicPseudoType, nil } return cty.Tuple(argTy.TupleElementTypes()[startIndex:endIndex]), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { inputList, marks := args[0].Unmark() if retType == cty.DynamicPseudoType { return cty.DynamicVal.WithMarks(marks), nil } // we ignore idxsKnown return value here because the indices are always // known here, or else the call would've short-circuited. startIndex, endIndex, _, err := sliceIndexes(args) if err != nil { return cty.NilVal, err } if endIndex-startIndex == 0 { if retType.IsTupleType() { return cty.EmptyTupleVal.WithMarks(marks), nil } return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil } outputList := inputList.AsValueSlice()[startIndex:endIndex] if retType.IsTupleType() { return cty.TupleVal(outputList).WithMarks(marks), nil } return cty.ListVal(outputList).WithMarks(marks), nil }, }) func sliceIndexes(args []cty.Value) (int, int, bool, error) { var startIndex, endIndex, length int var startKnown, endKnown, lengthKnown bool // remove marks from args[0] list, _ := args[0].Unmark() // If it's a tuple then we always know the length by the type, but collections might be unknown or have unknown length if list.Type().IsTupleType() || list.Length().IsKnown() { length = list.LengthInt() lengthKnown = true } if args[1].IsKnown() { if err := gocty.FromCtyValue(args[1], &startIndex); err != nil { return 0, 0, false, function.NewArgErrorf(1, "invalid start index: %s", err) } if startIndex < 0 { return 0, 0, false, function.NewArgErrorf(1, "start index must not be less than zero") } if lengthKnown && startIndex > length { return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than the length of the list") } startKnown = true } if args[2].IsKnown() { if err := gocty.FromCtyValue(args[2], &endIndex); err != nil { return 0, 0, false, function.NewArgErrorf(2, "invalid end index: %s", err) } if endIndex < 0 { return 0, 0, false, function.NewArgErrorf(2, "end index must not be less than zero") } if lengthKnown && endIndex > length { return 0, 0, false, function.NewArgErrorf(2, "end index must not be greater than the length of the list") } endKnown = true } if startKnown && endKnown { if startIndex > endIndex { return 0, 0, false, function.NewArgErrorf(1, "start index must not be greater than end index") } } return startIndex, endIndex, startKnown && endKnown, nil } // ValuesFunc is a function that returns a list of the map values, // in the order of the sorted keys. var ValuesFunc = function.New(&function.Spec{ Description: `Returns the values of elements of a given map, or the values of attributes of a given object, in lexicographic order by key or attribute name.`, Params: []function.Parameter{ { Name: "mapping", Type: cty.DynamicPseudoType, AllowMarked: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { ty := args[0].Type() if ty.IsMapType() { return cty.List(ty.ElementType()), nil } else if ty.IsObjectType() { // The result is a tuple type with all of the same types as our // object type's attributes, sorted in lexicographical order by the // keys. (This matches the sort order guaranteed by ElementIterator // on a cty object value.) atys := ty.AttributeTypes() if len(atys) == 0 { return cty.EmptyTuple, nil } attrNames := make([]string, 0, len(atys)) for name := range atys { attrNames = append(attrNames, name) } sort.Strings(attrNames) tys := make([]cty.Type, len(attrNames)) for i, name := range attrNames { tys[i] = atys[name] } return cty.Tuple(tys), nil } return cty.NilType, errors.New("values() requires a map as the first argument") }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { mapVar := args[0] // We must unmark the value before we can use ElementIterator on it, // and then re-apply the same marks (possibly none) when we return. // (We leave the inner values just as they are, because we won't be // doing anything with them aside from copying them verbatim into the // result, marks and all.) mapVar, marks := mapVar.Unmark() // We can just iterate the map/object value here because cty guarantees // that these types always iterate in key lexicographical order. var values []cty.Value for it := mapVar.ElementIterator(); it.Next(); { _, val := it.Element() values = append(values, val) } // All of the return paths must include .WithMarks(marks) so that we // will preserve the markings of the overall map/object we were given. if retType.IsTupleType() { return cty.TupleVal(values).WithMarks(marks), nil } if len(values) == 0 { return cty.ListValEmpty(retType.ElementType()).WithMarks(marks), nil } return cty.ListVal(values).WithMarks(marks), nil }, }) // ZipmapFunc is a function that constructs a map from a list of keys // and a corresponding list of values. var ZipmapFunc = function.New(&function.Spec{ Description: `Constructs a map from a list of keys and a corresponding list of values, which must both be of the same length.`, Params: []function.Parameter{ { Name: "keys", Type: cty.List(cty.String), AllowMarked: true, }, { Name: "values", Type: cty.DynamicPseudoType, AllowMarked: true, }, }, Type: func(args []cty.Value) (ret cty.Type, err error) { keys := args[0] values := args[1] valuesTy := values.Type() switch { case valuesTy.IsListType(): return cty.Map(values.Type().ElementType()), nil case valuesTy.IsTupleType(): if !keys.IsWhollyKnown() { // Since zipmap with a tuple produces an object, we need to know // all of the key names before we can predict our result type. return cty.DynamicPseudoType, nil } // NOTE: Marking of the keys list can't be represented in the // result type, so the tuple type here will disclose the keys. // This is unfortunate but is a common compromise with dynamic // return types; the result from Impl will still reflect the marks // from the keys list, so a mark-using caller should look out for // that if it's important for their use-case. keys, _ := keys.Unmark() keysRaw := keys.AsValueSlice() valueTypesRaw := valuesTy.TupleElementTypes() if len(keysRaw) != len(valueTypesRaw) { return cty.NilType, fmt.Errorf("number of keys (%d) does not match number of values (%d)", len(keysRaw), len(valueTypesRaw)) } atys := make(map[string]cty.Type, len(valueTypesRaw)) for i, keyVal := range keysRaw { keyVal, _ = keyVal.Unmark() if keyVal.IsNull() { return cty.NilType, fmt.Errorf("keys list has null value at index %d", i) } key := keyVal.AsString() atys[key] = valueTypesRaw[i] } return cty.Object(atys), nil default: return cty.NilType, errors.New("values argument must be a list or tuple value") } }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { keys := args[0] values := args[1] keys, keysMarks := keys.Unmark() values, valuesMarks := values.Unmark() // All of our return paths must pass through the merged marks from // both the keys and the values, if any, using .WithMarks(retMarks) retMarks := cty.NewValueMarks(keysMarks, valuesMarks) if !keys.IsWhollyKnown() { // Unknown map keys and object attributes are not supported, so // our entire result must be unknown in this case. return cty.UnknownVal(retType).WithMarks(retMarks), nil } // both keys and values are guaranteed to be shallowly-known here, // because our declared params above don't allow unknown or null values. if keys.LengthInt() != values.LengthInt() { return cty.NilVal, fmt.Errorf("number of keys (%d) does not match number of values (%d)", keys.LengthInt(), values.LengthInt()) } output := make(map[string]cty.Value) i := 0 for it := keys.ElementIterator(); it.Next(); { _, v := it.Element() v, vMarks := v.Unmark() val := values.Index(cty.NumberIntVal(int64(i))) output[v.AsString()] = val // We also need to accumulate the individual key marks on the // returned map, because keys can't carry marks on their own. retMarks = cty.NewValueMarks(retMarks, vMarks) i++ } switch { case retType.IsMapType(): if len(output) == 0 { return cty.MapValEmpty(retType.ElementType()).WithMarks(retMarks), nil } return cty.MapVal(output).WithMarks(retMarks), nil case retType.IsObjectType(): return cty.ObjectVal(output).WithMarks(retMarks), nil default: // Should never happen because the type-check function should've // caught any other case. return cty.NilVal, fmt.Errorf("internally selected incorrect result type %s (this is a bug)", retType.FriendlyName()) } }, }) // helper function to add an element to a list, if it does not already exist func appendIfMissing(slice []cty.Value, element cty.Value) ([]cty.Value, error) { for _, ele := range slice { eq, err := Equal(ele, element) if err != nil { return slice, err } if eq.True() { return slice, nil } } return append(slice, element), nil } // HasIndex determines whether the given collection can be indexed with the // given key. func HasIndex(collection cty.Value, key cty.Value) (cty.Value, error) { return HasIndexFunc.Call([]cty.Value{collection, key}) } // Index returns an element from the given collection using the given key, // or returns an error if there is no element for the given key. func Index(collection cty.Value, key cty.Value) (cty.Value, error) { return IndexFunc.Call([]cty.Value{collection, key}) } // Length returns the number of elements in the given collection. func Length(collection cty.Value) (cty.Value, error) { return LengthFunc.Call([]cty.Value{collection}) } // Element returns a single element from a given list at the given index. If // index is greater than the length of the list then it is wrapped modulo // the list length. func Element(list, index cty.Value) (cty.Value, error) { return ElementFunc.Call([]cty.Value{list, index}) } // CoalesceList takes any number of list arguments and returns the first one that isn't empty. func CoalesceList(args ...cty.Value) (cty.Value, error) { return CoalesceListFunc.Call(args) } // Compact takes a list of strings and returns a new list // with any empty string elements removed. func Compact(list cty.Value) (cty.Value, error) { return CompactFunc.Call([]cty.Value{list}) } // Contains determines whether a given list contains a given single value // as one of its elements. func Contains(list, value cty.Value) (cty.Value, error) { return ContainsFunc.Call([]cty.Value{list, value}) } // Distinct takes a list and returns a new list with any duplicate elements removed. func Distinct(list cty.Value) (cty.Value, error) { return DistinctFunc.Call([]cty.Value{list}) } // Chunklist splits a single list into fixed-size chunks, returning a list of lists. func Chunklist(list, size cty.Value) (cty.Value, error) { return ChunklistFunc.Call([]cty.Value{list, size}) } // Flatten takes a list and replaces any elements that are lists with a flattened // sequence of the list contents. func Flatten(list cty.Value) (cty.Value, error) { return FlattenFunc.Call([]cty.Value{list}) } // Keys takes a map and returns a sorted list of the map keys. func Keys(inputMap cty.Value) (cty.Value, error) { return KeysFunc.Call([]cty.Value{inputMap}) } // Lookup performs a dynamic lookup into a map. // There are two required arguments, map and key, plus an optional default, // which is a value to return if no key is found in map. func Lookup(inputMap, key, defaultValue cty.Value) (cty.Value, error) { return LookupFunc.Call([]cty.Value{inputMap, key, defaultValue}) } // Merge takes an arbitrary number of maps and returns a single map that contains // a merged set of elements from all of the maps. // // If more than one given map defines the same key then the one that is later in // the argument sequence takes precedence. func Merge(maps ...cty.Value) (cty.Value, error) { return MergeFunc.Call(maps) } // ReverseList takes a sequence and produces a new sequence of the same length // with all of the same elements as the given sequence but in reverse order. func ReverseList(list cty.Value) (cty.Value, error) { return ReverseListFunc.Call([]cty.Value{list}) } // SetProduct computes the Cartesian product of sets or sequences. func SetProduct(sets ...cty.Value) (cty.Value, error) { return SetProductFunc.Call(sets) } // Slice extracts some consecutive elements from within a list. func Slice(list, start, end cty.Value) (cty.Value, error) { return SliceFunc.Call([]cty.Value{list, start, end}) } // Values returns a list of the map values, in the order of the sorted keys. // This function only works on flat maps. func Values(values cty.Value) (cty.Value, error) { return ValuesFunc.Call([]cty.Value{values}) } // Zipmap constructs a map from a list of keys and a corresponding list of values. func Zipmap(keys, values cty.Value) (cty.Value, error) { return ZipmapFunc.Call([]cty.Value{keys, values}) } go-cty-1.12.1/cty/function/stdlib/collection_test.go000066400000000000000000001736001433256746400224510ustar00rootroot00000000000000package stdlib import ( "fmt" "math" "testing" "github.com/zclconf/go-cty/cty" ) func TestHasIndex(t *testing.T) { tests := []struct { Collection cty.Value Key cty.Value Want cty.Value }{ { cty.ListValEmpty(cty.Number), cty.NumberIntVal(2), cty.False, }, { cty.ListVal([]cty.Value{cty.True}), cty.NumberIntVal(0), cty.True, }, { cty.ListVal([]cty.Value{cty.True}), cty.StringVal("hello"), cty.False, }, { cty.MapValEmpty(cty.Bool), cty.StringVal("hello"), cty.False, }, { cty.MapVal(map[string]cty.Value{"hello": cty.True}), cty.StringVal("hello"), cty.True, }, { cty.EmptyTupleVal, cty.StringVal("hello"), cty.False, }, { cty.EmptyTupleVal, cty.NumberIntVal(0), cty.False, }, { cty.TupleVal([]cty.Value{cty.True}), cty.NumberIntVal(0), cty.True, }, { cty.ListValEmpty(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.List(cty.Bool)), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.ListValEmpty(cty.Number), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("HasIndex(%#v,%#v)", test.Collection, test.Key), func(t *testing.T) { got, err := HasIndex(test.Collection, test.Key) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestChunklist(t *testing.T) { tests := []struct { List cty.Value Len cty.Value Want cty.Value Err string }{ { cty.ListValEmpty(cty.String), cty.NumberIntVal(2), cty.ListValEmpty(cty.List(cty.String)), ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.NumberIntVal(2), cty.UnknownVal(cty.List(cty.List(cty.String))), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a"), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), }), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }), }), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a"), }).Mark("a"), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }).Mark("a"), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), }), }), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), }), ``, }, { // Multiple result elements, one shorter cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.ListVal([]cty.Value{ cty.StringVal("c"), }), }), ``, }, { // Multiple result elements, all "full" cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), cty.StringVal("d"), cty.StringVal("e"), cty.StringVal("f"), }), cty.NumberIntVal(2), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), cty.ListVal([]cty.Value{ cty.StringVal("e"), cty.StringVal("f"), }), }), ``, }, { // We treat length zero as infinite length cty.ListVal([]cty.Value{ cty.StringVal("a"), }), cty.Zero, cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), }), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a"), }).Mark("a"), cty.Zero, cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a"), }), cty.Zero.Mark("a"), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }), cty.Zero, cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("b"), }), }), ``, }, { cty.ListValEmpty(cty.String), cty.NumberIntVal(-1), cty.NilVal, `the size argument must be positive`, }, { cty.ListValEmpty(cty.String), cty.PositiveInfinity, cty.NilVal, fmt.Sprintf(`invalid size: value must be a whole number, between %d and %d`, math.MinInt, math.MaxInt), }, { cty.ListValEmpty(cty.String), cty.NumberFloatVal(1.5), cty.NilVal, fmt.Sprintf(`invalid size: value must be a whole number, between %d and %d`, math.MinInt, math.MaxInt), }, } for _, test := range tests { t.Run(fmt.Sprintf("Chunklist(%#v, %#v)", test.List, test.Len), func(t *testing.T) { got, err := Chunklist(test.List, test.Len) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestContains(t *testing.T) { listOfStrings := cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("quick"), cty.StringVal("brown"), cty.StringVal("fox"), }) listOfInts := cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), }) listWithUnknown := cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("quick"), cty.StringVal("brown"), cty.UnknownVal(cty.String), }) tests := []struct { List cty.Value Value cty.Value Want cty.Value Err bool }{ { listOfStrings, cty.StringVal("the"), cty.BoolVal(true), false, }, { listWithUnknown, cty.StringVal("the"), cty.BoolVal(true), false, }, { listWithUnknown, cty.StringVal("orange"), cty.UnknownVal(cty.Bool), false, }, { listOfStrings, cty.StringVal("penguin"), cty.BoolVal(false), false, }, { listOfInts, cty.NumberIntVal(1), cty.BoolVal(true), false, }, { listOfInts, cty.NumberIntVal(42), cty.BoolVal(false), false, }, { // And now we mix and match listOfInts, cty.StringVal("1"), cty.BoolVal(false), false, }, { // Check a list with an unknown value cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("quick"), cty.StringVal("brown"), cty.StringVal("fox"), }), cty.StringVal("quick"), cty.BoolVal(true), false, }, { cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("brown"), cty.StringVal("fox"), }), cty.StringVal("quick"), cty.UnknownVal(cty.Bool), false, }, { // set val cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("brown"), cty.StringVal("fox"), }), cty.StringVal("quick"), cty.BoolVal(true), false, }, { cty.SetVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("brown"), cty.StringVal("fox"), }), cty.StringVal("quick"), cty.UnknownVal(cty.Bool), false, }, { // nested unknown cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.String), }), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("b"), }), cty.UnknownVal(cty.Bool), false, }, { // tuple val cty.TupleVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("brown"), cty.NumberIntVal(3), }), cty.NumberIntVal(3), cty.BoolVal(true), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("contains(%#v, %#v)", test.List, test.Value), func(t *testing.T) { got, err := Contains(test.List, test.Value) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestMerge(t *testing.T) { tests := []struct { Values []cty.Value Want cty.Value Err bool }{ { []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("b"), }), cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("b"), "c": cty.StringVal("d"), }), false, }, { // handle unknowns []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.String), }), cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.String), "c": cty.StringVal("d"), }), false, }, { // handle null map []cty.Value{ cty.NullVal(cty.Map(cty.String)), cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }, cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), false, }, { // all inputs are null []cty.Value{ cty.NullVal(cty.Map(cty.String)), cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.List(cty.String), })), }, cty.EmptyObjectVal, false, }, { // single null input []cty.Value{ cty.MapValEmpty(cty.String), }, cty.MapValEmpty(cty.String), false, }, { // handle null object []cty.Value{ cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), cty.NullVal(cty.Object(map[string]cty.Type{ "a": cty.List(cty.String), })), }, cty.ObjectVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), false, }, { // handle unknowns []cty.Value{ cty.UnknownVal(cty.Map(cty.String)), cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }, cty.UnknownVal(cty.Map(cty.String)), false, }, { // handle dynamic unknown []cty.Value{ cty.UnknownVal(cty.DynamicPseudoType), cty.MapVal(map[string]cty.Value{ "c": cty.StringVal("d"), }), }, cty.DynamicVal, false, }, { // merge with conflicts is ok, last in wins []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("b"), "c": cty.StringVal("d"), }), cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("x"), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("x"), "c": cty.StringVal("d"), }), false, }, { // only accept maps []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("b"), "c": cty.StringVal("d"), }), cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("x"), }), }, cty.NilVal, true, }, { // argument error, for a null type []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("b"), }), cty.NullVal(cty.String), }, cty.NilVal, true, }, { // merge maps of maps []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "b": cty.StringVal("c"), }), }), cty.MapVal(map[string]cty.Value{ "d": cty.MapVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.MapVal(map[string]cty.Value{ "b": cty.StringVal("c"), }), "d": cty.MapVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), }), false, }, { // map of lists []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), }), cty.MapVal(map[string]cty.Value{ "d": cty.ListVal([]cty.Value{ cty.StringVal("e"), cty.StringVal("f"), }), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), "d": cty.ListVal([]cty.Value{ cty.StringVal("e"), cty.StringVal("f"), }), }), false, }, { // merge map of various kinds []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), }), cty.MapVal(map[string]cty.Value{ "d": cty.MapVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), }), }, cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), "d": cty.MapVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), }), false, }, { // merge objects of various shapes []cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.DynamicVal, }), }, cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), }), "d": cty.DynamicVal, }), false, }, { // merge maps and objects []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), }), }), cty.ObjectVal(map[string]cty.Value{ "d": cty.NumberIntVal(2), }), }, cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), }), "d": cty.NumberIntVal(2), }), false, }, { // attr a type and value is overridden []cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), }), "b": cty.StringVal("b"), }), cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), }), }, cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "e": cty.StringVal("f"), }), "b": cty.StringVal("b"), }), false, }, { // argument error: non map type []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), }), cty.ListVal([]cty.Value{ cty.StringVal("d"), cty.StringVal("e"), }), }, cty.NilVal, true, }, { // Empty maps are allowed in merge []cty.Value{ cty.MapValEmpty(cty.String), cty.MapValEmpty(cty.String), }, cty.MapValEmpty(cty.String), false, }, { // Preserve marks from chosen elements []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a").Mark("first"), "c": cty.StringVal("c"), "d": cty.StringVal("d").Mark("first"), }), cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a"), "b": cty.StringVal("b").Mark("second"), "c": cty.StringVal("c").Mark("second"), }), }, cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a"), "b": cty.StringVal("b").Mark("second"), "c": cty.StringVal("c").Mark("second"), "d": cty.StringVal("d").Mark("first"), }), false, }, { // Marks on the collections must be merged, even if empty []cty.Value{ cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a"), }).Mark("first"), cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a"), "b": cty.StringVal("b"), }).Mark("second"), cty.MapValEmpty(cty.String).Mark("third"), }, cty.MapVal(map[string]cty.Value{ "a": cty.StringVal("a"), "b": cty.StringVal("b"), }).WithMarks(cty.NewValueMarks("first", "second", "third")), false, }, { // Similar test but where all args are the same object type []cty.Value{ cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("a"), "b": cty.NullVal(cty.String), }).Mark("first"), cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("A"), "b": cty.StringVal("B"), }).Mark("second"), }, cty.ObjectVal(map[string]cty.Value{ "a": cty.StringVal("A"), "b": cty.StringVal("B"), }).WithMarks(cty.NewValueMarks("first", "second")), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("merge(%#v)", test.Values), func(t *testing.T) { got, err := Merge(test.Values...) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestIndex(t *testing.T) { tests := []struct { Collection cty.Value Key cty.Value Want cty.Value }{ { cty.ListVal([]cty.Value{cty.True}), cty.NumberIntVal(0), cty.True, }, { cty.MapVal(map[string]cty.Value{"hello": cty.True}), cty.StringVal("hello"), cty.True, }, { cty.TupleVal([]cty.Value{cty.True, cty.StringVal("hello")}), cty.NumberIntVal(0), cty.True, }, { cty.TupleVal([]cty.Value{cty.True, cty.StringVal("hello")}), cty.NumberIntVal(1), cty.StringVal("hello"), }, { cty.ListValEmpty(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.List(cty.Bool)), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.ListValEmpty(cty.Number), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.MapValEmpty(cty.Number), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.StringVal("hello"), cty.DynamicVal, }, { cty.DynamicVal, cty.DynamicVal, cty.DynamicVal, }, } for _, test := range tests { t.Run(fmt.Sprintf("Index(%#v,%#v)", test.Collection, test.Key), func(t *testing.T) { got, err := Index(test.Collection, test.Key) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLength(t *testing.T) { tests := []struct { Collection cty.Value Want cty.Value }{ { cty.ListValEmpty(cty.Number), cty.NumberIntVal(0), }, { cty.ListVal([]cty.Value{cty.True}), cty.NumberIntVal(1), }, { cty.SetValEmpty(cty.Number), cty.NumberIntVal(0), }, { cty.SetVal([]cty.Value{cty.True}), cty.NumberIntVal(1), }, { cty.SetVal([]cty.Value{cty.True, cty.False}), cty.NumberIntVal(2), }, { cty.SetVal([]cty.Value{cty.True, cty.UnknownVal(cty.Bool)}), cty.UnknownVal(cty.Number), // Don't know if the unknown in the input represents cty.True or cty.False }, { cty.SetVal([]cty.Value{cty.UnknownVal(cty.Bool)}), cty.NumberIntVal(1), // Will be one regardless of what value the unknown in the input is representing }, { cty.MapValEmpty(cty.Bool), cty.NumberIntVal(0), }, { cty.MapVal(map[string]cty.Value{"hello": cty.True}), cty.NumberIntVal(1), }, { cty.EmptyTupleVal, cty.NumberIntVal(0), }, { cty.TupleVal([]cty.Value{cty.True}), cty.NumberIntVal(1), }, { cty.UnknownVal(cty.List(cty.Bool)), cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.UnknownVal(cty.Number), }, { // Marked collections return a marked length cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("world"), }).Mark("secret"), cty.NumberIntVal(2).Mark("secret"), }, { // Marks on values in unmarked collections do not propagate cty.ListVal([]cty.Value{ cty.StringVal("hello").Mark("a"), cty.StringVal("world").Mark("b"), }), cty.NumberIntVal(2), }, } for _, test := range tests { t.Run(fmt.Sprintf("Length(%#v)", test.Collection), func(t *testing.T) { got, err := Length(test.Collection) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLookup(t *testing.T) { tests := []struct { Collection cty.Value Key cty.Value Default cty.Value Want cty.Value }{ { cty.MapValEmpty(cty.String), cty.StringVal("baz"), cty.StringVal("foo"), cty.StringVal("foo"), }, { cty.MapVal(map[string]cty.Value{ "foo": cty.StringVal("bar"), }), cty.StringVal("foo"), cty.StringVal("nope"), cty.StringVal("bar"), }, { // successful marked collection lookup returns marked value cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep"), }).Mark("a"), cty.StringVal("boop"), cty.StringVal("nope"), cty.StringVal("beep").Mark("a"), }, { // apply collection marks to unknown return vaue cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep"), "frob": cty.UnknownVal(cty.String), }).Mark("a"), cty.StringVal("boop"), cty.StringVal("nope"), cty.UnknownVal(cty.String).Mark("a"), }, { // propagate collection marks to default when returning cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep"), }).Mark("a"), cty.StringVal("frob"), cty.StringVal("nope").Mark("b"), cty.StringVal("nope").WithMarks(cty.NewValueMarks("a", "b")), }, { // on unmarked collection, return only marks from found value cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep").Mark("a"), "frob": cty.StringVal("honk").Mark("b"), }), cty.StringVal("frob"), cty.StringVal("nope").Mark("c"), cty.StringVal("honk").Mark("b"), }, { // on unmarked collection, return default exactly on missing cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep").Mark("a"), "frob": cty.StringVal("honk").Mark("b"), }), cty.StringVal("squish"), cty.StringVal("nope").Mark("c"), cty.StringVal("nope").Mark("c"), }, { // retain marks on default if converted cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep").Mark("a"), "frob": cty.StringVal("honk").Mark("b"), }), cty.StringVal("squish"), cty.NumberIntVal(5).Mark("c"), cty.StringVal("5").Mark("c"), }, { // propagate marks from key cty.MapVal(map[string]cty.Value{ "boop": cty.StringVal("beep"), "frob": cty.StringVal("honk"), }), cty.StringVal("boop").Mark("a"), cty.StringVal("nope"), cty.StringVal("beep").Mark("a"), }, } for _, test := range tests { t.Run(fmt.Sprintf("Lookup(%#v,%#v,%#v)", test.Collection, test.Key, test.Default), func(t *testing.T) { got, err := Lookup(test.Collection, test.Key, test.Default) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestElement(t *testing.T) { listOfStrings := cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("quick"), cty.StringVal("brown"), cty.StringVal("fox"), }) listOfInts := cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), }) listWithUnknown := cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("quick"), cty.StringVal("brown"), cty.UnknownVal(cty.String), }) listWithMarks := cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("quick"), cty.StringVal("brown").Mark("fox"), cty.UnknownVal(cty.String), }) tests := []struct { List cty.Value Index cty.Value Want cty.Value Err bool }{ { listOfStrings, cty.NumberIntVal(2), cty.StringVal("brown"), false, }, { // index greater than length(list) listOfStrings, cty.NumberIntVal(5), cty.StringVal("quick"), false, }, { // list of lists cty.ListVal([]cty.Value{listOfStrings, listOfStrings}), cty.NumberIntVal(0), listOfStrings, false, }, { listOfStrings, cty.UnknownVal(cty.Number), cty.UnknownVal(cty.String), false, }, { listOfInts, cty.NumberIntVal(2), cty.NumberIntVal(3), false, }, { listWithUnknown, cty.NumberIntVal(2), cty.StringVal("brown"), false, }, { listWithUnknown, cty.NumberIntVal(3), cty.UnknownVal(cty.String), false, }, { // preserve marks listWithMarks, cty.NumberIntVal(2), cty.StringVal("brown").Mark("fox"), false, }, { // marked items listWithMarks, cty.NumberIntVal(1), cty.StringVal("quick"), false, }, { // The entire list is marked listWithMarks.Mark("thewholeshebang"), cty.NumberIntVal(2), cty.StringVal("brown").WithMarks(cty.NewValueMarks("thewholeshebang", "fox")), false, }, { listOfStrings, cty.NumberIntVal(-1), cty.DynamicVal, true, // index cannot be a negative number }, { listOfStrings, cty.StringVal("brown"), // definitely not an index cty.DynamicVal, true, }, } for _, test := range tests { t.Run(fmt.Sprintf("Element(%#v,%#v)", test.List, test.Index), func(t *testing.T) { got, err := Element(test.List, test.Index) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestCoalesceList(t *testing.T) { tests := map[string]struct { Values []cty.Value Want cty.Value Err bool }{ "returns first list if non-empty": { []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), false, }, "returns second list if first is empty": { []cty.Value{ cty.ListValEmpty(cty.String), cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), false, }, "return type is dynamic, not unified": { []cty.Value{ cty.ListValEmpty(cty.String), cty.ListVal([]cty.Value{ cty.NumberIntVal(3), cty.NumberIntVal(4), }), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(3), cty.NumberIntVal(4), }), false, }, "works with tuples": { []cty.Value{ cty.EmptyTupleVal, cty.TupleVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), }, cty.TupleVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), false, }, "unknown arguments": { []cty.Value{ cty.UnknownVal(cty.List(cty.String)), cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), }, cty.DynamicVal, false, }, "null arguments": { []cty.Value{ cty.NullVal(cty.List(cty.String)), cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("c"), cty.StringVal("d"), }), false, }, "all null arguments": { []cty.Value{ cty.NullVal(cty.List(cty.String)), cty.NullVal(cty.List(cty.String)), }, cty.NilVal, true, }, "invalid arguments": { []cty.Value{ cty.MapVal(map[string]cty.Value{"a": cty.True}), cty.ObjectVal(map[string]cty.Value{"b": cty.False}), }, cty.NilVal, true, }, "no arguments": { []cty.Value{}, cty.NilVal, true, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { got, err := CoalesceList(test.Values...) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestValues(t *testing.T) { tests := []struct { Collection cty.Value Want cty.Value Err string }{ { cty.MapValEmpty(cty.String), cty.ListValEmpty(cty.String), ``, }, { cty.MapValEmpty(cty.String).Mark("a"), cty.ListValEmpty(cty.String).Mark("a"), ``, }, { cty.NullVal(cty.Map(cty.String)), cty.NilVal, `argument must not be null`, }, { cty.UnknownVal(cty.Map(cty.String)), cty.UnknownVal(cty.List(cty.String)), ``, }, { cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world")}), cty.ListVal([]cty.Value{cty.StringVal("world")}), ``, }, { // The map itself is not marked, just an inner element. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}), cty.ListVal([]cty.Value{cty.StringVal("world").Mark("a")}), ``, }, { // The entire map is marked, so the resulting list is also marked. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("world")}).Mark("a"), ``, }, { // Marked both inside and outside. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("world").Mark("a")}).Mark("a"), ``, }, { cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world")}), cty.TupleVal([]cty.Value{cty.StringVal("world")}), ``, }, { cty.EmptyObjectVal, cty.EmptyTupleVal, ``, }, { cty.EmptyObjectVal.Mark("a"), cty.EmptyTupleVal.Mark("a"), ``, }, { cty.NullVal(cty.EmptyObject), cty.NilVal, `argument must not be null`, }, { cty.UnknownVal(cty.EmptyObject), cty.UnknownVal(cty.EmptyTuple), ``, }, { cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})), cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), ``, }, { // The object itself is not marked, just an inner attribute value. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}), cty.TupleVal([]cty.Value{cty.StringVal("world").Mark("a")}), ``, }, { // The entire object is marked, so the resulting tuple is also marked. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("world")}).Mark("a"), ``, }, { // Marked both inside and outside. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("world").Mark("a")}).Mark("a"), ``, }, } for _, test := range tests { t.Run(fmt.Sprintf("Values(%#v)", test.Collection), func(t *testing.T) { got, err := Values(test.Collection) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestZipMap(t *testing.T) { tests := []struct { Keys cty.Value Values cty.Value Want cty.Value Err string }{ // Lists of values (map result) { cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String), cty.MapValEmpty(cty.String), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.ListVal([]cty.Value{cty.StringVal("bloop")}), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep"), cty.StringVal("beep")}), cty.ListVal([]cty.Value{cty.StringVal("bloop"), cty.StringVal("boop")}), cty.MapVal(map[string]cty.Value{ "beep": cty.StringVal("boop"), "bleep": cty.StringVal("bloop"), }), ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.Map(cty.String)), ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.ListValEmpty(cty.String), cty.UnknownVal(cty.Map(cty.String)), ``, }, { cty.ListValEmpty(cty.String), cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.Map(cty.String)), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("bloop")}), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.ListVal([]cty.Value{cty.StringVal("bloop")}).Mark("b"), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("b"), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("bloop")}).Mark("b"), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a").Mark("b"), ``, }, { // cty map keys don't have individual marks, so marks on elements // in the keys list aggregate with the resulting map as a whole. cty.ListVal([]cty.Value{cty.StringVal("bleep").Mark("a")}), cty.ListVal([]cty.Value{cty.StringVal("bloop")}), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a"), ``, }, { // cty map _values_ can have individual marks, so individual // elements in the values list should have their marks preserved. cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.ListVal([]cty.Value{cty.StringVal("bloop").Mark("a")}), cty.MapVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop").Mark("a"), }), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("boop")}), cty.ListValEmpty(cty.String), cty.NilVal, `number of keys (1) does not match number of values (0)`, }, { cty.ListValEmpty(cty.String), cty.ListVal([]cty.Value{cty.StringVal("boop")}), cty.NilVal, `number of keys (0) does not match number of values (1)`, }, // Tuple of values (object result) { cty.ListValEmpty(cty.String), cty.EmptyTupleVal, cty.EmptyObjectVal, ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.TupleVal([]cty.Value{cty.StringVal("bloop")}), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep"), cty.StringVal("beep")}), cty.TupleVal([]cty.Value{cty.StringVal("bloop"), cty.StringVal("boop")}), cty.ObjectVal(map[string]cty.Value{ "beep": cty.StringVal("boop"), "bleep": cty.StringVal("bloop"), }), ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.EmptyTuple), cty.DynamicVal, ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.EmptyTupleVal, cty.DynamicVal, ``, }, { cty.ListValEmpty(cty.String), cty.UnknownVal(cty.EmptyTuple), cty.UnknownVal(cty.EmptyObject), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("bloop")}), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a"), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.TupleVal([]cty.Value{cty.StringVal("bloop")}).Mark("b"), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("b"), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("bleep")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("bloop")}).Mark("b"), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a").Mark("b"), ``, }, { // cty object attributes don't have individual marks, so marks on // elements in the keys list aggregate with the resulting object as // a whole. cty.ListVal([]cty.Value{cty.StringVal("bleep").Mark("a")}), cty.TupleVal([]cty.Value{cty.StringVal("bloop")}), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop"), }).Mark("a"), ``, }, { // cty attribute _values_ can have individual marks, so individual // elements in the values list should have their marks preserved. cty.ListVal([]cty.Value{cty.StringVal("bleep")}), cty.TupleVal([]cty.Value{cty.StringVal("bloop").Mark("a")}), cty.ObjectVal(map[string]cty.Value{ "bleep": cty.StringVal("bloop").Mark("a"), }), ``, }, { cty.ListVal([]cty.Value{cty.StringVal("boop")}), cty.EmptyTupleVal, cty.NilVal, `number of keys (1) does not match number of values (0)`, }, { cty.ListValEmpty(cty.String), cty.TupleVal([]cty.Value{cty.StringVal("boop")}), cty.NilVal, `number of keys (0) does not match number of values (1)`, }, } for _, test := range tests { t.Run(fmt.Sprintf("ZipMap(%#v, %#v)", test.Keys, test.Values), func(t *testing.T) { got, err := Zipmap(test.Keys, test.Values) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestKeys(t *testing.T) { tests := []struct { Collection cty.Value Want cty.Value Err string }{ { cty.MapValEmpty(cty.String), cty.ListValEmpty(cty.String), ``, }, { cty.MapValEmpty(cty.String).Mark("a"), cty.ListValEmpty(cty.String).Mark("a"), ``, }, { cty.NullVal(cty.Map(cty.String)), cty.NilVal, `argument must not be null`, }, { cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world")}), cty.ListVal([]cty.Value{cty.StringVal("hello")}), ``, }, { // The map itself is not marked, just an inner element. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}), cty.ListVal([]cty.Value{cty.StringVal("hello")}), ``, }, { // The entire map is marked, so the resulting list is also marked. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("hello")}).Mark("a"), ``, }, { // Marked both inside and outside. cty.MapVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}).Mark("a"), cty.ListVal([]cty.Value{cty.StringVal("hello")}).Mark("a"), ``, }, { cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world")}), cty.TupleVal([]cty.Value{cty.StringVal("hello")}), ``, }, { cty.EmptyObjectVal, cty.EmptyTupleVal, ``, }, { cty.EmptyObjectVal.Mark("a"), cty.EmptyTupleVal.Mark("a"), ``, }, { cty.NullVal(cty.EmptyObject), cty.NilVal, `argument must not be null`, }, { cty.UnknownVal(cty.EmptyObject), cty.EmptyTupleVal, ``, }, { cty.UnknownVal(cty.Object(map[string]cty.Type{"a": cty.String})), cty.TupleVal([]cty.Value{cty.StringVal("a")}), ``, }, { // The object itself is not marked, just an inner attribute value. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}), cty.TupleVal([]cty.Value{cty.StringVal("hello")}), ``, }, { // The entire object is marked, so the resulting tuple is also marked. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("hello")}).Mark("a"), ``, }, { // Marked both inside and outside. cty.ObjectVal(map[string]cty.Value{"hello": cty.StringVal("world").Mark("a")}).Mark("a"), cty.TupleVal([]cty.Value{cty.StringVal("hello")}).Mark("a"), ``, }, } for _, test := range tests { t.Run(fmt.Sprintf("Keys(%#v)", test.Collection), func(t *testing.T) { got, err := Keys(test.Collection) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestFlatten(t *testing.T) { tests := []struct { List cty.Value Want cty.Value Err string }{ { // Empty case is easy cty.ListValEmpty(cty.String), cty.EmptyTupleVal, "", }, { // Lists can contain unknown values cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("a"), }), cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("b"), cty.UnknownVal(cty.String), }), }), cty.TupleVal([]cty.Value{ cty.UnknownVal(cty.String), cty.StringVal("a"), cty.UnknownVal(cty.String), cty.StringVal("b"), cty.UnknownVal(cty.String), }), "", }, { // If the list itself is unknown this is the best we can do cty.UnknownVal(cty.List(cty.List(cty.String))), cty.UnknownVal(cty.DynamicPseudoType), "", }, { // Type error cty.MapValEmpty(cty.String), cty.DynamicVal, "can only flatten lists, sets and tuples", }, { // Top-level list marks should carry over cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }), cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }), cty.ListValEmpty(cty.String), }).Mark("mark"), cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }).Mark("mark"), "", }, { // Inner list marks should apply to the result collection cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a"), }).Mark("first"), cty.ListVal([]cty.Value{ cty.StringVal("b"), cty.StringVal("c"), }).Mark("second"), cty.ListValEmpty(cty.String).Mark("third"), }), cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }).WithMarks(cty.NewValueMarks("first", "second", "third")), "", }, { // Non-list element marks should be retained on the element only cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("a").Mark("a"), }), cty.ListVal([]cty.Value{ cty.StringVal("b").Mark("b"), cty.StringVal("c").Mark("b"), }), }), cty.TupleVal([]cty.Value{ cty.StringVal("a").Mark("a"), cty.StringVal("b").Mark("b"), cty.StringVal("c").Mark("b"), }), "", }, { // Nested unknown lists/sets/tuples should still propagate marks cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("a")}).Mark("first"), cty.UnknownVal(cty.List(cty.String)).Mark("second"), cty.ListVal([]cty.Value{cty.StringVal("c")}).Mark("third"), }), cty.UnknownVal(cty.DynamicPseudoType).WithMarks(cty.NewValueMarks("first", "second", "third")), "", }, { // Empty marked list retains marks cty.ListValEmpty(cty.String).Mark("a"), cty.EmptyTupleVal.Mark("a"), "", }, { cty.ListValEmpty(cty.Number), cty.EmptyTupleVal, "", }, { cty.ListVal([]cty.Value{ cty.DynamicVal, }), cty.DynamicVal, "", }, { cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.DynamicVal, }), }), cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.DynamicVal, }).Mark("marked"), }), }), cty.DynamicVal.Mark("marked"), "", }, { cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.ListVal([]cty.Value{ cty.DynamicVal, }), }), }), cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), }), }), cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.ListVal([]cty.Value{ cty.DynamicVal, }), }), cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), }), "", }, { cty.ListVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), }), cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), }), }), cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), cty.ObjectVal(map[string]cty.Value{ "bloop": cty.DynamicVal, }), }), "", }, { cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.ListVal([]cty.Value{ cty.StringVal("b"), }), cty.TupleVal([]cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("c"), }), cty.ListVal([]cty.Value{ cty.StringVal("d"), cty.StringVal("e"), }), }), }), cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), cty.StringVal("d"), cty.StringVal("e"), }), "", }, { cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.NullVal(cty.DynamicPseudoType), cty.TupleVal([]cty.Value{ cty.StringVal("c"), }), }), cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.NullVal(cty.DynamicPseudoType), cty.StringVal("c"), }), "", }, { cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), cty.DynamicVal, cty.TupleVal([]cty.Value{ cty.StringVal("c"), }), }), cty.UnknownVal(cty.DynamicPseudoType), "", }, { // null of an unknown type cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.True, }), "", }, { // null of a string type cty.TupleVal([]cty.Value{ cty.NullVal(cty.String), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.String), cty.True, }), "", }, { // null of a list type cty.TupleVal([]cty.Value{ cty.NullVal(cty.List(cty.String)), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.List(cty.String)), cty.True, }), "", }, { // null of a tuple type cty.TupleVal([]cty.Value{ cty.NullVal(cty.EmptyTuple), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.EmptyTuple), cty.True, }), "", }, { // nested null of an unknown type cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), }), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.DynamicPseudoType), cty.True, }), "", }, { // nested null of a string type cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.NullVal(cty.String), }), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.String), cty.True, }), "", }, { // nested null of a list type cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.NullVal(cty.List(cty.String)), }), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.List(cty.String)), cty.True, }), "", }, { // nested null of a tuple type cty.TupleVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.NullVal(cty.EmptyTuple), }), cty.True, }), cty.TupleVal([]cty.Value{ cty.NullVal(cty.EmptyTuple), cty.True, }), "", }, } for _, test := range tests { t.Run(fmt.Sprintf("Flatten(%#v)", test.List), func(t *testing.T) { got, err := Flatten(test.List) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSetproduct(t *testing.T) { tests := []struct { Collections []cty.Value Want cty.Value Err string }{ { []cty.Value{cty.ListValEmpty(cty.String)}, cty.NilVal, `at least two arguments are required`, }, { []cty.Value{ cty.ListValEmpty(cty.EmptyObject), cty.ListVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }), }, cty.ListValEmpty(cty.Tuple([]cty.Type{cty.EmptyObject, cty.String})), ``, }, { []cty.Value{ cty.SetValEmpty(cty.EmptyObject), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }), }, cty.SetValEmpty(cty.Tuple([]cty.Type{cty.EmptyObject, cty.String})), ``, }, { []cty.Value{ cty.ListValEmpty(cty.EmptyObject), cty.ListValEmpty(cty.EmptyObject), }, cty.ListValEmpty(cty.Tuple([]cty.Type{cty.EmptyObject, cty.EmptyObject})), ``, }, { []cty.Value{ cty.SetValEmpty(cty.EmptyObject), cty.SetValEmpty(cty.EmptyObject), }, cty.SetValEmpty(cty.Tuple([]cty.Type{cty.EmptyObject, cty.EmptyObject})), ``, }, { []cty.Value{ cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}), cty.ListVal([]cty.Value{cty.ListValEmpty(cty.String)}), }, cty.ListVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String)})}), ``, }, { []cty.Value{ cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}), cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}), }, cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String)})}), ``, }, { []cty.Value{ cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String).Mark("a")}), cty.SetVal([]cty.Value{cty.ListValEmpty(cty.String)}), }, cty.SetVal([]cty.Value{cty.TupleVal([]cty.Value{cty.ListValEmpty(cty.String).Mark("a"), cty.ListValEmpty(cty.String)})}), ``, }, { []cty.Value{ cty.TupleVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }), cty.TupleVal([]cty.Value{ cty.StringVal("fox"), cty.NumberIntVal(3), }), }, cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("3")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("3")}), }), ``, }, { []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }), }, cty.SetVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }), ``, }, { // The collection itself is not marked, just some elements []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown").Mark("a"), }), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox").Mark("b"), }), }, // Sets don't allow individually-marked elements, so the marks // end up aggregating on the set itself anyway in this case. cty.SetVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).Mark("a").Mark("b"), ``, }, { // The collections are marked []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }).Mark("a"), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }).Mark("b"), }, cty.SetVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).Mark("a").Mark("b"), ``, }, { // One collection is marked []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }).Mark("a"), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }), }, cty.SetVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).Mark("a"), ``, }, { // Inner and outer marks []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown").Mark("a"), }).Mark("b"), cty.SetVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox").Mark("c"), }), }, cty.SetVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).WithMarks(cty.NewValueMarks("b", "c", "a")), ``, }, // SetproductFunc supports lists too, in which case it preserves the // input order and returns a list as the result. In this case we can // preserve the marks more precisely. { // The collection itself is not marked, just some elements []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown").Mark("a"), }), cty.ListVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox").Mark("b"), }), }, cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox").Mark("b")}), cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("fox").Mark("b")}), }), ``, }, { // The collections are marked []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }).Mark("a"), cty.ListVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }).Mark("b"), }, cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).Mark("a").Mark("b"), ``, }, { // One collection is marked []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown"), }).Mark("a"), cty.ListVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox"), }), }, cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown"), cty.StringVal("fox")}), }).Mark("a"), ``, }, { // Inner and outer marks []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("the"), cty.StringVal("brown").Mark("a"), }).Mark("b"), cty.ListVal([]cty.Value{ cty.StringVal("quick"), cty.StringVal("fox").Mark("c"), }), }, cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("the"), cty.StringVal("fox").Mark("c")}), cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("quick")}), cty.TupleVal([]cty.Value{cty.StringVal("brown").Mark("a"), cty.StringVal("fox").Mark("c")}), }).Mark("b"), ``, }, { // Empty lists with marks should propagate the marks []cty.Value{ cty.ListValEmpty(cty.String).Mark("a"), cty.ListValEmpty(cty.Bool).Mark("b"), }, cty.ListValEmpty(cty.Tuple([]cty.Type{cty.String, cty.Bool})).WithMarks(cty.NewValueMarks("a", "b")), ``, }, { // Empty sets with marks should propagate the marks []cty.Value{ cty.SetValEmpty(cty.String).Mark("a"), cty.SetValEmpty(cty.Bool).Mark("b"), }, cty.SetValEmpty(cty.Tuple([]cty.Type{cty.String, cty.Bool})).WithMarks(cty.NewValueMarks("a", "b")), ``, }, { // Arguments which are sets with partially unknown values results // in unknown length (since the unknown values may already be // present in the set). This gives an unknown result preserving all // marks []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("x"), cty.UnknownVal(cty.String)}).Mark("a"), cty.SetVal([]cty.Value{cty.True, cty.False}).Mark("b"), }, cty.UnknownVal(cty.Set(cty.Tuple([]cty.Type{cty.String, cty.Bool}))).WithMarks(cty.NewValueMarks("a", "b")), ``, }, } for _, test := range tests { t.Run(fmt.Sprintf("Setproduct(%#v)", test.Collections), func(t *testing.T) { got, err := SetProduct(test.Collections...) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestReverseList(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value Err string }{ { cty.NilVal, cty.NilVal, `argument must not be null`, }, { cty.ListValEmpty(cty.String), cty.ListValEmpty(cty.String), ``, }, { cty.ListValEmpty(cty.String).Mark("foo"), cty.ListValEmpty(cty.String).Mark("foo"), ``, }, { cty.UnknownVal(cty.List(cty.String)), cty.UnknownVal(cty.List(cty.String)), ``, }, { // marks on list elements cty.ListVal([]cty.Value{ cty.StringVal("beep").Mark("boop"), cty.StringVal("bop"), cty.StringVal("bloop"), }), cty.ListVal([]cty.Value{ cty.StringVal("bloop"), cty.StringVal("bop"), cty.StringVal("beep").Mark("boop"), }), ``, }, { // marks on the entire input are preserved cty.ListVal([]cty.Value{ cty.StringVal("beep").Mark("boop"), cty.StringVal("bop"), cty.StringVal("bloop"), }).Mark("outer"), cty.ListVal([]cty.Value{ cty.StringVal("bloop"), cty.StringVal("bop"), cty.StringVal("beep").Mark("boop"), }).Mark("outer"), ``, }, { // marks on tuple elements cty.TupleVal([]cty.Value{ cty.StringVal("beep").Mark("boop"), cty.StringVal("bop"), cty.StringVal("bloop"), }), cty.TupleVal([]cty.Value{ cty.StringVal("bloop"), cty.StringVal("bop"), cty.StringVal("beep").Mark("boop"), }), ``, }, { // Set elements don't support individual marks; any marks on elements get propegated to the entire set. cty.SetVal([]cty.Value{ cty.StringVal("beep").Mark("boop"), cty.StringVal("bop"), cty.StringVal("bloop"), }), // sets end up sorted alphabetically when converted to lists cty.ListVal([]cty.Value{ cty.StringVal("bop"), cty.StringVal("bloop"), cty.StringVal("beep"), }).Mark("boop"), ``, }, } for _, test := range tests { t.Run(fmt.Sprintf("ReverseList(%#v)", test.Input), func(t *testing.T) { got, err := ReverseList(test.Input) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSlice(t *testing.T) { tests := []struct { Input cty.Value Start cty.Value End cty.Value Want cty.Value Err string }{ { Input: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }), Start: cty.NumberIntVal(0), End: cty.NumberIntVal(2), Want: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }), Err: ``, }, { // The entire input list is marked, so the return should be marked Input: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }).Mark("bloop"), Start: cty.NumberIntVal(0), End: cty.NumberIntVal(2), Want: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), }).Mark("bloop"), Err: ``, }, { // individual element marks should be preserved Input: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b").Mark("bloop"), cty.StringVal("c"), }), Start: cty.NumberIntVal(0), End: cty.NumberIntVal(2), Want: cty.ListVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b").Mark("bloop"), }), Err: ``, }, } for _, test := range tests { t.Run(fmt.Sprintf("Slice(%#v)", test.Input), func(t *testing.T) { got, err := Slice(test.Input, test.Start, test.End) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/conversion.go000066400000000000000000000072441433256746400214440ustar00rootroot00000000000000package stdlib import ( "fmt" "strconv" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/function" ) // MakeToFunc constructs a "to..." function, like "tostring", which converts // its argument to a specific type or type kind. // // The given type wantTy can be any type constraint that cty's "convert" package // would accept. In particular, this means that you can pass // cty.List(cty.DynamicPseudoType) to mean "list of any single type", which // will then cause cty to attempt to unify all of the element types when given // a tuple. func MakeToFunc(wantTy cty.Type) function.Function { return function.New(&function.Spec{ Description: fmt.Sprintf("Converts the given value to %s, or raises an error if that conversion is impossible.", wantTy.FriendlyName()), Params: []function.Parameter{ { Name: "v", // We use DynamicPseudoType rather than wantTy here so that // all values will pass through the function API verbatim and // we can handle the conversion logic within the Type and // Impl functions. This allows us to customize the error // messages to be more appropriate for an explicit type // conversion, whereas the cty function system produces // messages aimed at _implicit_ type conversions. Type: cty.DynamicPseudoType, AllowNull: true, }, }, Type: func(args []cty.Value) (cty.Type, error) { gotTy := args[0].Type() if gotTy.Equals(wantTy) { return wantTy, nil } conv := convert.GetConversionUnsafe(args[0].Type(), wantTy) if conv == nil { // We'll use some specialized errors for some trickier cases, // but most we can handle in a simple way. switch { case gotTy.IsTupleType() && wantTy.IsTupleType(): return cty.NilType, function.NewArgErrorf(0, "incompatible tuple type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) case gotTy.IsObjectType() && wantTy.IsObjectType(): return cty.NilType, function.NewArgErrorf(0, "incompatible object type for conversion: %s", convert.MismatchMessage(gotTy, wantTy)) default: return cty.NilType, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) } } // If a conversion is available then everything is fine. return wantTy, nil }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { // We didn't set "AllowUnknown" on our argument, so it is guaranteed // to be known here but may still be null. ret, err := convert.Convert(args[0], retType) if err != nil { // Because we used GetConversionUnsafe above, conversion can // still potentially fail in here. For example, if the user // asks to convert the string "a" to bool then we'll // optimistically permit it during type checking but fail here // once we note that the value isn't either "true" or "false". gotTy := args[0].Type() switch { case gotTy == cty.String && wantTy == cty.Bool: what := "string" if !args[0].IsNull() { what = strconv.Quote(args[0].AsString()) } return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to bool; only the strings "true" or "false" are allowed`, what) case gotTy == cty.String && wantTy == cty.Number: what := "string" if !args[0].IsNull() { what = strconv.Quote(args[0].AsString()) } return cty.NilVal, function.NewArgErrorf(0, `cannot convert %s to number; given string must be a decimal representation of a number`, what) default: return cty.NilVal, function.NewArgErrorf(0, "cannot convert %s to %s", gotTy.FriendlyName(), wantTy.FriendlyNameForConstraint()) } } return ret, nil }, }) } go-cty-1.12.1/cty/function/stdlib/conversion_test.go000066400000000000000000000053121433256746400224750ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestTo(t *testing.T) { tests := []struct { Value cty.Value TargetTy cty.Type Want cty.Value Err string }{ { cty.StringVal("a"), cty.String, cty.StringVal("a"), ``, }, { cty.UnknownVal(cty.String), cty.String, cty.UnknownVal(cty.String), ``, }, { cty.NullVal(cty.String), cty.String, cty.NullVal(cty.String), ``, }, { cty.True, cty.String, cty.StringVal("true"), ``, }, { cty.StringVal("a"), cty.Bool, cty.DynamicVal, `cannot convert "a" to bool; only the strings "true" or "false" are allowed`, }, { cty.StringVal("a"), cty.Number, cty.DynamicVal, `cannot convert "a" to number; given string must be a decimal representation of a number`, }, { cty.NullVal(cty.String), cty.Number, cty.NullVal(cty.Number), ``, }, { cty.UnknownVal(cty.Bool), cty.String, cty.UnknownVal(cty.String), ``, }, { cty.UnknownVal(cty.String), cty.Bool, cty.UnknownVal(cty.Bool), // conversion is optimistic ``, }, { cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), cty.List(cty.String), cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}), ``, }, { cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), cty.Set(cty.String), cty.SetVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("true")}), ``, }, { cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.True}), cty.Map(cty.String), cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("hello"), "bar": cty.StringVal("true")}), ``, }, { cty.EmptyTupleVal, cty.String, cty.DynamicVal, `cannot convert tuple to string`, }, { cty.UnknownVal(cty.EmptyTuple), cty.String, cty.DynamicVal, `cannot convert tuple to string`, }, { cty.EmptyObjectVal, cty.Object(map[string]cty.Type{"foo": cty.String}), cty.DynamicVal, `incompatible object type for conversion: attribute "foo" is required`, }, } for _, test := range tests { t.Run(fmt.Sprintf("to %s(%#v)", test.TargetTy.FriendlyNameForConstraint(), test.Value), func(t *testing.T) { f := MakeToFunc(test.TargetTy) got, err := f.Call([]cty.Value{test.Value}) if test.Err != "" { if err == nil { t.Fatal("succeeded; want error") } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/csv.go000066400000000000000000000051401433256746400200430ustar00rootroot00000000000000package stdlib import ( "encoding/csv" "fmt" "io" "strings" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) var CSVDecodeFunc = function.New(&function.Spec{ Description: `Parses the given string as Comma Separated Values (as defined by RFC 4180) and returns a map of objects representing the table of data, using the first row as a header row to define the object attributes.`, Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: func(args []cty.Value) (cty.Type, error) { str := args[0] if !str.IsKnown() { return cty.DynamicPseudoType, nil } r := strings.NewReader(str.AsString()) cr := csv.NewReader(r) headers, err := cr.Read() if err == io.EOF { return cty.DynamicPseudoType, fmt.Errorf("missing header line") } if err != nil { return cty.DynamicPseudoType, csvError(err) } atys := make(map[string]cty.Type, len(headers)) for _, name := range headers { if _, exists := atys[name]; exists { return cty.DynamicPseudoType, fmt.Errorf("duplicate column name %q", name) } atys[name] = cty.String } return cty.List(cty.Object(atys)), nil }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { ety := retType.ElementType() atys := ety.AttributeTypes() str := args[0] r := strings.NewReader(str.AsString()) cr := csv.NewReader(r) cr.FieldsPerRecord = len(atys) // Read the header row first, since that'll tell us which indices // map to which attribute names. headers, err := cr.Read() if err != nil { return cty.DynamicVal, err } var rows []cty.Value for { cols, err := cr.Read() if err == io.EOF { break } if err != nil { return cty.DynamicVal, csvError(err) } vals := make(map[string]cty.Value, len(cols)) for i, str := range cols { name := headers[i] vals[name] = cty.StringVal(str) } rows = append(rows, cty.ObjectVal(vals)) } if len(rows) == 0 { return cty.ListValEmpty(ety), nil } return cty.ListVal(rows), nil }, }) // CSVDecode parses the given CSV (RFC 4180) string and, if it is valid, // returns a list of objects representing the rows. // // The result is always a list of some object type. The first row of the // input is used to determine the object attributes, and subsequent rows // determine the values of those attributes. func CSVDecode(str cty.Value) (cty.Value, error) { return CSVDecodeFunc.Call([]cty.Value{str}) } func csvError(err error) error { switch err := err.(type) { case *csv.ParseError: return fmt.Errorf("CSV parse error on line %d: %w", err.Line, err.Err) default: return err } } go-cty-1.12.1/cty/function/stdlib/csv_test.go000066400000000000000000000042061433256746400211040ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestCSVDecode(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value WantErr string }{ { cty.StringVal(csvTest), cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("foo"), "size": cty.StringVal("100"), "type": cty.StringVal("tiny"), }), cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("bar"), "size": cty.StringVal(""), "type": cty.StringVal("huge"), }), cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("baz"), "size": cty.StringVal("50"), "type": cty.StringVal("weedy"), }), }), ``, }, { cty.StringVal(`"just","header","line"`), cty.ListValEmpty(cty.Object(map[string]cty.Type{ "just": cty.String, "header": cty.String, "line": cty.String, })), ``, }, { cty.StringVal(``), cty.DynamicVal, `missing header line`, }, { cty.StringVal(`not csv at all`), cty.ListValEmpty(cty.Object(map[string]cty.Type{ "not csv at all": cty.String, })), ``, }, { cty.StringVal(`invalid"thing"`), cty.DynamicVal, `CSV parse error on line 1: bare " in non-quoted-field`, }, { cty.UnknownVal(cty.String), cty.DynamicVal, // need to know the value to determine the type ``, }, { cty.DynamicVal, cty.DynamicVal, ``, }, { cty.True, cty.DynamicVal, `string required, but received bool`, }, { cty.NullVal(cty.String), cty.DynamicVal, `argument must not be null`, }, } for _, test := range tests { t.Run(fmt.Sprintf("CSVDecode(%#v)", test.Input), func(t *testing.T) { got, err := CSVDecode(test.Input) var errStr string if err != nil { errStr = err.Error() } if errStr != test.WantErr { t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantErr) } if err != nil { return } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } const csvTest = `"name","size","type" "foo","100","tiny" "bar","","huge" "baz","50","weedy" ` go-cty-1.12.1/cty/function/stdlib/datetime.go000066400000000000000000000337051433256746400210540ustar00rootroot00000000000000package stdlib import ( "bufio" "bytes" "fmt" "strings" "time" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) var FormatDateFunc = function.New(&function.Spec{ Description: `Formats a timestamp given in RFC 3339 syntax into another timestamp in some other machine-oriented time syntax, as described in the format string.`, Params: []function.Parameter{ { Name: "format", Type: cty.String, }, { Name: "time", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { formatStr := args[0].AsString() timeStr := args[1].AsString() t, err := parseTimestamp(timeStr) if err != nil { return cty.DynamicVal, function.NewArgError(1, err) } var buf bytes.Buffer sc := bufio.NewScanner(strings.NewReader(formatStr)) sc.Split(splitDateFormat) const esc = '\'' for sc.Scan() { tok := sc.Bytes() // The leading byte signals the token type switch { case tok[0] == esc: if tok[len(tok)-1] != esc || len(tok) == 1 { return cty.DynamicVal, function.NewArgErrorf(0, "unterminated literal '") } if len(tok) == 2 { // Must be a single escaped quote, '' buf.WriteByte(esc) } else { // The content (until a closing esc) is printed out verbatim // except that we must un-double any double-esc escapes in // the middle of the string. raw := tok[1 : len(tok)-1] for i := 0; i < len(raw); i++ { buf.WriteByte(raw[i]) if raw[i] == esc { i++ // skip the escaped quote } } } case startsDateFormatVerb(tok[0]): switch tok[0] { case 'Y': y := t.Year() switch len(tok) { case 2: fmt.Fprintf(&buf, "%02d", y%100) case 4: fmt.Fprintf(&buf, "%04d", y) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: year must either be \"YY\" or \"YYYY\"", tok) } case 'M': m := t.Month() switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", m) case 2: fmt.Fprintf(&buf, "%02d", m) case 3: buf.WriteString(m.String()[:3]) case 4: buf.WriteString(m.String()) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: month must be \"M\", \"MM\", \"MMM\", or \"MMMM\"", tok) } case 'D': d := t.Day() switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", d) case 2: fmt.Fprintf(&buf, "%02d", d) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of month must either be \"D\" or \"DD\"", tok) } case 'E': d := t.Weekday() switch len(tok) { case 3: buf.WriteString(d.String()[:3]) case 4: buf.WriteString(d.String()) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of week must either be \"EEE\" or \"EEEE\"", tok) } case 'h': h := t.Hour() switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", h) case 2: fmt.Fprintf(&buf, "%02d", h) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 24-hour must either be \"h\" or \"hh\"", tok) } case 'H': h := t.Hour() % 12 if h == 0 { h = 12 } switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", h) case 2: fmt.Fprintf(&buf, "%02d", h) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 12-hour must either be \"H\" or \"HH\"", tok) } case 'A', 'a': if len(tok) != 2 { return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: must be \"%s%s\"", tok, tok[0:1], tok[0:1]) } upper := tok[0] == 'A' switch t.Hour() / 12 { case 0: if upper { buf.WriteString("AM") } else { buf.WriteString("am") } case 1: if upper { buf.WriteString("PM") } else { buf.WriteString("pm") } } case 'm': m := t.Minute() switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", m) case 2: fmt.Fprintf(&buf, "%02d", m) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: minute must either be \"m\" or \"mm\"", tok) } case 's': s := t.Second() switch len(tok) { case 1: fmt.Fprintf(&buf, "%d", s) case 2: fmt.Fprintf(&buf, "%02d", s) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: second must either be \"s\" or \"ss\"", tok) } case 'Z': // We'll just lean on Go's own formatter for this one, since // the necessary information is unexported. switch len(tok) { case 1: buf.WriteString(t.Format("Z07:00")) case 3: str := t.Format("-0700") switch str { case "+0000": buf.WriteString("UTC") default: buf.WriteString(str) } case 4: buf.WriteString(t.Format("-0700")) case 5: buf.WriteString(t.Format("-07:00")) default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: timezone must be Z, ZZZZ, or ZZZZZ", tok) } default: return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q", tok) } default: // Any other starting character indicates a literal sequence buf.Write(tok) } } return cty.StringVal(buf.String()), nil }, }) // TimeAddFunc is a function that adds a duration to a timestamp, returning a new timestamp. var TimeAddFunc = function.New(&function.Spec{ Description: `Adds the duration represented by the given duration string to the given RFC 3339 timestamp string, returning another RFC 3339 timestamp.`, Params: []function.Parameter{ { Name: "timestamp", Type: cty.String, }, { Name: "duration", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { ts, err := parseTimestamp(args[0].AsString()) if err != nil { return cty.UnknownVal(cty.String), err } duration, err := time.ParseDuration(args[1].AsString()) if err != nil { return cty.UnknownVal(cty.String), err } return cty.StringVal(ts.Add(duration).Format(time.RFC3339)), nil }, }) // FormatDate reformats a timestamp given in RFC3339 syntax into another time // syntax defined by a given format string. // // The format string uses letter mnemonics to represent portions of the // timestamp, with repetition signifying length variants of each portion. // Single quote characters ' can be used to quote sequences of literal letters // that should not be interpreted as formatting mnemonics. // // The full set of supported mnemonic sequences is listed below: // // YY Year modulo 100 zero-padded to two digits, like "06". // YYYY Four (or more) digit year, like "2006". // M Month number, like "1" for January. // MM Month number zero-padded to two digits, like "01". // MMM English month name abbreviated to three letters, like "Jan". // MMMM English month name unabbreviated, like "January". // D Day of month number, like "2". // DD Day of month number zero-padded to two digits, like "02". // EEE English day of week name abbreviated to three letters, like "Mon". // EEEE English day of week name unabbreviated, like "Monday". // h 24-hour number, like "2". // hh 24-hour number zero-padded to two digits, like "02". // H 12-hour number, like "2". // HH 12-hour number zero-padded to two digits, like "02". // AA Hour AM/PM marker in uppercase, like "AM". // aa Hour AM/PM marker in lowercase, like "am". // m Minute within hour, like "5". // mm Minute within hour zero-padded to two digits, like "05". // s Second within minute, like "9". // ss Second within minute zero-padded to two digits, like "09". // ZZZZ Timezone offset with just sign and digit, like "-0800". // ZZZZZ Timezone offset with colon separating hours and minutes, like "-08:00". // Z Like ZZZZZ but with a special case "Z" for UTC. // ZZZ Like ZZZZ but with a special case "UTC" for UTC. // // The format syntax is optimized mainly for generating machine-oriented // timestamps rather than human-oriented timestamps; the English language // portions of the output reflect the use of English names in a number of // machine-readable date formatting standards. For presentation to humans, // a locale-aware time formatter (not included in this package) is a better // choice. // // The format syntax is not compatible with that of any other language, but // is optimized so that patterns for common standard date formats can be // recognized quickly even by a reader unfamiliar with the format syntax. func FormatDate(format cty.Value, timestamp cty.Value) (cty.Value, error) { return FormatDateFunc.Call([]cty.Value{format, timestamp}) } func parseTimestamp(ts string) (time.Time, error) { t, err := time.Parse(time.RFC3339, ts) if err != nil { switch err := err.(type) { case *time.ParseError: // If err is s time.ParseError then its string representation is not // appropriate since it relies on details of Go's strange date format // representation, which a caller of our functions is not expected // to be familiar with. // // Therefore we do some light transformation to get a more suitable // error that should make more sense to our callers. These are // still not awesome error messages, but at least they refer to // the timestamp portions by name rather than by Go's example // values. if err.LayoutElem == "" && err.ValueElem == "" && err.Message != "" { // For some reason err.Message is populated with a ": " prefix // by the time package. return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp%s", err.Message) } var what string switch err.LayoutElem { case "2006": what = "year" case "01": what = "month" case "02": what = "day of month" case "15": what = "hour" case "04": what = "minute" case "05": what = "second" case "Z07:00": what = "UTC offset" case "T": return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: missing required time introducer 'T'") case ":", "-": if err.ValueElem == "" { return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string where %q is expected", err.LayoutElem) } else { return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: found %q where %q is expected", err.ValueElem, err.LayoutElem) } default: // Should never get here, because time.RFC3339 includes only the // above portions, but since that might change in future we'll // be robust here. what = "timestamp segment" } if err.ValueElem == "" { return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string before %s", what) } else { return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: cannot use %q as %s", err.ValueElem, what) } } return time.Time{}, err } return t, nil } // splitDataFormat is a bufio.SplitFunc used to tokenize a date format. func splitDateFormat(data []byte, atEOF bool) (advance int, token []byte, err error) { if len(data) == 0 { return 0, nil, nil } const esc = '\'' switch { case data[0] == esc: // If we have another quote immediately after then this is a single // escaped escape. if len(data) > 1 && data[1] == esc { return 2, data[:2], nil } // Beginning of quoted sequence, so we will seek forward until we find // the closing quote, ignoring escaped quotes along the way. for i := 1; i < len(data); i++ { if data[i] == esc { if (i + 1) == len(data) { if atEOF { // We have a closing quote and are at the end of our input return len(data), data, nil } else { // We need at least one more byte to decide if this is an // escape or a terminator. return 0, nil, nil } } if data[i+1] == esc { i++ // doubled-up quotes are an escape sequence continue } // We've found the closing quote return i + 1, data[:i+1], nil } } // If we fall out here then we need more bytes to find the end, // unless we're already at the end with an unclosed quote. if atEOF { return len(data), data, nil } return 0, nil, nil case startsDateFormatVerb(data[0]): rep := data[0] for i := 1; i < len(data); i++ { if data[i] != rep { return i, data[:i], nil } } if atEOF { return len(data), data, nil } // We need more data to decide if we've found the end return 0, nil, nil default: for i := 1; i < len(data); i++ { if data[i] == esc || startsDateFormatVerb(data[i]) { return i, data[:i], nil } } // We might not actually be at the end of a literal sequence, // but that doesn't matter since we'll concat them back together // anyway. return len(data), data, nil } } func startsDateFormatVerb(b byte) bool { return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') } // TimeAdd adds a duration to a timestamp, returning a new timestamp. // // In the HCL language, timestamps are conventionally represented as // strings using RFC 3339 "Date and Time format" syntax. Timeadd requires // the timestamp argument to be a string conforming to this syntax. // // `duration` is a string representation of a time difference, consisting of // sequences of number and unit pairs, like `"1.5h"` or `1h30m`. The accepted // units are `ns`, `us` (or `µs`), `"ms"`, `"s"`, `"m"`, and `"h"`. The first // number may be negative to indicate a negative duration, like `"-2h5m"`. // // The result is a string, also in RFC 3339 format, representing the result // of adding the given direction to the given timestamp. func TimeAdd(timestamp cty.Value, duration cty.Value) (cty.Value, error) { return TimeAddFunc.Call([]cty.Value{timestamp, duration}) } go-cty-1.12.1/cty/function/stdlib/datetime_test.go000066400000000000000000000123261433256746400221070ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "time" "github.com/zclconf/go-cty/cty" ) func TestFormatDate(t *testing.T) { tests := []struct { Format cty.Value Want cty.Value Err string }{ { cty.StringVal(""), // pointless, but valid cty.StringVal(""), ``, }, { cty.StringVal("YYYY-MM-DD"), cty.StringVal("2006-01-02"), ``, }, { cty.StringVal("EEE, MMM D ''YY"), cty.StringVal("Mon, Jan 2 '06"), ``, }, { cty.StringVal("hh:mm:ss"), cty.StringVal("15:04:05"), ``, }, { cty.StringVal("H 'o''clock' AA"), cty.StringVal("3 o'clock PM"), ``, }, { cty.StringVal("H 'o''clock'"), cty.StringVal("3 o'clock"), ``, }, { cty.StringVal("hh:mm:ssZZZZ"), cty.StringVal("15:04:05+0000"), ``, }, { cty.StringVal("hh:mm:ssZZZZZ"), cty.StringVal("15:04:05+00:00"), ``, }, { cty.StringVal("MMMM"), cty.StringVal("January"), ``, }, { cty.StringVal("EEEE"), cty.StringVal("Monday"), ``, }, { cty.StringVal("aa"), cty.StringVal("pm"), ``, }, // Some common standard machine-oriented formats { cty.StringVal("YYYY-MM-DD'T'hh:mm:ssZ"), // RFC3339 cty.StringVal("2006-01-02T15:04:05Z"), // (since RFC3339 is the input format too, this is a bit pointless) ``, }, { cty.StringVal("DD MMM YYYY hh:mm ZZZ"), // RFC822 cty.StringVal("02 Jan 2006 15:04 UTC"), ``, }, { cty.StringVal("EEEE, DD-MMM-YY hh:mm:ss ZZZ"), // RFC850 cty.StringVal("Monday, 02-Jan-06 15:04:05 UTC"), ``, }, { cty.StringVal("EEE, DD MMM YYYY hh:mm:ss ZZZ"), // RFC1123 cty.StringVal("Mon, 02 Jan 2006 15:04:05 UTC"), ``, }, // Invalids { cty.StringVal("Y"), cty.NilVal, `invalid date format verb "Y": year must either be "YY" or "YYYY"`, }, { cty.StringVal("YYYYY"), cty.NilVal, `invalid date format verb "YYYYY": year must either be "YY" or "YYYY"`, }, { cty.StringVal("A"), cty.NilVal, `invalid date format verb "A": must be "AA"`, }, { cty.StringVal("a"), cty.NilVal, `invalid date format verb "a": must be "aa"`, }, { cty.StringVal("'blah blah"), cty.NilVal, `unterminated literal '`, }, { cty.StringVal("'"), cty.NilVal, `unterminated literal '`, }, } ts := time.Date(2006, time.January, 2, 15, 04, 05, 0, time.UTC) timeVal := cty.StringVal(ts.Format(time.RFC3339)) for _, test := range tests { t.Run(test.Format.GoString(), func(t *testing.T) { got, err := FormatDate(test.Format, timeVal) if test.Err != "" { if err == nil { t.Fatalf("no error; want error %q", test.Err) } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } } else { if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } } }) } parseErrTests := []struct { Timestamp cty.Value Err string }{ { cty.StringVal(""), `not a valid RFC3339 timestamp: end of string before year`, }, { cty.StringVal("2017-01-02"), `not a valid RFC3339 timestamp: missing required time introducer 'T'`, }, { cty.StringVal(`2017-12-02t00:00:00Z`), `not a valid RFC3339 timestamp: missing required time introducer 'T'`, }, { cty.StringVal("2017:01:02"), `not a valid RFC3339 timestamp: found ":01:02" where "-" is expected`, }, { cty.StringVal("2017"), `not a valid RFC3339 timestamp: end of string where "-" is expected`, }, { cty.StringVal("2017-01-02T"), `not a valid RFC3339 timestamp: end of string before hour`, }, { cty.StringVal("2017-01-02T00"), `not a valid RFC3339 timestamp: end of string where ":" is expected`, }, { cty.StringVal("2017-01-02T00:00:00"), `not a valid RFC3339 timestamp: end of string before UTC offset`, }, { cty.StringVal("2017-01-02T26:00:00Z"), // This one generates an odd message due to an apparent quirk in // the Go time parser. Ideally it would use "26" as the errant string. `not a valid RFC3339 timestamp: cannot use ":00:00Z" as hour`, }, { cty.StringVal("2017-13-02T00:00:00Z"), // This one generates an odd message due to an apparent quirk in // the Go time parser. Ideally it would use "13" as the errant string. `not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as month`, }, { cty.StringVal("2017-02-31T00:00:00Z"), `not a valid RFC3339 timestamp: day out of range`, }, { cty.StringVal(`"2017-12-02T00:00:00Z"`), `not a valid RFC3339 timestamp: cannot use "\"2017-12-02T00:00:00Z\"" as year`, }, { cty.StringVal(`2-12-02T00:00:00Z`), // Go parser seems to be trying to parse "2-12" as a year here, // producing a confusing error message. `not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as year`, }, } for _, test := range parseErrTests { t.Run(fmt.Sprintf("%s parse error", test.Timestamp.AsString()), func(t *testing.T) { _, err := FormatDate(cty.StringVal(""), test.Timestamp) if err == nil { t.Fatalf("no error; want error %q", test.Err) } if got, want := err.Error(), test.Err; got != want { t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) } }) } } go-cty-1.12.1/cty/function/stdlib/doc.go000066400000000000000000000011571433256746400200210ustar00rootroot00000000000000// Package stdlib is a collection of cty functions that are expected to be // generally useful, and are thus factored out into this shared library in // the hope that cty-using applications will have consistent behavior when // using these functions. // // See the parent package "function" for more information on the purpose // and usage of cty functions. // // This package contains both Go functions, which provide convenient access // to call the functions from Go code, and the Function objects themselves. // The latter follow the naming scheme of appending "Func" to the end of // the function name. package stdlib go-cty-1.12.1/cty/function/stdlib/format.go000066400000000000000000000375561433256746400205600ustar00rootroot00000000000000package stdlib import ( "bytes" "fmt" "math/big" "strings" "github.com/apparentlymart/go-textseg/v13/textseg" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/json" ) //go:generate ragel -Z format_fsm.rl //go:generate gofmt -w format_fsm.go var FormatFunc = function.New(&function.Spec{ Description: `Constructs a string by applying formatting verbs to a series of arguments, using a similar syntax to the C function \"printf\".`, Params: []function.Parameter{ { Name: "format", Type: cty.String, }, }, VarParam: &function.Parameter{ Name: "args", Type: cty.DynamicPseudoType, AllowNull: true, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { for _, arg := range args[1:] { if !arg.IsWhollyKnown() { // We require all nested values to be known because the only // thing we can do for a collection/structural type is print // it as JSON and that requires it to be wholly known. return cty.UnknownVal(cty.String), nil } } str, err := formatFSM(args[0].AsString(), args[1:]) return cty.StringVal(str), err }, }) var FormatListFunc = function.New(&function.Spec{ Description: `Constructs a list of strings by applying formatting verbs to a series of arguments, using a similar syntax to the C function \"printf\".`, Params: []function.Parameter{ { Name: "format", Type: cty.String, }, }, VarParam: &function.Parameter{ Name: "args", Type: cty.DynamicPseudoType, AllowNull: true, AllowUnknown: true, }, Type: function.StaticReturnType(cty.List(cty.String)), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { fmtVal := args[0] args = args[1:] if len(args) == 0 { // With no arguments, this function is equivalent to Format, but // returning a single-element list result. result, err := Format(fmtVal, args...) return cty.ListVal([]cty.Value{result}), err } fmtStr := fmtVal.AsString() // Each of our arguments will be dealt with either as an iterator // or as a single value. Iterators are used for sequence-type values // (lists, sets, tuples) while everything else is treated as a // single value. The sequences we iterate over are required to be // all the same length. iterLen := -1 lenChooser := -1 iterators := make([]cty.ElementIterator, len(args)) singleVals := make([]cty.Value, len(args)) unknowns := make([]bool, len(args)) for i, arg := range args { argTy := arg.Type() switch { case (argTy.IsListType() || argTy.IsSetType() || argTy.IsTupleType()) && !arg.IsNull(): if !argTy.IsTupleType() && !(arg.IsKnown() && arg.Length().IsKnown()) { // We can't iterate this one at all yet then, so we can't // yet produce a result. unknowns[i] = true continue } thisLen := arg.LengthInt() if iterLen == -1 { iterLen = thisLen lenChooser = i } else { if thisLen != iterLen { return cty.NullVal(cty.List(cty.String)), function.NewArgErrorf( i+1, "argument %d has length %d, which is inconsistent with argument %d of length %d", i+1, thisLen, lenChooser+1, iterLen, ) } } if !arg.IsKnown() { // We allowed an unknown tuple value to fall through in // our initial check above so that we'd be able to run // the above error checks against it, but we still can't // iterate it if the checks pass. unknowns[i] = true continue } iterators[i] = arg.ElementIterator() case arg == cty.DynamicVal: unknowns[i] = true default: singleVals[i] = arg } } for _, isUnk := range unknowns { if isUnk { return cty.UnknownVal(retType), nil } } if iterLen == 0 { // If our sequences are all empty then our result must be empty. return cty.ListValEmpty(cty.String), nil } if iterLen == -1 { // If we didn't encounter any iterables at all then we're going // to just do one iteration with items from singleVals. iterLen = 1 } ret := make([]cty.Value, 0, iterLen) fmtArgs := make([]cty.Value, len(iterators)) Results: for iterIdx := 0; iterIdx < iterLen; iterIdx++ { // Construct our arguments for a single format call for i := range fmtArgs { switch { case iterators[i] != nil: iterator := iterators[i] iterator.Next() _, val := iterator.Element() fmtArgs[i] = val default: fmtArgs[i] = singleVals[i] } // If any of the arguments to this call would be unknown then // this particular result is unknown, but we'll keep going // to see if any other iterations can produce known values. if !fmtArgs[i].IsWhollyKnown() { // We require all nested values to be known because the only // thing we can do for a collection/structural type is print // it as JSON and that requires it to be wholly known. ret = append(ret, cty.UnknownVal(cty.String)) continue Results } } str, err := formatFSM(fmtStr, fmtArgs) if err != nil { return cty.NullVal(cty.List(cty.String)), fmt.Errorf( "error on format iteration %d: %s", iterIdx, err, ) } ret = append(ret, cty.StringVal(str)) } return cty.ListVal(ret), nil }, }) // Format produces a string representation of zero or more values using a // format string similar to the "printf" function in C. // // It supports the following "verbs": // // %% Literal percent sign, consuming no value // %v A default formatting of the value based on type, as described below. // %#v JSON serialization of the value // %t Converts to boolean and then produces "true" or "false" // %b Converts to number, requires integer, produces binary representation // %d Converts to number, requires integer, produces decimal representation // %o Converts to number, requires integer, produces octal representation // %x Converts to number, requires integer, produces hexadecimal representation // with lowercase letters // %X Like %x but with uppercase letters // %e Converts to number, produces scientific notation like -1.234456e+78 // %E Like %e but with an uppercase "E" representing the exponent // %f Converts to number, produces decimal representation with fractional // part but no exponent, like 123.456 // %g %e for large exponents or %f otherwise // %G %E for large exponents or %f otherwise // %s Converts to string and produces the string's characters // %q Converts to string and produces JSON-quoted string representation, // like %v. // // The default format selections made by %v are: // // string %s // number %g // bool %t // other %#v // // Null values produce the literal keyword "null" for %v and %#v, and produce // an error otherwise. // // Width is specified by an optional decimal number immediately preceding the // verb letter. If absent, the width is whatever is necessary to represent the // value. Precision is specified after the (optional) width by a period // followed by a decimal number. If no period is present, a default precision // is used. A period with no following number is invalid. // For examples: // // %f default width, default precision // %9f width 9, default precision // %.2f default width, precision 2 // %9.2f width 9, precision 2 // // Width and precision are measured in unicode characters (grapheme clusters). // // For most values, width is the minimum number of characters to output, // padding the formatted form with spaces if necessary. // // For strings, precision limits the length of the input to be formatted (not // the size of the output), truncating if necessary. // // For numbers, width sets the minimum width of the field and precision sets // the number of places after the decimal, if appropriate, except that for // %g/%G precision sets the total number of significant digits. // // The following additional symbols can be used immediately after the percent // introducer as flags: // // (a space) leave a space where the sign would be if number is positive // + Include a sign for a number even if it is positive (numeric only) // - Pad with spaces on the left rather than the right // 0 Pad with zeros rather than spaces. // // Flag characters are ignored for verbs that do not support them. // // By default, % sequences consume successive arguments starting with the first. // Introducing a [n] sequence immediately before the verb letter, where n is a // decimal integer, explicitly chooses a particular value argument by its // one-based index. Subsequent calls without an explicit index will then // proceed with n+1, n+2, etc. // // An error is produced if the format string calls for an impossible conversion // or accesses more values than are given. An error is produced also for // an unsupported format verb. func Format(format cty.Value, vals ...cty.Value) (cty.Value, error) { args := make([]cty.Value, 0, len(vals)+1) args = append(args, format) args = append(args, vals...) return FormatFunc.Call(args) } // FormatList applies the same formatting behavior as Format, but accepts // a mixture of list and non-list values as arguments. Any list arguments // passed must have the same length, which dictates the length of the // resulting list. // // Any non-list arguments are used repeatedly for each iteration over the // list arguments. The list arguments are iterated in order by key, so // corresponding items are formatted together. func FormatList(format cty.Value, vals ...cty.Value) (cty.Value, error) { args := make([]cty.Value, 0, len(vals)+1) args = append(args, format) args = append(args, vals...) return FormatListFunc.Call(args) } type formatVerb struct { Raw string Offset int ArgNum int Mode rune Zero bool Sharp bool Plus bool Minus bool Space bool HasPrec bool Prec int HasWidth bool Width int } // formatAppend is called by formatFSM (generated by format_fsm.rl) for each // formatting sequence that is encountered. func formatAppend(verb *formatVerb, buf *bytes.Buffer, args []cty.Value) error { argIdx := verb.ArgNum - 1 if argIdx >= len(args) { return fmt.Errorf( "not enough arguments for %q at %d: need index %d but have %d total", verb.Raw, verb.Offset, verb.ArgNum, len(args), ) } arg := args[argIdx] if verb.Mode != 'v' && arg.IsNull() { return fmt.Errorf("unsupported value for %q at %d: null value cannot be formatted", verb.Raw, verb.Offset) } // Normalize to make some things easier for downstream formatters if !verb.HasWidth { verb.Width = -1 } if !verb.HasPrec { verb.Prec = -1 } // For our first pass we'll ensure the verb is supported and then fan // out to other functions based on what conversion is needed. switch verb.Mode { case 'v': return formatAppendAsIs(verb, buf, arg) case 't': return formatAppendBool(verb, buf, arg) case 'b', 'd', 'o', 'x', 'X', 'e', 'E', 'f', 'g', 'G': return formatAppendNumber(verb, buf, arg) case 's', 'q': return formatAppendString(verb, buf, arg) default: return fmt.Errorf("unsupported format verb %q in %q at offset %d", verb.Mode, verb.Raw, verb.Offset) } } func formatAppendAsIs(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { if !verb.Sharp && !arg.IsNull() { // Unless the caller overrode it with the sharp flag, we'll try some // specialized formats before we fall back on JSON. switch arg.Type() { case cty.String: fmted := arg.AsString() fmted = formatPadWidth(verb, fmted) buf.WriteString(fmted) return nil case cty.Number: bf := arg.AsBigFloat() fmted := bf.Text('g', -1) fmted = formatPadWidth(verb, fmted) buf.WriteString(fmted) return nil } } jb, err := json.Marshal(arg, arg.Type()) if err != nil { return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) } fmted := formatPadWidth(verb, string(jb)) buf.WriteString(fmted) return nil } func formatAppendBool(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { var err error arg, err = convert.Convert(arg, cty.Bool) if err != nil { return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) } if arg.True() { buf.WriteString("true") } else { buf.WriteString("false") } return nil } func formatAppendNumber(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { var err error arg, err = convert.Convert(arg, cty.Number) if err != nil { return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) } switch verb.Mode { case 'b', 'd', 'o', 'x', 'X': return formatAppendInteger(verb, buf, arg) default: bf := arg.AsBigFloat() // For floats our format syntax is a subset of Go's, so it's // safe for us to just lean on the existing Go implementation. fmtstr := formatStripIndexSegment(verb.Raw) fmted := fmt.Sprintf(fmtstr, bf) buf.WriteString(fmted) return nil } } func formatAppendInteger(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { bf := arg.AsBigFloat() bi, acc := bf.Int(nil) if acc != big.Exact { return fmt.Errorf("unsupported value for %q at %d: an integer is required", verb.Raw, verb.Offset) } // For integers our format syntax is a subset of Go's, so it's // safe for us to just lean on the existing Go implementation. fmtstr := formatStripIndexSegment(verb.Raw) fmted := fmt.Sprintf(fmtstr, bi) buf.WriteString(fmted) return nil } func formatAppendString(verb *formatVerb, buf *bytes.Buffer, arg cty.Value) error { var err error arg, err = convert.Convert(arg, cty.String) if err != nil { return fmt.Errorf("unsupported value for %q at %d: %s", verb.Raw, verb.Offset, err) } // We _cannot_ directly use the Go fmt.Sprintf implementation for strings // because it measures widths and precisions in runes rather than grapheme // clusters. str := arg.AsString() if verb.Prec > 0 { strB := []byte(str) pos := 0 wanted := verb.Prec for i := 0; i < wanted; i++ { next := strB[pos:] if len(next) == 0 { // ran out of characters before we hit our max width break } d, _, _ := textseg.ScanGraphemeClusters(strB[pos:], true) pos += d } str = str[:pos] } switch verb.Mode { case 's': fmted := formatPadWidth(verb, str) buf.WriteString(fmted) case 'q': jb, err := json.Marshal(cty.StringVal(str), cty.String) if err != nil { // Should never happen, since we know this is a known, non-null string panic(fmt.Errorf("failed to marshal %#v as JSON: %s", arg, err)) } fmted := formatPadWidth(verb, string(jb)) buf.WriteString(fmted) default: // Should never happen because formatAppend should've already validated panic(fmt.Errorf("invalid string formatting mode %q", verb.Mode)) } return nil } func formatPadWidth(verb *formatVerb, fmted string) string { if verb.Width < 0 { return fmted } // Safe to ignore errors because ScanGraphemeClusters cannot produce errors givenLen, _ := textseg.TokenCount([]byte(fmted), textseg.ScanGraphemeClusters) wantLen := verb.Width if givenLen >= wantLen { return fmted } padLen := wantLen - givenLen padChar := " " if verb.Zero { padChar = "0" } pads := strings.Repeat(padChar, padLen) if verb.Minus { return fmted + pads } return pads + fmted } // formatStripIndexSegment strips out any [nnn] segment present in a verb // string so that we can pass it through to Go's fmt.Sprintf with a single // argument. This is used in cases where we're just leaning on Go's formatter // because it's a superset of ours. func formatStripIndexSegment(rawVerb string) string { // We assume the string has already been validated here, since we should // only be using this function with strings that were accepted by our // scanner in formatFSM. start := strings.Index(rawVerb, "[") end := strings.Index(rawVerb, "]") if start == -1 || end == -1 { return rawVerb } return rawVerb[:start] + rawVerb[end+1:] } go-cty-1.12.1/cty/function/stdlib/format_fsm.go000066400000000000000000000166661433256746400214240ustar00rootroot00000000000000// line 1 "format_fsm.rl" // This file is generated from format_fsm.rl. DO NOT EDIT. // line 5 "format_fsm.rl" package stdlib import ( "bytes" "fmt" "unicode/utf8" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) // line 21 "format_fsm.go" var _formatfsm_actions []byte = []byte{ 0, 1, 0, 1, 1, 1, 2, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 1, 10, 1, 11, 1, 14, 1, 16, 1, 17, 1, 18, 2, 3, 4, 2, 12, 10, 2, 12, 16, 2, 12, 18, 2, 13, 14, 2, 15, 10, 2, 15, 18, } var _formatfsm_key_offsets []byte = []byte{ 0, 0, 14, 27, 34, 36, 39, 43, 51, } var _formatfsm_trans_keys []byte = []byte{ 32, 35, 37, 43, 45, 46, 48, 91, 49, 57, 65, 90, 97, 122, 32, 35, 43, 45, 46, 48, 91, 49, 57, 65, 90, 97, 122, 91, 48, 57, 65, 90, 97, 122, 49, 57, 93, 48, 57, 65, 90, 97, 122, 46, 91, 48, 57, 65, 90, 97, 122, 37, } var _formatfsm_single_lengths []byte = []byte{ 0, 8, 7, 1, 0, 1, 0, 2, 1, } var _formatfsm_range_lengths []byte = []byte{ 0, 3, 3, 3, 1, 1, 2, 3, 0, } var _formatfsm_index_offsets []byte = []byte{ 0, 0, 12, 23, 28, 30, 33, 36, 42, } var _formatfsm_indicies []byte = []byte{ 1, 2, 3, 4, 5, 6, 7, 10, 8, 9, 9, 0, 1, 2, 4, 5, 6, 7, 10, 8, 9, 9, 0, 13, 11, 12, 12, 0, 14, 0, 15, 14, 0, 9, 9, 0, 16, 19, 17, 18, 18, 0, 20, 3, } var _formatfsm_trans_targs []byte = []byte{ 0, 2, 2, 8, 2, 2, 3, 2, 7, 8, 4, 3, 8, 4, 5, 6, 3, 7, 8, 4, 1, } var _formatfsm_trans_actions []byte = []byte{ 7, 17, 9, 3, 15, 13, 25, 11, 43, 29, 19, 27, 49, 46, 21, 0, 37, 23, 40, 34, 1, } var _formatfsm_eof_actions []byte = []byte{ 0, 31, 31, 31, 31, 31, 31, 31, 5, } const formatfsm_start int = 8 const formatfsm_first_final int = 8 const formatfsm_error int = 0 const formatfsm_en_main int = 8 // line 20 "format_fsm.rl" func formatFSM(format string, a []cty.Value) (string, error) { var buf bytes.Buffer data := format nextArg := 1 // arg numbers are 1-based var verb formatVerb highestArgIdx := 0 // zero means "none", since arg numbers are 1-based // line 159 "format_fsm.rl" // Ragel state p := 0 // "Pointer" into data pe := len(data) // End-of-data "pointer" cs := 0 // current state (will be initialized by ragel-generated code) ts := 0 te := 0 eof := pe // Keep Go compiler happy even if generated code doesn't use these _ = ts _ = te _ = eof // line 123 "format_fsm.go" { cs = formatfsm_start } // line 128 "format_fsm.go" { var _klen int var _trans int var _acts int var _nacts uint var _keys int if p == pe { goto _test_eof } if cs == 0 { goto _out } _resume: _keys = int(_formatfsm_key_offsets[cs]) _trans = int(_formatfsm_index_offsets[cs]) _klen = int(_formatfsm_single_lengths[cs]) if _klen > 0 { _lower := int(_keys) var _mid int _upper := int(_keys + _klen - 1) for { if _upper < _lower { break } _mid = _lower + ((_upper - _lower) >> 1) switch { case data[p] < _formatfsm_trans_keys[_mid]: _upper = _mid - 1 case data[p] > _formatfsm_trans_keys[_mid]: _lower = _mid + 1 default: _trans += int(_mid - int(_keys)) goto _match } } _keys += _klen _trans += _klen } _klen = int(_formatfsm_range_lengths[cs]) if _klen > 0 { _lower := int(_keys) var _mid int _upper := int(_keys + (_klen << 1) - 2) for { if _upper < _lower { break } _mid = _lower + (((_upper - _lower) >> 1) & ^1) switch { case data[p] < _formatfsm_trans_keys[_mid]: _upper = _mid - 2 case data[p] > _formatfsm_trans_keys[_mid+1]: _lower = _mid + 2 default: _trans += int((_mid - int(_keys)) >> 1) goto _match } } _trans += _klen } _match: _trans = int(_formatfsm_indicies[_trans]) cs = int(_formatfsm_trans_targs[_trans]) if _formatfsm_trans_actions[_trans] == 0 { goto _again } _acts = int(_formatfsm_trans_actions[_trans]) _nacts = uint(_formatfsm_actions[_acts]) _acts++ for ; _nacts > 0; _nacts-- { _acts++ switch _formatfsm_actions[_acts-1] { case 0: // line 31 "format_fsm.rl" verb = formatVerb{ ArgNum: nextArg, Prec: -1, Width: -1, } ts = p case 1: // line 40 "format_fsm.rl" buf.WriteByte(data[p]) case 4: // line 51 "format_fsm.rl" // We'll try to slurp a whole UTF-8 sequence here, to give the user // better feedback. r, _ := utf8.DecodeRuneInString(data[p:]) return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p) case 5: // line 58 "format_fsm.rl" verb.Sharp = true case 6: // line 61 "format_fsm.rl" verb.Zero = true case 7: // line 64 "format_fsm.rl" verb.Minus = true case 8: // line 67 "format_fsm.rl" verb.Plus = true case 9: // line 70 "format_fsm.rl" verb.Space = true case 10: // line 74 "format_fsm.rl" verb.ArgNum = 0 case 11: // line 77 "format_fsm.rl" verb.ArgNum = (10 * verb.ArgNum) + (int(data[p]) - '0') case 12: // line 81 "format_fsm.rl" verb.HasWidth = true case 13: // line 84 "format_fsm.rl" verb.Width = 0 case 14: // line 87 "format_fsm.rl" verb.Width = (10 * verb.Width) + (int(data[p]) - '0') case 15: // line 91 "format_fsm.rl" verb.HasPrec = true case 16: // line 94 "format_fsm.rl" verb.Prec = 0 case 17: // line 97 "format_fsm.rl" verb.Prec = (10 * verb.Prec) + (int(data[p]) - '0') case 18: // line 101 "format_fsm.rl" verb.Mode = rune(data[p]) te = p + 1 verb.Raw = data[ts:te] verb.Offset = ts if verb.ArgNum > highestArgIdx { highestArgIdx = verb.ArgNum } err := formatAppend(&verb, &buf, a) if err != nil { return buf.String(), err } nextArg = verb.ArgNum + 1 // line 330 "format_fsm.go" } } _again: if cs == 0 { goto _out } p++ if p != pe { goto _resume } _test_eof: { } if p == eof { __acts := _formatfsm_eof_actions[cs] __nacts := uint(_formatfsm_actions[__acts]) __acts++ for ; __nacts > 0; __nacts-- { __acts++ switch _formatfsm_actions[__acts-1] { case 2: // line 44 "format_fsm.rl" case 3: // line 47 "format_fsm.rl" return buf.String(), fmt.Errorf("invalid format string starting at offset %d", p) case 4: // line 51 "format_fsm.rl" // We'll try to slurp a whole UTF-8 sequence here, to give the user // better feedback. r, _ := utf8.DecodeRuneInString(data[p:]) return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p) // line 369 "format_fsm.go" } } } _out: { } } // line 177 "format_fsm.rl" // If we fall out here without being in a final state then we've // encountered something that the scanner can't match, which should // be impossible (the scanner matches all bytes _somehow_) but we'll // flag it anyway rather than just losing data from the end. if cs < formatfsm_first_final { return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p) } if highestArgIdx < len(a) { // Extraneous args are an error, to more easily detect mistakes firstBad := highestArgIdx + 1 if highestArgIdx == 0 { // Custom error message for this case return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string") } return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx) } return buf.String(), nil } go-cty-1.12.1/cty/function/stdlib/format_fsm.rl000066400000000000000000000074331433256746400214240ustar00rootroot00000000000000// This file is generated from format_fsm.rl. DO NOT EDIT. %%{ # (except you are actually in scan_tokens.rl here, so edit away!) machine formatfsm; }%% package stdlib import ( "bytes" "fmt" "unicode/utf8" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) %%{ write data; }%% func formatFSM(format string, a []cty.Value) (string, error) { var buf bytes.Buffer data := format nextArg := 1 // arg numbers are 1-based var verb formatVerb highestArgIdx := 0 // zero means "none", since arg numbers are 1-based %%{ action begin { verb = formatVerb{ ArgNum: nextArg, Prec: -1, Width: -1, } ts = p } action emit { buf.WriteByte(fc); } action finish_ok { } action finish_err { return buf.String(), fmt.Errorf("invalid format string starting at offset %d", p) } action err_char { // We'll try to slurp a whole UTF-8 sequence here, to give the user // better feedback. r, _ := utf8.DecodeRuneInString(data[p:]) return buf.String(), fmt.Errorf("unrecognized format character %q at offset %d", r, p) } action flag_sharp { verb.Sharp = true } action flag_zero { verb.Zero = true } action flag_minus { verb.Minus = true } action flag_plus { verb.Plus = true } action flag_space { verb.Space = true } action argidx_reset { verb.ArgNum = 0 } action argidx_num { verb.ArgNum = (10 * verb.ArgNum) + (int(fc) - '0') } action has_width { verb.HasWidth = true } action width_reset { verb.Width = 0 } action width_num { verb.Width = (10 * verb.Width) + (int(fc) - '0') } action has_prec { verb.HasPrec = true } action prec_reset { verb.Prec = 0 } action prec_num { verb.Prec = (10 * verb.Prec) + (int(fc) - '0') } action mode { verb.Mode = rune(fc) te = p+1 verb.Raw = data[ts:te] verb.Offset = ts if verb.ArgNum > highestArgIdx { highestArgIdx = verb.ArgNum } err := formatAppend(&verb, &buf, a) if err != nil { return buf.String(), err } nextArg = verb.ArgNum + 1 } # a number that isn't zero and doesn't have a leading zero num = [1-9] [0-9]*; flags = ( '0' @flag_zero | '#' @flag_sharp | '-' @flag_minus | '+' @flag_plus | ' ' @flag_space )*; argidx = (( '[' (num $argidx_num) ']' ) >argidx_reset)?; width = ( ( num $width_num ) >width_reset %has_width )?; precision = ( ('.' ( digit* $prec_num )) >prec_reset %has_prec )?; # We accept any letter here, but will be more picky in formatAppend mode = ('a'..'z' | 'A'..'Z') @mode; fmt_verb = ( '%' @begin flags width precision argidx mode ); main := ( [^%] @emit | '%%' @emit | fmt_verb )* @/finish_err %/finish_ok $!err_char; }%% // Ragel state p := 0 // "Pointer" into data pe := len(data) // End-of-data "pointer" cs := 0 // current state (will be initialized by ragel-generated code) ts := 0 te := 0 eof := pe // Keep Go compiler happy even if generated code doesn't use these _ = ts _ = te _ = eof %%{ write init; write exec; }%% // If we fall out here without being in a final state then we've // encountered something that the scanner can't match, which should // be impossible (the scanner matches all bytes _somehow_) but we'll // flag it anyway rather than just losing data from the end. if cs < formatfsm_first_final { return buf.String(), fmt.Errorf("extraneous characters beginning at offset %d", p) } if highestArgIdx < len(a) { // Extraneous args are an error, to more easily detect mistakes firstBad := highestArgIdx+1 if highestArgIdx == 0 { // Custom error message for this case return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; no verbs in format string") } return buf.String(), function.NewArgErrorf(firstBad, "too many arguments; only %d used by format string", highestArgIdx) } return buf.String(), nil } go-cty-1.12.1/cty/function/stdlib/format_test.go000066400000000000000000000453701433256746400216100ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestFormat(t *testing.T) { tests := []struct { Format cty.Value Args []cty.Value Want cty.Value WantErr string }{ { cty.StringVal(""), nil, cty.StringVal(""), ``, }, { cty.StringVal("hello"), nil, cty.StringVal("hello"), ``, }, { cty.StringVal("100%% successful"), nil, cty.StringVal("100% successful"), ``, }, { cty.StringVal("100%%"), nil, cty.StringVal("100%"), ``, }, // Default formats { cty.StringVal("string %v"), []cty.Value{cty.StringVal("hello")}, cty.StringVal("string hello"), ``, }, { cty.StringVal("string %[2]v"), []cty.Value{cty.True, cty.StringVal("hello")}, cty.StringVal("string hello"), ``, }, { cty.StringVal("string %#v"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`string "hello"`), ``, }, { cty.StringVal("number %v"), []cty.Value{cty.NumberIntVal(2)}, cty.StringVal("number 2"), ``, }, { cty.StringVal("number %#v"), []cty.Value{cty.NumberIntVal(2)}, cty.StringVal("number 2"), ``, }, { cty.StringVal("bool %v"), []cty.Value{cty.True}, cty.StringVal("bool true"), ``, }, { cty.StringVal("bool %#v"), []cty.Value{cty.True}, cty.StringVal("bool true"), ``, }, { cty.StringVal("object %v"), []cty.Value{cty.EmptyObjectVal}, cty.StringVal("object {}"), ``, }, { cty.StringVal("tuple %v"), []cty.Value{cty.EmptyTupleVal}, cty.StringVal("tuple []"), ``, }, { cty.StringVal("tuple with unknown %v"), []cty.Value{cty.TupleVal([]cty.Value{ cty.UnknownVal(cty.String), })}, cty.UnknownVal(cty.String), ``, }, { cty.StringVal("%%%v"), []cty.Value{cty.False}, cty.StringVal("%false"), ``, }, { cty.StringVal("%v"), []cty.Value{cty.NullVal(cty.Bool)}, cty.StringVal("null"), ``, }, // Strings { cty.StringVal("Hello, %s!"), []cty.Value{cty.StringVal("Ermintrude")}, cty.StringVal("Hello, Ermintrude!"), ``, }, { cty.StringVal("Hello, %[2]s!"), []cty.Value{cty.StringVal("Stephen"), cty.StringVal("Ermintrude")}, cty.StringVal("Hello, Ermintrude!"), ``, }, { cty.StringVal("Hello, %q... if that _is_ your real name!"), []cty.Value{cty.StringVal("Ermintrude")}, cty.StringVal(`Hello, "Ermintrude"... if that _is_ your real name!`), ``, }, { cty.StringVal("This statement is %s"), []cty.Value{cty.False}, cty.StringVal("This statement is false"), ``, }, { cty.StringVal("This statement is %q"), []cty.Value{cty.False}, cty.StringVal(`This statement is "false"`), ``, }, { cty.StringVal("%s"), []cty.Value{cty.NullVal(cty.String)}, cty.NilVal, `unsupported value for "%s" at 0: null value cannot be formatted`, }, { cty.StringVal("%10s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(` hello`), ``, }, { cty.StringVal("%-10s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`hello `), ``, }, { cty.StringVal("%4s"), []cty.Value{cty.StringVal("💃🏿")}, cty.StringVal(` 💃🏿`), // three spaces because this emoji sequence is a single grapheme cluster ``, }, { cty.StringVal("%-4s"), []cty.Value{cty.StringVal("💃🏿")}, cty.StringVal(`💃🏿 `), // three spaces because this emoji sequence is a single grapheme cluster ``, }, { cty.StringVal("%q"), []cty.Value{cty.StringVal("💃🏿")}, cty.StringVal(`"💃🏿"`), ``, }, { cty.StringVal("%6q"), []cty.Value{cty.StringVal("💃🏿")}, cty.StringVal(` "💃🏿"`), // three spaces because this emoji sequence is a single grapheme cluster ``, }, { cty.StringVal("%-6q"), []cty.Value{cty.StringVal("💃🏿")}, cty.StringVal(`"💃🏿" `), // three spaces because this emoji sequence is a single grapheme cluster ``, }, { cty.StringVal("%.2s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`he`), ``, }, { cty.StringVal("%.2q"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`"he"`), ``, }, { cty.StringVal("%.5s"), []cty.Value{cty.StringVal("日本語日本語")}, cty.StringVal(`日本語日本`), ``, }, { cty.StringVal("%.1q"), []cty.Value{cty.StringVal("日本語日本語")}, cty.StringVal(`"日"`), ``, }, { cty.StringVal("%.10s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`hello`), ``, }, { cty.StringVal("%4.2s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(` he`), ``, }, { cty.StringVal("%6.2q"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(` "he"`), ``, }, { cty.StringVal("%-4.2s"), []cty.Value{cty.StringVal("hello")}, cty.StringVal(`he `), ``, }, { cty.StringVal("%q"), []cty.Value{cty.StringVal("Hello\nWorld")}, cty.StringVal(`"Hello\nWorld"`), ``, }, // Booleans { cty.StringVal("This statement is %t"), []cty.Value{cty.False}, cty.StringVal("This statement is false"), ``, }, { cty.StringVal("This statement is %[2]t"), []cty.Value{cty.True, cty.False}, cty.StringVal("This statement is false"), ``, }, { cty.StringVal("This statement is %t"), []cty.Value{cty.True}, cty.StringVal("This statement is true"), ``, }, { cty.StringVal("This statement is %t"), []cty.Value{cty.StringVal("false")}, cty.StringVal("This statement is false"), ``, }, // Integer Numbers { cty.StringVal("%d green bottles standing on the wall"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal("10 green bottles standing on the wall"), ``, }, { cty.StringVal("%[2]d things"), []cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)}, cty.StringVal("10 things"), ``, }, { cty.StringVal("%+d green bottles standing on the wall"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal("+10 green bottles standing on the wall"), ``, }, { cty.StringVal("% d green bottles standing on the wall"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal(" 10 green bottles standing on the wall"), ``, }, { cty.StringVal("%5d green bottles standing on the wall"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal(" 10 green bottles standing on the wall"), ``, }, { cty.StringVal("%-5d green bottles standing on the wall"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal("10 green bottles standing on the wall"), ``, }, { cty.StringVal("%d green bottles standing on the wall"), []cty.Value{cty.True}, cty.NilVal, `unsupported value for "%d" at 0: number required`, }, { cty.StringVal("%b"), []cty.Value{cty.NumberIntVal(5)}, cty.StringVal("101"), ``, }, { cty.StringVal("%o"), []cty.Value{cty.NumberIntVal(9)}, cty.StringVal("11"), ``, }, { cty.StringVal("%x"), []cty.Value{cty.NumberIntVal(254)}, cty.StringVal("fe"), ``, }, { cty.StringVal("%X"), []cty.Value{cty.NumberIntVal(254)}, cty.StringVal("FE"), ``, }, // Floating-point numbers { cty.StringVal("%f things"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal("10.000000 things"), ``, }, { cty.StringVal("%[2]f things"), []cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(10)}, cty.StringVal("10.000000 things"), ``, }, { cty.StringVal("%+f things"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal("+10.000000 things"), ``, }, { cty.StringVal("% f things"), []cty.Value{cty.NumberIntVal(10)}, cty.StringVal(" 10.000000 things"), ``, }, { cty.StringVal("%+f things"), []cty.Value{cty.NumberIntVal(-10)}, cty.StringVal("-10.000000 things"), ``, }, { cty.StringVal("% f things"), []cty.Value{cty.NumberIntVal(-10)}, cty.StringVal("-10.000000 things"), ``, }, { cty.StringVal("%f things"), []cty.Value{cty.StringVal("100000000000000000000000000000000000001")}, cty.StringVal("100000000000000000000000000000000000001.000000 things"), ``, }, { cty.StringVal("%f things"), []cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")}, cty.StringVal("1.000000 things"), ``, }, { cty.StringVal("%.4f things"), []cty.Value{cty.StringVal("1.00000000000000000000000000000000000001")}, cty.StringVal("1.0000 things"), ``, }, { cty.StringVal("%.1f things"), []cty.Value{cty.StringVal("1.06")}, cty.StringVal("1.1 things"), ``, }, { cty.StringVal("%e things"), []cty.Value{cty.NumberIntVal(1000)}, cty.StringVal("1.000000e+03 things"), ``, }, { cty.StringVal("%E things"), []cty.Value{cty.NumberIntVal(1000)}, cty.StringVal("1.000000E+03 things"), ``, }, { cty.StringVal("%g things"), []cty.Value{cty.NumberIntVal(1000)}, cty.StringVal("1000 things"), ``, }, { cty.StringVal("%G things"), []cty.Value{cty.NumberIntVal(1000)}, cty.StringVal("1000 things"), ``, }, { cty.StringVal("%g things"), []cty.Value{cty.StringVal("0.00000000000000000000001")}, cty.StringVal("1e-23 things"), ``, }, { cty.StringVal("%G things"), []cty.Value{cty.StringVal("0.00000000000000000000001")}, cty.StringVal("1E-23 things"), ``, }, // Unknowns { cty.UnknownVal(cty.String), []cty.Value{cty.True}, cty.UnknownVal(cty.String), ``, }, { cty.UnknownVal(cty.Bool), []cty.Value{cty.True}, cty.NilVal, `string required, but received bool`, }, { cty.StringVal("Hello, %s!"), []cty.Value{cty.UnknownVal(cty.String)}, cty.UnknownVal(cty.String), ``, }, { cty.StringVal("Hello, %[2]s!"), []cty.Value{cty.UnknownVal(cty.String), cty.StringVal("Ermintrude")}, cty.UnknownVal(cty.String), ``, }, // Invalids { cty.StringVal("%s is not in the args list"), nil, cty.NilVal, `not enough arguments for "%s" at 0: need index 1 but have 0 total`, }, { cty.StringVal("%[3]s is not in the args list"), []cty.Value{cty.True, cty.True}, cty.NilVal, `not enough arguments for "%[3]s" at 0: need index 3 but have 2 total`, }, { cty.StringVal("%[0]s is not valid because args are 1-based"), []cty.Value{cty.True, cty.True}, cty.NilVal, `unrecognized format character '0' at offset 2`, }, { cty.StringVal("%v %v %v"), []cty.Value{cty.True, cty.True}, cty.NilVal, `not enough arguments for "%v" at 6: need index 3 but have 2 total`, }, { cty.StringVal("%z is not a valid sequence"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `unsupported format verb 'z' in "%z" at offset 0`, }, { cty.StringVal("%#z is not a valid sequence"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `unsupported format verb 'z' in "%#z" at offset 0`, }, { cty.StringVal("%012z is not a valid sequence"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `unsupported format verb 'z' in "%012z" at offset 0`, }, { cty.StringVal("%☠ is not a valid sequence"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `unrecognized format character '☠' at offset 1`, }, { cty.StringVal("%💃🏿 is not a valid sequence"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `unrecognized format character '💃' at offset 1`, // since this is a grammar-level error, we don't get the full grapheme cluster }, { cty.NullVal(cty.String), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `argument must not be null`, }, { cty.StringVal("no format verbs at all"), []cty.Value{cty.NumberIntVal(10)}, cty.NilVal, `too many arguments; no verbs in format string`, }, { cty.StringVal("only one verb %d"), []cty.Value{cty.NumberIntVal(10), cty.NumberIntVal(11)}, cty.NilVal, `too many arguments; only 1 used by format string`, }, { cty.StringVal("hello %s").Mark(1), []cty.Value{cty.StringVal("world")}, cty.StringVal("hello world").Mark(1), ``, }, { cty.StringVal("hello %s"), []cty.Value{cty.StringVal("world").Mark(1)}, cty.StringVal("hello world").Mark(1), ``, }, { cty.StringVal("hello %s").Mark(0), []cty.Value{cty.StringVal("world").Mark(1)}, cty.StringVal("hello world").WithMarks(cty.NewValueMarks(0, 1)), ``, }, } for i, test := range tests { t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) { got, err := Format(test.Format, test.Args...) if test.WantErr == "" { if err != nil { t.Fatalf("unexpected error: %s", err) } } else { if err == nil { t.Fatalf("no error; want %q", test.WantErr) } errStr := err.Error() if errStr != test.WantErr { t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantErr) } return } if test.Want != cty.NilVal { if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } } else { t.Errorf("unexpected success %#v; want error", got) } }) } } func TestFormatList(t *testing.T) { tests := []struct { Format cty.Value Args []cty.Value Want cty.Value WantErr string }{ 0: { cty.StringVal(""), nil, cty.ListVal([]cty.Value{ cty.StringVal(""), }), ``, }, 1: { cty.StringVal("hello"), nil, cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), ``, }, 2: { cty.StringVal("100%% successful"), nil, cty.ListVal([]cty.Value{ cty.StringVal("100% successful"), }), ``, }, 3: { cty.StringVal("100%%"), nil, cty.ListVal([]cty.Value{ cty.StringVal("100%"), }), ``, }, 4: { cty.StringVal("%s"), []cty.Value{cty.StringVal("hello")}, cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), ``, }, 5: { cty.StringVal("%s"), []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), ``, }, 6: { cty.StringVal("%s"), []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("world"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("world"), }), ``, }, 7: { cty.StringVal("%s %s"), []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("goodbye"), }), cty.ListVal([]cty.Value{ cty.StringVal("world"), cty.StringVal("universe"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("hello world"), cty.StringVal("goodbye universe"), }), ``, }, 8: { cty.StringVal("%s %s"), []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("goodbye"), }), cty.StringVal("world"), }, cty.ListVal([]cty.Value{ cty.StringVal("hello world"), cty.StringVal("goodbye world"), }), ``, }, 9: { cty.StringVal("%s %s"), []cty.Value{ cty.StringVal("hello"), cty.ListVal([]cty.Value{ cty.StringVal("world"), cty.StringVal("universe"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("hello world"), cty.StringVal("hello universe"), }), ``, }, 10: { cty.StringVal("%s %s"), []cty.Value{ cty.ListVal([]cty.Value{ cty.StringVal("hello"), cty.StringVal("goodbye"), }), cty.ListVal([]cty.Value{ cty.StringVal("world"), }), }, cty.ListValEmpty(cty.String), `argument 2 has length 1, which is inconsistent with argument 1 of length 2`, }, 11: { cty.StringVal("%s"), []cty.Value{cty.EmptyObjectVal}, cty.ListValEmpty(cty.String), `error on format iteration 0: unsupported value for "%s" at 0: string required`, }, 12: { cty.StringVal("%v"), []cty.Value{cty.EmptyTupleVal}, cty.ListValEmpty(cty.String), // no items because our given tuple is empty ``, }, 13: { cty.StringVal("%v"), []cty.Value{cty.NullVal(cty.List(cty.String))}, cty.ListVal([]cty.Value{ cty.StringVal("null"), // we treat a null list like a list whose elements are all null }), ``, }, 14: { cty.UnknownVal(cty.String), []cty.Value{ cty.True, }, cty.UnknownVal(cty.List(cty.String)), ``, }, 15: { cty.StringVal("%v"), []cty.Value{ cty.UnknownVal(cty.String), }, cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), }), ``, }, 16: { cty.StringVal("%v"), []cty.Value{ cty.NullVal(cty.String), }, cty.ListVal([]cty.Value{ cty.StringVal("null"), }), ``, }, 17: { cty.StringVal("%v"), []cty.Value{ cty.UnknownVal(cty.List(cty.String)), }, cty.UnknownVal(cty.List(cty.String)), ``, }, 18: { cty.StringVal("%v"), []cty.Value{ cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{cty.StringVal("hello")}), cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}), cty.TupleVal([]cty.Value{cty.StringVal("world")}), }), }, cty.ListVal([]cty.Value{ cty.StringVal(`["hello"]`), cty.UnknownVal(cty.String), cty.StringVal(`["world"]`), }), ``, }, 19: { cty.StringVal("%v"), []cty.Value{ cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), }, cty.UnknownVal(cty.List(cty.String)), ``, }, 20: { cty.StringVal("%s %s"), []cty.Value{ cty.UnknownVal(cty.Tuple([]cty.Type{cty.String})), cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})), }, cty.UnknownVal(cty.List(cty.String)), `argument 2 has length 2, which is inconsistent with argument 1 of length 1`, }, 21: { cty.StringVal("%s %s"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hi")}), cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.String})), }, cty.UnknownVal(cty.List(cty.String)), `argument 2 has length 2, which is inconsistent with argument 1 of length 1`, }, 22: { cty.StringVal("%v"), []cty.Value{ cty.SetVal([]cty.Value{ cty.StringVal("hello"), cty.UnknownVal(cty.String), }), }, cty.UnknownVal(cty.List(cty.String)), ``, }, 23: { cty.StringVal("%v"), []cty.Value{cty.DynamicVal}, // The current Function implementation will default to DynamicVal // if AllowUnknown is true, even though this function has a static // return type cty.DynamicVal, ``, }, } for i, test := range tests { t.Run(fmt.Sprintf("%02d-%#v", i, test.Format), func(t *testing.T) { got, err := FormatList(test.Format, test.Args...) if test.WantErr == "" { if err != nil { t.Fatalf("unexpected error: %s", err) } } else { if err == nil { t.Fatalf("no error; want %q", test.WantErr) } errStr := err.Error() if errStr != test.WantErr { t.Fatalf("wrong error\ngot: %s\nwant: %s", errStr, test.WantErr) } return } if test.Want != cty.NilVal { if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } } else { t.Errorf("unexpected success %#v; want error", got) } }) } } go-cty-1.12.1/cty/function/stdlib/general.go000066400000000000000000000060231433256746400206660ustar00rootroot00000000000000package stdlib import ( "fmt" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/function" ) var EqualFunc = function.New(&function.Spec{ Description: `Returns true if the two given values are equal, or false otherwise.`, Params: []function.Parameter{ { Name: "a", Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, { Name: "b", Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].Equals(args[1]), nil }, }) var NotEqualFunc = function.New(&function.Spec{ Description: `Returns false if the two given values are equal, or true otherwise.`, Params: []function.Parameter{ { Name: "a", Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, { Name: "b", Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].Equals(args[1]).Not(), nil }, }) var CoalesceFunc = function.New(&function.Spec{ Description: `Returns the first of the given arguments that isn't null, or raises an error if there are no non-null arguments.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "vals", Type: cty.DynamicPseudoType, AllowUnknown: true, AllowDynamicType: true, AllowNull: true, }, Type: func(args []cty.Value) (ret cty.Type, err error) { argTypes := make([]cty.Type, len(args)) for i, val := range args { argTypes[i] = val.Type() } retType, _ := convert.UnifyUnsafe(argTypes) if retType == cty.NilType { return cty.NilType, fmt.Errorf("all arguments must have the same type") } return retType, nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { for _, argVal := range args { if !argVal.IsKnown() { return cty.UnknownVal(retType), nil } if argVal.IsNull() { continue } return convert.Convert(argVal, retType) } return cty.NilVal, fmt.Errorf("no non-null arguments") }, }) // Equal determines whether the two given values are equal, returning a // bool value. func Equal(a cty.Value, b cty.Value) (cty.Value, error) { return EqualFunc.Call([]cty.Value{a, b}) } // NotEqual is the opposite of Equal. func NotEqual(a cty.Value, b cty.Value) (cty.Value, error) { return NotEqualFunc.Call([]cty.Value{a, b}) } // Coalesce returns the first of the given arguments that is not null. If // all arguments are null, an error is produced. func Coalesce(vals ...cty.Value) (cty.Value, error) { return CoalesceFunc.Call(vals) } go-cty-1.12.1/cty/function/stdlib/general_test.go000066400000000000000000000044031433256746400217250ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestEqual(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.False, }, { cty.NumberIntVal(2), cty.NumberIntVal(2), cty.True, }, { cty.NullVal(cty.Number), cty.NullVal(cty.Number), cty.True, }, { cty.NumberIntVal(2), cty.NullVal(cty.Number), cty.False, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("Equal(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Equal(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestCoalesce(t *testing.T) { tests := []struct { Values []cty.Value Want cty.Value }{ { []cty.Value{cty.True}, cty.True, }, { []cty.Value{cty.NullVal(cty.Bool), cty.True}, cty.True, }, { []cty.Value{cty.NullVal(cty.Bool), cty.False}, cty.False, }, { []cty.Value{cty.NullVal(cty.Bool), cty.False, cty.StringVal("hello")}, cty.StringVal("false"), }, { []cty.Value{cty.True, cty.UnknownVal(cty.Bool)}, cty.True, }, { []cty.Value{cty.UnknownVal(cty.Bool), cty.True}, cty.UnknownVal(cty.Bool), }, { []cty.Value{cty.UnknownVal(cty.Bool), cty.StringVal("hello")}, cty.UnknownVal(cty.String), }, { []cty.Value{cty.DynamicVal, cty.True}, cty.UnknownVal(cty.Bool), }, { []cty.Value{cty.DynamicVal}, cty.DynamicVal, }, } for _, test := range tests { t.Run(fmt.Sprintf("Coalesce(%#v...)", test.Values), func(t *testing.T) { got, err := Coalesce(test.Values...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/json.go000066400000000000000000000042441433256746400202250ustar00rootroot00000000000000package stdlib import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/json" ) var JSONEncodeFunc = function.New(&function.Spec{ Description: `Returns a string containing a JSON representation of the given value.`, Params: []function.Parameter{ { Name: "val", Type: cty.DynamicPseudoType, AllowDynamicType: true, AllowNull: true, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { val := args[0] if !val.IsWhollyKnown() { // We can't serialize unknowns, so if the value is unknown or // contains any _nested_ unknowns then our result must be // unknown. return cty.UnknownVal(retType), nil } if val.IsNull() { return cty.StringVal("null"), nil } buf, err := json.Marshal(val, val.Type()) if err != nil { return cty.NilVal, err } return cty.StringVal(string(buf)), nil }, }) var JSONDecodeFunc = function.New(&function.Spec{ Description: `Parses the given string as JSON and returns a value corresponding to what the JSON document describes.`, Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: func(args []cty.Value) (cty.Type, error) { str := args[0] if !str.IsKnown() { return cty.DynamicPseudoType, nil } buf := []byte(str.AsString()) return json.ImpliedType(buf) }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { buf := []byte(args[0].AsString()) return json.Unmarshal(buf, retType) }, }) // JSONEncode returns a JSON serialization of the given value. func JSONEncode(val cty.Value) (cty.Value, error) { return JSONEncodeFunc.Call([]cty.Value{val}) } // JSONDecode parses the given JSON string and, if it is valid, returns the // value it represents. // // Note that applying JSONDecode to the result of JSONEncode may not produce // an identically-typed result, since JSON encoding is lossy for cty Types. // The resulting value will consist only of primitive types, object types, and // tuple types. func JSONDecode(str cty.Value) (cty.Value, error) { return JSONDecodeFunc.Call([]cty.Value{str}) } go-cty-1.12.1/cty/function/stdlib/json_test.go000066400000000000000000000051451433256746400212650ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestJSONEncode(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ // This does not comprehensively test all possible inputs because // the underlying functions in package json already have tests of // their own. Here we are mainly concerned with seeing that the // function's definition accepts all reasonable values. { cty.NumberIntVal(15), cty.StringVal(`15`), }, { cty.StringVal("hello"), cty.StringVal(`"hello"`), }, { cty.True, cty.StringVal(`true`), }, { cty.ListValEmpty(cty.Number), cty.StringVal(`[]`), }, { cty.ListVal([]cty.Value{cty.True, cty.False}), cty.StringVal(`[true,false]`), }, { cty.ObjectVal(map[string]cty.Value{"true": cty.True, "false": cty.False}), cty.StringVal(`{"false":false,"true":true}`), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.String), }, { cty.ObjectVal(map[string]cty.Value{"dunno": cty.UnknownVal(cty.Bool), "false": cty.False}), cty.UnknownVal(cty.String), }, { cty.DynamicVal, cty.UnknownVal(cty.String), }, { cty.NullVal(cty.String), cty.StringVal("null"), }, } for _, test := range tests { t.Run(fmt.Sprintf("JSONEncode(%#v)", test.Input), func(t *testing.T) { got, err := JSONEncode(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestJSONDecode(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.StringVal(`15`), cty.NumberIntVal(15), }, { cty.StringVal(`"hello"`), cty.StringVal("hello"), }, { cty.StringVal(`true`), cty.True, }, { cty.StringVal(`[]`), cty.EmptyTupleVal, }, { cty.StringVal(`[true,false]`), cty.TupleVal([]cty.Value{cty.True, cty.False}), }, { cty.StringVal(`{"false":false,"true":true}`), cty.ObjectVal(map[string]cty.Value{"true": cty.True, "false": cty.False}), }, { cty.UnknownVal(cty.String), cty.DynamicVal, // need to know the value to determine the type }, { cty.DynamicVal, cty.DynamicVal, }, { cty.StringVal(`true`).Mark(1), cty.True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("JSONDecode(%#v)", test.Input), func(t *testing.T) { got, err := JSONDecode(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/number.go000066400000000000000000000451671433256746400205550ustar00rootroot00000000000000package stdlib import ( "fmt" "math" "math/big" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" ) var AbsoluteFunc = function.New(&function.Spec{ Description: `If the given number is negative then returns its positive equivalent, or otherwise returns the given number unchanged.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return args[0].Absolute(), nil }, }) var AddFunc = function.New(&function.Spec{ Description: `Returns the sum of the two given numbers.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // big.Float.Add can panic if the input values are opposing infinities, // so we must catch that here in order to remain within // the cty Function abstraction. defer func() { if r := recover(); r != nil { if _, ok := r.(big.ErrNaN); ok { ret = cty.NilVal err = fmt.Errorf("can't compute sum of opposing infinities") } else { // not a panic we recognize panic(r) } } }() return args[0].Add(args[1]), nil }, }) var SubtractFunc = function.New(&function.Spec{ Description: `Returns the difference between the two given numbers.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // big.Float.Sub can panic if the input values are infinities, // so we must catch that here in order to remain within // the cty Function abstraction. defer func() { if r := recover(); r != nil { if _, ok := r.(big.ErrNaN); ok { ret = cty.NilVal err = fmt.Errorf("can't subtract infinity from itself") } else { // not a panic we recognize panic(r) } } }() return args[0].Subtract(args[1]), nil }, }) var MultiplyFunc = function.New(&function.Spec{ Description: `Returns the product of the two given numbers.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // big.Float.Mul can panic if the input values are both zero or both // infinity, so we must catch that here in order to remain within // the cty Function abstraction. defer func() { if r := recover(); r != nil { if _, ok := r.(big.ErrNaN); ok { ret = cty.NilVal err = fmt.Errorf("can't multiply zero by infinity") } else { // not a panic we recognize panic(r) } } }() return args[0].Multiply(args[1]), nil }, }) var DivideFunc = function.New(&function.Spec{ Description: `Divides the first given number by the second.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // big.Float.Quo can panic if the input values are both zero or both // infinity, so we must catch that here in order to remain within // the cty Function abstraction. defer func() { if r := recover(); r != nil { if _, ok := r.(big.ErrNaN); ok { ret = cty.NilVal err = fmt.Errorf("can't divide zero by zero or infinity by infinity") } else { // not a panic we recognize panic(r) } } }() return args[0].Divide(args[1]), nil }, }) var ModuloFunc = function.New(&function.Spec{ Description: `Divides the first given number by the second and then returns the remainder.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { // big.Float.Mul can panic if the input values are both zero or both // infinity, so we must catch that here in order to remain within // the cty Function abstraction. defer func() { if r := recover(); r != nil { if _, ok := r.(big.ErrNaN); ok { ret = cty.NilVal err = fmt.Errorf("can't use modulo with zero and infinity") } else { // not a panic we recognize panic(r) } } }() return args[0].Modulo(args[1]), nil }, }) var GreaterThanFunc = function.New(&function.Spec{ Description: `Returns true if and only if the second number is greater than the first.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].GreaterThan(args[1]), nil }, }) var GreaterThanOrEqualToFunc = function.New(&function.Spec{ Description: `Returns true if and only if the second number is greater than or equal to the first.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].GreaterThanOrEqualTo(args[1]), nil }, }) var LessThanFunc = function.New(&function.Spec{ Description: `Returns true if and only if the second number is less than the first.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].LessThan(args[1]), nil }, }) var LessThanOrEqualToFunc = function.New(&function.Spec{ Description: `Returns true if and only if the second number is less than or equal to the first.`, Params: []function.Parameter{ { Name: "a", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, { Name: "b", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].LessThanOrEqualTo(args[1]), nil }, }) var NegateFunc = function.New(&function.Spec{ Description: `Multiplies the given number by -1.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, AllowDynamicType: true, AllowMarked: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return args[0].Negate(), nil }, }) var MinFunc = function.New(&function.Spec{ Description: `Returns the numerically smallest of all of the given numbers.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "numbers", Type: cty.Number, AllowDynamicType: true, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { if len(args) == 0 { return cty.NilVal, fmt.Errorf("must pass at least one number") } min := cty.PositiveInfinity for _, num := range args { if num.LessThan(min).True() { min = num } } return min, nil }, }) var MaxFunc = function.New(&function.Spec{ Description: `Returns the numerically greatest of all of the given numbers.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "numbers", Type: cty.Number, AllowDynamicType: true, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { if len(args) == 0 { return cty.NilVal, fmt.Errorf("must pass at least one number") } max := cty.NegativeInfinity for _, num := range args { if num.GreaterThan(max).True() { max = num } } return max, nil }, }) var IntFunc = function.New(&function.Spec{ Description: `Discards any fractional portion of the given number.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { bf := args[0].AsBigFloat() if bf.IsInt() { return args[0], nil } bi, _ := bf.Int(nil) bf = (&big.Float{}).SetInt(bi) return cty.NumberVal(bf), nil }, }) // CeilFunc is a function that returns the closest whole number greater // than or equal to the given value. var CeilFunc = function.New(&function.Spec{ Description: `Returns the smallest whole number that is greater than or equal to the given value.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { f := args[0].AsBigFloat() if f.IsInf() { return cty.NumberVal(f), nil } i, acc := f.Int(nil) switch acc { case big.Exact, big.Above: // Done. case big.Below: i.Add(i, big.NewInt(1)) } return cty.NumberVal(f.SetInt(i)), nil }, }) // FloorFunc is a function that returns the closest whole number lesser // than or equal to the given value. var FloorFunc = function.New(&function.Spec{ Description: `Returns the greatest whole number that is less than or equal to the given value.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { f := args[0].AsBigFloat() if f.IsInf() { return cty.NumberVal(f), nil } i, acc := f.Int(nil) switch acc { case big.Exact, big.Below: // Done. case big.Above: i.Sub(i, big.NewInt(1)) } return cty.NumberVal(f.SetInt(i)), nil }, }) // LogFunc is a function that returns the logarithm of a given number in a given base. var LogFunc = function.New(&function.Spec{ Description: `Returns the logarithm of the given number in the given base.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, }, { Name: "base", Type: cty.Number, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var num float64 if err := gocty.FromCtyValue(args[0], &num); err != nil { return cty.UnknownVal(cty.String), err } var base float64 if err := gocty.FromCtyValue(args[1], &base); err != nil { return cty.UnknownVal(cty.String), err } return cty.NumberFloatVal(math.Log(num) / math.Log(base)), nil }, }) // PowFunc is a function that returns the logarithm of a given number in a given base. var PowFunc = function.New(&function.Spec{ Description: `Returns the given number raised to the given power (exponentiation).`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, }, { Name: "power", Type: cty.Number, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var num float64 if err := gocty.FromCtyValue(args[0], &num); err != nil { return cty.UnknownVal(cty.String), err } var power float64 if err := gocty.FromCtyValue(args[1], &power); err != nil { return cty.UnknownVal(cty.String), err } return cty.NumberFloatVal(math.Pow(num, power)), nil }, }) // SignumFunc is a function that determines the sign of a number, returning a // number between -1 and 1 to represent the sign.. var SignumFunc = function.New(&function.Spec{ Description: `Returns 0 if the given number is zero, 1 if the given number is positive, or -1 if the given number is negative.`, Params: []function.Parameter{ { Name: "num", Type: cty.Number, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var num int if err := gocty.FromCtyValue(args[0], &num); err != nil { return cty.UnknownVal(cty.String), err } switch { case num < 0: return cty.NumberIntVal(-1), nil case num > 0: return cty.NumberIntVal(+1), nil default: return cty.NumberIntVal(0), nil } }, }) // ParseIntFunc is a function that parses a string argument and returns an integer of the specified base. var ParseIntFunc = function.New(&function.Spec{ Description: `Parses the given string as a number of the given base, or raises an error if the string contains invalid characters.`, Params: []function.Parameter{ { Name: "number", Type: cty.DynamicPseudoType, }, { Name: "base", Type: cty.Number, }, }, Type: func(args []cty.Value) (cty.Type, error) { if !args[0].Type().Equals(cty.String) { return cty.Number, function.NewArgErrorf(0, "first argument must be a string, not %s", args[0].Type().FriendlyName()) } return cty.Number, nil }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { var numstr string var base int var err error if err = gocty.FromCtyValue(args[0], &numstr); err != nil { return cty.UnknownVal(cty.String), function.NewArgError(0, err) } if err = gocty.FromCtyValue(args[1], &base); err != nil { return cty.UnknownVal(cty.Number), function.NewArgError(1, err) } if base < 2 || base > 62 { return cty.UnknownVal(cty.Number), function.NewArgErrorf( 1, "base must be a whole number between 2 and 62 inclusive", ) } num, ok := (&big.Int{}).SetString(numstr, base) if !ok { return cty.UnknownVal(cty.Number), function.NewArgErrorf( 0, "cannot parse %q as a base %d integer", numstr, base, ) } parsedNum := cty.NumberVal((&big.Float{}).SetInt(num)) return parsedNum, nil }, }) // Absolute returns the magnitude of the given number, without its sign. // That is, it turns negative values into positive values. func Absolute(num cty.Value) (cty.Value, error) { return AbsoluteFunc.Call([]cty.Value{num}) } // Add returns the sum of the two given numbers. func Add(a cty.Value, b cty.Value) (cty.Value, error) { return AddFunc.Call([]cty.Value{a, b}) } // Subtract returns the difference between the two given numbers. func Subtract(a cty.Value, b cty.Value) (cty.Value, error) { return SubtractFunc.Call([]cty.Value{a, b}) } // Multiply returns the product of the two given numbers. func Multiply(a cty.Value, b cty.Value) (cty.Value, error) { return MultiplyFunc.Call([]cty.Value{a, b}) } // Divide returns a divided by b, where both a and b are numbers. func Divide(a cty.Value, b cty.Value) (cty.Value, error) { return DivideFunc.Call([]cty.Value{a, b}) } // Negate returns the given number multipled by -1. func Negate(num cty.Value) (cty.Value, error) { return NegateFunc.Call([]cty.Value{num}) } // LessThan returns true if a is less than b. func LessThan(a cty.Value, b cty.Value) (cty.Value, error) { return LessThanFunc.Call([]cty.Value{a, b}) } // LessThanOrEqualTo returns true if a is less than b. func LessThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) { return LessThanOrEqualToFunc.Call([]cty.Value{a, b}) } // GreaterThan returns true if a is less than b. func GreaterThan(a cty.Value, b cty.Value) (cty.Value, error) { return GreaterThanFunc.Call([]cty.Value{a, b}) } // GreaterThanOrEqualTo returns true if a is less than b. func GreaterThanOrEqualTo(a cty.Value, b cty.Value) (cty.Value, error) { return GreaterThanOrEqualToFunc.Call([]cty.Value{a, b}) } // Modulo returns the remainder of a divided by b under integer division, // where both a and b are numbers. func Modulo(a cty.Value, b cty.Value) (cty.Value, error) { return ModuloFunc.Call([]cty.Value{a, b}) } // Min returns the minimum number from the given numbers. func Min(numbers ...cty.Value) (cty.Value, error) { return MinFunc.Call(numbers) } // Max returns the maximum number from the given numbers. func Max(numbers ...cty.Value) (cty.Value, error) { return MaxFunc.Call(numbers) } // Int removes the fractional component of the given number returning an // integer representing the whole number component, rounding towards zero. // For example, -1.5 becomes -1. // // If an infinity is passed to Int, an error is returned. func Int(num cty.Value) (cty.Value, error) { if num == cty.PositiveInfinity || num == cty.NegativeInfinity { return cty.NilVal, fmt.Errorf("can't truncate infinity to an integer") } return IntFunc.Call([]cty.Value{num}) } // Ceil returns the closest whole number greater than or equal to the given value. func Ceil(num cty.Value) (cty.Value, error) { return CeilFunc.Call([]cty.Value{num}) } // Floor returns the closest whole number lesser than or equal to the given value. func Floor(num cty.Value) (cty.Value, error) { return FloorFunc.Call([]cty.Value{num}) } // Log returns returns the logarithm of a given number in a given base. func Log(num, base cty.Value) (cty.Value, error) { return LogFunc.Call([]cty.Value{num, base}) } // Pow returns the logarithm of a given number in a given base. func Pow(num, power cty.Value) (cty.Value, error) { return PowFunc.Call([]cty.Value{num, power}) } // Signum determines the sign of a number, returning a number between -1 and // 1 to represent the sign. func Signum(num cty.Value) (cty.Value, error) { return SignumFunc.Call([]cty.Value{num}) } // ParseInt parses a string argument and returns an integer of the specified base. func ParseInt(num cty.Value, base cty.Value) (cty.Value, error) { return ParseIntFunc.Call([]cty.Value{num, base}) } go-cty-1.12.1/cty/function/stdlib/number_test.go000066400000000000000000000546071433256746400216130ustar00rootroot00000000000000package stdlib import ( "fmt" "math" "math/big" "testing" "github.com/zclconf/go-cty/cty" ) func TestAbsolute(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.NumberIntVal(15), cty.NumberIntVal(15), }, { cty.NumberIntVal(-15), cty.NumberIntVal(15), }, { cty.NumberIntVal(0), cty.NumberIntVal(0), }, { cty.PositiveInfinity, cty.PositiveInfinity, }, { cty.NegativeInfinity, cty.PositiveInfinity, }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Absolute(%#v)", test.Input), func(t *testing.T) { got, err := Absolute(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestAdd(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Add(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Add(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSubtract(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(-1), }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Subtract(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Subtract(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestMultiply(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(5), cty.NumberIntVal(2), cty.NumberIntVal(10), }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Multiply(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Multiply(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestDivide(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(5), cty.NumberIntVal(2), cty.NumberFloatVal(2.5), }, { cty.NumberIntVal(5), cty.NumberIntVal(0), cty.PositiveInfinity, }, { cty.NumberIntVal(-5), cty.NumberIntVal(0), cty.NegativeInfinity, }, { cty.NumberIntVal(1), cty.PositiveInfinity, cty.Zero, }, { cty.NumberIntVal(1), cty.NegativeInfinity, cty.Zero, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Divide(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Divide(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestModulo(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(15), cty.NumberIntVal(10), cty.NumberIntVal(5), }, { cty.NumberIntVal(0), cty.NumberIntVal(0), cty.NumberIntVal(0), }, { cty.PositiveInfinity, cty.NumberIntVal(1), cty.PositiveInfinity, }, { cty.NegativeInfinity, cty.NumberIntVal(1), cty.NegativeInfinity, }, { cty.NumberIntVal(1), cty.PositiveInfinity, cty.PositiveInfinity, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Modulo(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := Modulo(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestNegate(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.NumberIntVal(15), cty.NumberIntVal(-15), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("Negate(%#v)", test.Input), func(t *testing.T) { got, err := Negate(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLessThan(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.True, }, { cty.NumberIntVal(2), cty.NumberIntVal(1), cty.False, }, { cty.NumberIntVal(2), cty.NumberIntVal(2), cty.False, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("LessThan(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := LessThan(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLessThanOrEqualTo(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.True, }, { cty.NumberIntVal(2), cty.NumberIntVal(1), cty.False, }, { cty.NumberIntVal(2), cty.NumberIntVal(2), cty.True, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("LessThanOrEqualTo(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := LessThanOrEqualTo(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestGreaterThan(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.False, }, { cty.NumberIntVal(2), cty.NumberIntVal(1), cty.True, }, { cty.NumberIntVal(2), cty.NumberIntVal(2), cty.False, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("GreaterThan(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := GreaterThan(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestGreaterThanOrEqualTo(t *testing.T) { tests := []struct { A cty.Value B cty.Value Want cty.Value }{ { cty.NumberIntVal(1), cty.NumberIntVal(2), cty.False, }, { cty.NumberIntVal(2), cty.NumberIntVal(1), cty.True, }, { cty.NumberIntVal(2), cty.NumberIntVal(2), cty.True, }, { cty.NumberIntVal(1), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Number), cty.UnknownVal(cty.Bool), }, { cty.NumberIntVal(1), cty.DynamicVal, cty.UnknownVal(cty.Bool), }, { cty.DynamicVal, cty.DynamicVal, cty.UnknownVal(cty.Bool), }, } for _, test := range tests { t.Run(fmt.Sprintf("GreaterThanOrEqualTo(%#v,%#v)", test.A, test.B), func(t *testing.T) { got, err := GreaterThanOrEqualTo(test.A, test.B) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestMin(t *testing.T) { tests := []struct { Inputs []cty.Value Want cty.Value }{ { []cty.Value{cty.NumberIntVal(0)}, cty.NumberIntVal(0), }, { []cty.Value{cty.NumberIntVal(-12)}, cty.NumberIntVal(-12), }, { []cty.Value{cty.NumberIntVal(12)}, cty.NumberIntVal(12), }, { []cty.Value{cty.NumberIntVal(-12), cty.NumberIntVal(0), cty.NumberIntVal(2)}, cty.NumberIntVal(-12), }, { []cty.Value{cty.NegativeInfinity, cty.NumberIntVal(0)}, cty.NegativeInfinity, }, { []cty.Value{cty.PositiveInfinity, cty.NumberIntVal(0)}, cty.NumberIntVal(0), }, { []cty.Value{cty.NegativeInfinity}, cty.NegativeInfinity, }, { []cty.Value{cty.PositiveInfinity, cty.UnknownVal(cty.Number)}, cty.UnknownVal(cty.Number), }, { []cty.Value{cty.PositiveInfinity, cty.DynamicVal}, cty.UnknownVal(cty.Number), }, { []cty.Value{cty.Zero.Mark(1), cty.NumberIntVal(1)}, cty.Zero.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v", test.Inputs), func(t *testing.T) { got, err := Min(test.Inputs...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestMax(t *testing.T) { tests := []struct { Inputs []cty.Value Want cty.Value }{ { []cty.Value{cty.NumberIntVal(0)}, cty.NumberIntVal(0), }, { []cty.Value{cty.NumberIntVal(-12)}, cty.NumberIntVal(-12), }, { []cty.Value{cty.NumberIntVal(12)}, cty.NumberIntVal(12), }, { []cty.Value{cty.NumberIntVal(-12), cty.NumberIntVal(0), cty.NumberIntVal(2)}, cty.NumberIntVal(2), }, { []cty.Value{cty.NegativeInfinity, cty.NumberIntVal(0)}, cty.NumberIntVal(0), }, { []cty.Value{cty.PositiveInfinity, cty.NumberIntVal(0)}, cty.PositiveInfinity, }, { []cty.Value{cty.NegativeInfinity}, cty.NegativeInfinity, }, { []cty.Value{cty.PositiveInfinity, cty.UnknownVal(cty.Number)}, cty.UnknownVal(cty.Number), }, { []cty.Value{cty.PositiveInfinity, cty.DynamicVal}, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v", test.Inputs), func(t *testing.T) { got, err := Max(test.Inputs...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestInt(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.NumberIntVal(0), cty.NumberIntVal(0), }, { cty.NumberIntVal(1), cty.NumberIntVal(1), }, { cty.NumberIntVal(-1), cty.NumberIntVal(-1), }, { cty.NumberFloatVal(1.3), cty.NumberIntVal(1), }, { cty.NumberFloatVal(-1.7), cty.NumberIntVal(-1), }, { cty.NumberFloatVal(-1.3), cty.NumberIntVal(-1), }, { cty.NumberFloatVal(-1.7), cty.NumberIntVal(-1), }, { cty.NumberVal(mustParseFloat("999999999999999999999999999999999999999999999999999999999999.7")), cty.NumberVal(mustParseFloat("999999999999999999999999999999999999999999999999999999999999")), }, { cty.NumberVal(mustParseFloat("-999999999999999999999999999999999999999999999999999999999999.7")), cty.NumberVal(mustParseFloat("-999999999999999999999999999999999999999999999999999999999999")), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Int(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func mustParseFloat(s string) *big.Float { ret, _, err := big.ParseFloat(s, 10, 0, big.AwayFromZero) if err != nil { panic(err) } return ret } func TestCeil(t *testing.T) { tests := []struct { Num cty.Value Want cty.Value Err bool }{ { cty.NumberFloatVal(-1.8), cty.NumberFloatVal(-1), false, }, { cty.NumberFloatVal(1.2), cty.NumberFloatVal(2), false, }, { cty.NumberFloatVal(math.Inf(1)), cty.NumberFloatVal(math.Inf(1)), false, }, { cty.NumberFloatVal(math.Inf(-1)), cty.NumberFloatVal(math.Inf(-1)), false, }, { cty.MustParseNumberVal("99999999999999999999999999999999999999999999999999998.123"), cty.MustParseNumberVal("99999999999999999999999999999999999999999999999999999"), false, }, { cty.MustParseNumberVal("-99999999999999999999999999999999999999999999999999998.123"), cty.MustParseNumberVal("-99999999999999999999999999999999999999999999999999998"), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("ceil(%#v)", test.Num), func(t *testing.T) { got, err := Ceil(test.Num) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestFloor(t *testing.T) { tests := []struct { Num cty.Value Want cty.Value Err bool }{ { cty.NumberFloatVal(-1.8), cty.NumberFloatVal(-2), false, }, { cty.NumberFloatVal(1.2), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(math.Inf(1)), cty.NumberFloatVal(math.Inf(1)), false, }, { cty.NumberFloatVal(math.Inf(-1)), cty.NumberFloatVal(math.Inf(-1)), false, }, { cty.MustParseNumberVal("99999999999999999999999999999999999999999999999999999.123"), cty.MustParseNumberVal("99999999999999999999999999999999999999999999999999999"), false, }, { cty.MustParseNumberVal("-99999999999999999999999999999999999999999999999999998.123"), cty.MustParseNumberVal("-99999999999999999999999999999999999999999999999999999"), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("floor(%#v)", test.Num), func(t *testing.T) { got, err := Floor(test.Num) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLog(t *testing.T) { tests := []struct { Num cty.Value Base cty.Value Want cty.Value Err bool }{ { cty.NumberFloatVal(1), cty.NumberFloatVal(10), cty.NumberFloatVal(0), false, }, { cty.NumberFloatVal(10), cty.NumberFloatVal(10), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(0), cty.NumberFloatVal(10), cty.NegativeInfinity, false, }, { cty.NumberFloatVal(10), cty.NumberFloatVal(0), cty.NumberFloatVal(-0), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("log(%#v, %#v)", test.Num, test.Base), func(t *testing.T) { got, err := Log(test.Num, test.Base) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestPow(t *testing.T) { tests := []struct { Num cty.Value Power cty.Value Want cty.Value Err bool }{ { cty.NumberFloatVal(1), cty.NumberFloatVal(0), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(1), cty.NumberFloatVal(1), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(2), cty.NumberFloatVal(0), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(2), cty.NumberFloatVal(1), cty.NumberFloatVal(2), false, }, { cty.NumberFloatVal(3), cty.NumberFloatVal(2), cty.NumberFloatVal(9), false, }, { cty.NumberFloatVal(-3), cty.NumberFloatVal(2), cty.NumberFloatVal(9), false, }, { cty.NumberFloatVal(2), cty.NumberFloatVal(-2), cty.NumberFloatVal(0.25), false, }, { cty.NumberFloatVal(0), cty.NumberFloatVal(2), cty.NumberFloatVal(0), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("pow(%#v, %#v)", test.Num, test.Power), func(t *testing.T) { got, err := Pow(test.Num, test.Power) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSignum(t *testing.T) { tests := []struct { Num cty.Value Want cty.Value Err bool }{ { cty.NumberFloatVal(0), cty.NumberFloatVal(0), false, }, { cty.NumberFloatVal(12), cty.NumberFloatVal(1), false, }, { cty.NumberFloatVal(-29), cty.NumberFloatVal(-1), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("signum(%#v)", test.Num), func(t *testing.T) { got, err := Signum(test.Num) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestParseInt(t *testing.T) { tests := []struct { Num cty.Value Base cty.Value Want cty.Value Err bool }{ { cty.StringVal("128"), cty.NumberIntVal(10), cty.NumberIntVal(128), false, }, { cty.StringVal("-128"), cty.NumberIntVal(10), cty.NumberIntVal(-128), false, }, { cty.StringVal("00128"), cty.NumberIntVal(10), cty.NumberIntVal(128), false, }, { cty.StringVal("-00128"), cty.NumberIntVal(10), cty.NumberIntVal(-128), false, }, { cty.StringVal("FF00"), cty.NumberIntVal(16), cty.NumberIntVal(65280), false, }, { cty.StringVal("ff00"), cty.NumberIntVal(16), cty.NumberIntVal(65280), false, }, { cty.StringVal("-FF00"), cty.NumberIntVal(16), cty.NumberIntVal(-65280), false, }, { cty.StringVal("00FF00"), cty.NumberIntVal(16), cty.NumberIntVal(65280), false, }, { cty.StringVal("-00FF00"), cty.NumberIntVal(16), cty.NumberIntVal(-65280), false, }, { cty.StringVal("1011111011101111"), cty.NumberIntVal(2), cty.NumberIntVal(48879), false, }, { cty.StringVal("aA"), cty.NumberIntVal(62), cty.NumberIntVal(656), false, }, { cty.StringVal("Aa"), cty.NumberIntVal(62), cty.NumberIntVal(2242), false, }, { cty.StringVal("999999999999999999999999999999999999999999999999999999999999"), cty.NumberIntVal(10), cty.MustParseNumberVal("999999999999999999999999999999999999999999999999999999999999"), false, }, { cty.StringVal("FF"), cty.NumberIntVal(10), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("00FF"), cty.NumberIntVal(10), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("-00FF"), cty.NumberIntVal(10), cty.UnknownVal(cty.Number), true, }, { cty.NumberIntVal(2), cty.NumberIntVal(10), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("1"), cty.NumberIntVal(63), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("1"), cty.NumberIntVal(-1), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("1"), cty.NumberIntVal(1), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("1"), cty.NumberIntVal(0), cty.UnknownVal(cty.Number), true, }, { cty.StringVal("1.2"), cty.NumberIntVal(10), cty.UnknownVal(cty.Number), true, }, } for _, test := range tests { t.Run(fmt.Sprintf("parseint(%#v, %#v)", test.Num, test.Base), func(t *testing.T) { got, err := ParseInt(test.Num, test.Base) if test.Err { if err == nil { t.Fatal("succeeded; want error") } return } else if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/regexp.go000066400000000000000000000172451433256746400205530ustar00rootroot00000000000000package stdlib import ( "fmt" "regexp" resyntax "regexp/syntax" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) var RegexFunc = function.New(&function.Spec{ Description: `Applies the given regular expression pattern to the given string and returns information about a single match, or raises an error if there is no match.`, Params: []function.Parameter{ { Name: "pattern", Type: cty.String, }, { Name: "string", Type: cty.String, }, }, Type: func(args []cty.Value) (cty.Type, error) { if !args[0].IsKnown() { // We can't predict our type without seeing our pattern return cty.DynamicPseudoType, nil } retTy, err := regexPatternResultType(args[0].AsString()) if err != nil { err = function.NewArgError(0, err) } return retTy, err }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { if retType == cty.DynamicPseudoType { return cty.DynamicVal, nil } re, err := regexp.Compile(args[0].AsString()) if err != nil { // Should never happen, since we checked this in the Type function above. return cty.NilVal, function.NewArgErrorf(0, "error parsing pattern: %s", err) } str := args[1].AsString() captureIdxs := re.FindStringSubmatchIndex(str) if captureIdxs == nil { return cty.NilVal, fmt.Errorf("pattern did not match any part of the given string") } return regexPatternResult(re, str, captureIdxs, retType), nil }, }) var RegexAllFunc = function.New(&function.Spec{ Description: `Applies the given regular expression pattern to the given string and returns a list of information about all non-overlapping matches, or an empty list if there are no matches.`, Params: []function.Parameter{ { Name: "pattern", Type: cty.String, }, { Name: "string", Type: cty.String, }, }, Type: func(args []cty.Value) (cty.Type, error) { if !args[0].IsKnown() { // We can't predict our type without seeing our pattern, // but we do know it'll always be a list of something. return cty.List(cty.DynamicPseudoType), nil } retTy, err := regexPatternResultType(args[0].AsString()) if err != nil { err = function.NewArgError(0, err) } return cty.List(retTy), err }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { ety := retType.ElementType() if ety == cty.DynamicPseudoType { return cty.DynamicVal, nil } re, err := regexp.Compile(args[0].AsString()) if err != nil { // Should never happen, since we checked this in the Type function above. return cty.NilVal, function.NewArgErrorf(0, "error parsing pattern: %s", err) } str := args[1].AsString() captureIdxsEach := re.FindAllStringSubmatchIndex(str, -1) if len(captureIdxsEach) == 0 { return cty.ListValEmpty(ety), nil } elems := make([]cty.Value, len(captureIdxsEach)) for i, captureIdxs := range captureIdxsEach { elems[i] = regexPatternResult(re, str, captureIdxs, ety) } return cty.ListVal(elems), nil }, }) // Regex is a function that extracts one or more substrings from a given // string by applying a regular expression pattern, describing the first // match. // // The return type depends on the composition of the capture groups (if any) // in the pattern: // // - If there are no capture groups at all, the result is a single string // representing the entire matched pattern. // - If all of the capture groups are named, the result is an object whose // keys are the named groups and whose values are their sub-matches, or // null if a particular sub-group was inside another group that didn't // match. // - If none of the capture groups are named, the result is a tuple whose // elements are the sub-groups in order and whose values are their // sub-matches, or null if a particular sub-group was inside another group // that didn't match. // - It is invalid to use both named and un-named capture groups together in // the same pattern. // // If the pattern doesn't match, this function returns an error. To test for // a match, call RegexAll and check if the length of the result is greater // than zero. func Regex(pattern, str cty.Value) (cty.Value, error) { return RegexFunc.Call([]cty.Value{pattern, str}) } // RegexAll is similar to Regex but it finds all of the non-overlapping matches // in the given string and returns a list of them. // // The result type is always a list, whose element type is deduced from the // pattern in the same way as the return type for Regex is decided. // // If the pattern doesn't match at all, this function returns an empty list. func RegexAll(pattern, str cty.Value) (cty.Value, error) { return RegexAllFunc.Call([]cty.Value{pattern, str}) } // regexPatternResultType parses the given regular expression pattern and // returns the structural type that would be returned to represent its // capture groups. // // Returns an error if parsing fails or if the pattern uses a mixture of // named and unnamed capture groups, which is not permitted. func regexPatternResultType(pattern string) (cty.Type, error) { re, rawErr := regexp.Compile(pattern) switch err := rawErr.(type) { case *resyntax.Error: return cty.NilType, fmt.Errorf("invalid regexp pattern: %s in %s", err.Code, err.Expr) case error: // Should never happen, since all regexp compile errors should // be resyntax.Error, but just in case... return cty.NilType, fmt.Errorf("error parsing pattern: %s", err) } allNames := re.SubexpNames()[1:] var names []string unnamed := 0 for _, name := range allNames { if name == "" { unnamed++ } else { if names == nil { names = make([]string, 0, len(allNames)) } names = append(names, name) } } switch { case unnamed == 0 && len(names) == 0: // If there are no capture groups at all then we'll return just a // single string for the whole match. return cty.String, nil case unnamed > 0 && len(names) > 0: return cty.NilType, fmt.Errorf("invalid regexp pattern: cannot mix both named and unnamed capture groups") case unnamed > 0: // For unnamed captures, we return a tuple of them all in order. etys := make([]cty.Type, unnamed) for i := range etys { etys[i] = cty.String } return cty.Tuple(etys), nil default: // For named captures, we return an object using the capture names // as keys. atys := make(map[string]cty.Type, len(names)) for _, name := range names { atys[name] = cty.String } return cty.Object(atys), nil } } func regexPatternResult(re *regexp.Regexp, str string, captureIdxs []int, retType cty.Type) cty.Value { switch { case retType == cty.String: start, end := captureIdxs[0], captureIdxs[1] return cty.StringVal(str[start:end]) case retType.IsTupleType(): captureIdxs = captureIdxs[2:] // index 0 is the whole pattern span, which we ignore by skipping one pair vals := make([]cty.Value, len(captureIdxs)/2) for i := range vals { start, end := captureIdxs[i*2], captureIdxs[i*2+1] if start < 0 || end < 0 { vals[i] = cty.NullVal(cty.String) // Did not match anything because containing group didn't match continue } vals[i] = cty.StringVal(str[start:end]) } return cty.TupleVal(vals) case retType.IsObjectType(): captureIdxs = captureIdxs[2:] // index 0 is the whole pattern span, which we ignore by skipping one pair vals := make(map[string]cty.Value, len(captureIdxs)/2) names := re.SubexpNames()[1:] for i, name := range names { start, end := captureIdxs[i*2], captureIdxs[i*2+1] if start < 0 || end < 0 { vals[name] = cty.NullVal(cty.String) // Did not match anything because containing group didn't match continue } vals[name] = cty.StringVal(str[start:end]) } return cty.ObjectVal(vals) default: // Should never happen panic(fmt.Sprintf("invalid return type %#v", retType)) } } go-cty-1.12.1/cty/function/stdlib/regexp_test.go000066400000000000000000000100661433256746400216040ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestRegex(t *testing.T) { tests := []struct { Pattern cty.Value String cty.Value Want cty.Value }{ { cty.StringVal("[a-z]+"), cty.StringVal("135abc456def789"), cty.StringVal("abc"), }, { cty.StringVal("([0-9]*)([a-z]*)"), cty.StringVal("135abc456def"), cty.TupleVal([]cty.Value{ cty.StringVal("135"), cty.StringVal("abc"), }), }, { cty.StringVal(`^(?:(?P[^:/?#]+):)?(?://(?P[^/?#]*))?(?P[^?#]*)(?:\?(?P[^#]*))?(?:#(?P.*))?`), cty.StringVal("http://www.ics.uci.edu/pub/ietf/uri/#Related"), cty.ObjectVal(map[string]cty.Value{ "scheme": cty.StringVal("http"), "authority": cty.StringVal("www.ics.uci.edu"), "path": cty.StringVal("/pub/ietf/uri/"), "query": cty.NullVal(cty.String), // query portion isn't present at all, because there's no ? "fragment": cty.StringVal("Related"), }), }, { cty.StringVal("([0-9]*)([a-z]*)"), cty.UnknownVal(cty.String), cty.UnknownVal(cty.Tuple([]cty.Type{ cty.String, cty.String, })), }, { cty.StringVal("(?P[0-9]*)"), cty.UnknownVal(cty.String), cty.UnknownVal(cty.Object(map[string]cty.Type{ "num": cty.String, })), }, { cty.UnknownVal(cty.String), cty.StringVal("135abc456def"), cty.DynamicVal, }, { cty.StringVal("[a-z]+").Mark(1), cty.StringVal("135abc456def789"), cty.StringVal("abc").Mark(1), }, { cty.StringVal("[a-z]+"), cty.StringVal("135abc456def789").Mark(2), cty.StringVal("abc").Mark(2), }, } for _, test := range tests { t.Run(fmt.Sprintf("Regex(%#v, %#v)", test.Pattern, test.String), func(t *testing.T) { got, err := Regex(test.Pattern, test.String) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf( "wrong result\npattern: %#v\nstring: %#v\ngot: %#v\nwant: %#v", test.Pattern, test.String, got, test.Want, ) } }) } } func TestRegexAll(t *testing.T) { tests := []struct { Pattern cty.Value String cty.Value Want cty.Value }{ { cty.StringVal("[a-z]+"), cty.StringVal("135abc456def789"), cty.ListVal([]cty.Value{ cty.StringVal("abc"), cty.StringVal("def"), }), }, { cty.StringVal("([0-9]*)([a-z]*)"), cty.StringVal("135abc456def"), cty.ListVal([]cty.Value{ cty.TupleVal([]cty.Value{ cty.StringVal("135"), cty.StringVal("abc"), }), cty.TupleVal([]cty.Value{ cty.StringVal("456"), cty.StringVal("def"), }), }), }, { cty.StringVal(`^(?:(?P[^:/?#]+):)?(?://(?P[^/?#]*))?(?P[^?#]*)(?:\?(?P[^#]*))?(?:#(?P.*))?`), cty.StringVal("http://www.ics.uci.edu/pub/ietf/uri/#Related"), cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "scheme": cty.StringVal("http"), "authority": cty.StringVal("www.ics.uci.edu"), "path": cty.StringVal("/pub/ietf/uri/"), "query": cty.NullVal(cty.String), // query portion isn't present at all, because there's no ? "fragment": cty.StringVal("Related"), }), }), }, { cty.StringVal("([0-9]*)([a-z]*)"), cty.UnknownVal(cty.String), cty.UnknownVal(cty.List(cty.Tuple([]cty.Type{ cty.String, cty.String, }))), }, { cty.StringVal("(?P[0-9]*)"), cty.UnknownVal(cty.String), cty.UnknownVal(cty.List(cty.Object(map[string]cty.Type{ "num": cty.String, }))), }, { cty.UnknownVal(cty.String), cty.StringVal("135abc456def"), cty.UnknownVal(cty.List(cty.DynamicPseudoType)), }, } for _, test := range tests { t.Run(fmt.Sprintf("RegexAll(%#v, %#v)", test.Pattern, test.String), func(t *testing.T) { got, err := RegexAll(test.Pattern, test.String) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf( "wrong result\npattern: %#v\nstring: %#v\ngot: %#v\nwant: %#v", test.Pattern, test.String, got, test.Want, ) } }) } } go-cty-1.12.1/cty/function/stdlib/sequence.go000066400000000000000000000164311433256746400210650ustar00rootroot00000000000000package stdlib import ( "fmt" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/function" ) var ConcatFunc = function.New(&function.Spec{ Description: `Concatenates together all of the given lists or tuples into a single sequence, preserving the input order.`, Params: []function.Parameter{}, VarParam: &function.Parameter{ Name: "seqs", Type: cty.DynamicPseudoType, AllowMarked: true, }, Type: func(args []cty.Value) (ret cty.Type, err error) { if len(args) == 0 { return cty.NilType, fmt.Errorf("at least one argument is required") } if args[0].Type().IsListType() { // Possibly we're going to return a list, if all of our other // args are also lists and we can find a common element type. tys := make([]cty.Type, len(args)) for i, val := range args { ty := val.Type() if !ty.IsListType() { tys = nil break } tys[i] = ty } if tys != nil { commonType, _ := convert.UnifyUnsafe(tys) if commonType != cty.NilType { return commonType, nil } } } etys := make([]cty.Type, 0, len(args)) for i, val := range args { // Discard marks for nested values, as we only need to handle types // and lengths. val, _ := val.UnmarkDeep() ety := val.Type() switch { case ety.IsTupleType(): etys = append(etys, ety.TupleElementTypes()...) case ety.IsListType(): if !val.IsKnown() { // We need to know the list to count its elements to // build our tuple type, so any concat of an unknown // list can't be typed yet. return cty.DynamicPseudoType, nil } l := val.LengthInt() subEty := ety.ElementType() for j := 0; j < l; j++ { etys = append(etys, subEty) } default: return cty.NilType, function.NewArgErrorf( i, "all arguments must be lists or tuples; got %s", ety.FriendlyName(), ) } } return cty.Tuple(etys), nil }, Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { switch { case retType.IsListType(): // If retType is a list type then we know that all of the // given values will be lists and that they will either be of // retType or of something we can convert to retType. vals := make([]cty.Value, 0, len(args)) var markses []cty.ValueMarks // remember any marked lists we find for i, list := range args { list, err = convert.Convert(list, retType) if err != nil { // Conversion might fail because we used UnifyUnsafe // to choose our return type. return cty.NilVal, function.NewArgError(i, err) } list, listMarks := list.Unmark() if len(listMarks) > 0 { markses = append(markses, listMarks) } it := list.ElementIterator() for it.Next() { _, v := it.Element() vals = append(vals, v) } } if len(vals) == 0 { return cty.ListValEmpty(retType.ElementType()).WithMarks(markses...), nil } return cty.ListVal(vals).WithMarks(markses...), nil case retType.IsTupleType(): // If retType is a tuple type then we could have a mixture of // lists and tuples but we know they all have known values // (because our params don't AllowUnknown) and we know that // concatenating them all together will produce a tuple of // retType because of the work we did in the Type function above. vals := make([]cty.Value, 0, len(args)) var markses []cty.ValueMarks // remember any marked seqs we find for _, seq := range args { seq, seqMarks := seq.Unmark() if len(seqMarks) > 0 { markses = append(markses, seqMarks) } // Both lists and tuples support ElementIterator, so this is easy. it := seq.ElementIterator() for it.Next() { _, v := it.Element() vals = append(vals, v) } } return cty.TupleVal(vals).WithMarks(markses...), nil default: // should never happen if Type is working correctly above panic("unsupported return type") } }, }) var RangeFunc = function.New(&function.Spec{ Description: `Returns a list of numbers spread evenly over a particular range.`, VarParam: &function.Parameter{ Name: "params", Type: cty.Number, }, Type: function.StaticReturnType(cty.List(cty.Number)), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var start, end, step cty.Value switch len(args) { case 1: if args[0].LessThan(cty.Zero).True() { start, end, step = cty.Zero, args[0], cty.NumberIntVal(-1) } else { start, end, step = cty.Zero, args[0], cty.NumberIntVal(1) } case 2: if args[1].LessThan(args[0]).True() { start, end, step = args[0], args[1], cty.NumberIntVal(-1) } else { start, end, step = args[0], args[1], cty.NumberIntVal(1) } case 3: start, end, step = args[0], args[1], args[2] default: return cty.NilVal, fmt.Errorf("must have one, two, or three arguments") } var vals []cty.Value if step == cty.Zero { return cty.NilVal, function.NewArgErrorf(2, "step must not be zero") } down := step.LessThan(cty.Zero).True() if down { if end.GreaterThan(start).True() { return cty.NilVal, function.NewArgErrorf(1, "end must be less than start when step is negative") } } else { if end.LessThan(start).True() { return cty.NilVal, function.NewArgErrorf(1, "end must be greater than start when step is positive") } } num := start for { if down { if num.LessThanOrEqualTo(end).True() { break } } else { if num.GreaterThanOrEqualTo(end).True() { break } } if len(vals) >= 1024 { // Artificial limit to prevent bad arguments from consuming huge amounts of memory return cty.NilVal, fmt.Errorf("more than 1024 values were generated; either decrease the difference between start and end or use a smaller step") } vals = append(vals, num) num = num.Add(step) } if len(vals) == 0 { return cty.ListValEmpty(cty.Number), nil } return cty.ListVal(vals), nil }, }) // Concat takes one or more sequences (lists or tuples) and returns the single // sequence that results from concatenating them together in order. // // If all of the given sequences are lists of the same element type then the // result is a list of that type. Otherwise, the result is a of a tuple type // constructed from the given sequence types. func Concat(seqs ...cty.Value) (cty.Value, error) { return ConcatFunc.Call(seqs) } // Range creates a list of numbers by starting from the given starting value, // then adding the given step value until the result is greater than or // equal to the given stopping value. Each intermediate result becomes an // element in the resulting list. // // When all three parameters are set, the order is (start, end, step). If // only two parameters are set, they are the start and end respectively and // step defaults to 1. If only one argument is set, it gives the end value // with start defaulting to 0 and step defaulting to 1. // // Because the resulting list must be fully buffered in memory, there is an // artificial cap of 1024 elements, after which this function will return // an error to avoid consuming unbounded amounts of memory. The Range function // is primarily intended for creating small lists of indices to iterate over, // so there should be no reason to generate huge lists with it. func Range(params ...cty.Value) (cty.Value, error) { return RangeFunc.Call(params) } go-cty-1.12.1/cty/function/stdlib/sequence_test.go000066400000000000000000000207201433256746400221200ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestConcat(t *testing.T) { tests := []struct { Input []cty.Value Want cty.Value }{ { []cty.Value{ cty.ListValEmpty(cty.Number), }, cty.ListValEmpty(cty.Number), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.NumberIntVal(2), cty.NumberIntVal(3), }), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.NumberIntVal(2), cty.NumberIntVal(3), }).Mark("a"), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }).Mark("a"), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }).Mark("a"), cty.ListVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }).Mark("a"), }, { []cty.Value{ cty.ListValEmpty(cty.DynamicPseudoType).Mark("a"), cty.ListVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }).Mark("c"), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }).WithMarks(cty.NewValueMarks("a", "c")), }, { []cty.Value{ cty.ListValEmpty(cty.DynamicPseudoType).Mark("a"), cty.TupleVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }).Mark("c"), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(2).Mark("b"), cty.NumberIntVal(3), }).WithMarks(cty.NewValueMarks("a", "c")), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.StringVal("foo"), }), cty.ListVal([]cty.Value{ cty.True, }), }, cty.ListVal([]cty.Value{ cty.StringVal("1"), cty.StringVal("foo"), cty.StringVal("true"), }), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.StringVal("foo"), cty.StringVal("bar"), }), }, cty.ListVal([]cty.Value{ cty.StringVal("1"), cty.StringVal("foo"), cty.StringVal("bar"), }), }, { []cty.Value{ cty.EmptyTupleVal, }, cty.EmptyTupleVal, }, { []cty.Value{ cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, cty.NumberIntVal(3), }), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, cty.NumberIntVal(3), }), }, { []cty.Value{ cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), }), cty.TupleVal([]cty.Value{ cty.True, cty.NumberIntVal(3), }), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, cty.NumberIntVal(3), }), }, { []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.TupleVal([]cty.Value{ cty.True, cty.NumberIntVal(3), }), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, cty.NumberIntVal(3), }), }, { []cty.Value{ cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, }), cty.ListVal([]cty.Value{ cty.NumberIntVal(3), }), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.True, cty.NumberIntVal(3), }), }, { // Two lists with unconvertable element types become a tuple. []cty.Value{ cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), cty.ListVal([]cty.Value{ cty.ListValEmpty(cty.Bool), }), }, cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.ListValEmpty(cty.Bool), }), }, } for _, test := range tests { t.Run(fmt.Sprintf("Concat(%#v...)", test.Input), func(t *testing.T) { got, err := Concat(test.Input...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestRange(t *testing.T) { tests := []struct { Args []cty.Value Want cty.Value }{ // One argument { []cty.Value{ cty.NumberIntVal(5), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), }), }, { []cty.Value{ cty.NumberIntVal(-5), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(-1), cty.NumberIntVal(-2), cty.NumberIntVal(-3), cty.NumberIntVal(-4), }), }, { []cty.Value{ cty.NumberIntVal(1), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), }), }, { []cty.Value{ cty.NumberIntVal(0), }, cty.ListValEmpty(cty.Number), }, { []cty.Value{ cty.MustParseNumberVal("5.5"), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), cty.NumberIntVal(5), // because 5 < 5.5 }), }, // Two arguments { []cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(5), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), }), }, { []cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(1), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(4), cty.NumberIntVal(3), cty.NumberIntVal(2), }), }, { []cty.Value{ cty.NumberFloatVal(1.5), cty.NumberIntVal(5), }, cty.ListVal([]cty.Value{ cty.NumberFloatVal(1.5), cty.NumberFloatVal(2.5), cty.NumberFloatVal(3.5), cty.NumberFloatVal(4.5), }), }, { []cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(1), }), }, { []cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(1), }, cty.ListValEmpty(cty.Number), }, // Three arguments { []cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(5), cty.NumberIntVal(2), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(2), cty.NumberIntVal(4), }), }, { []cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(5), cty.NumberIntVal(1), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), cty.NumberIntVal(4), }), }, { []cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(1), cty.NumberIntVal(1), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), }), }, { []cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(0), cty.NumberIntVal(1), }, cty.ListValEmpty(cty.Number), }, { []cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(0), cty.NumberIntVal(-1), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(5), cty.NumberIntVal(4), cty.NumberIntVal(3), cty.NumberIntVal(2), cty.NumberIntVal(1), }), }, { []cty.Value{ cty.NumberIntVal(0), cty.NumberIntVal(5), cty.NumberFloatVal(0.5), }, cty.ListVal([]cty.Value{ cty.NumberIntVal(0), cty.NumberFloatVal(0.5), cty.NumberIntVal(1), cty.NumberFloatVal(1.5), cty.NumberIntVal(2), cty.NumberFloatVal(2.5), cty.NumberIntVal(3), cty.NumberFloatVal(3.5), cty.NumberIntVal(4), cty.NumberFloatVal(4.5), }), }, } for _, test := range tests { t.Run(fmt.Sprintf("Range(%#v)", test.Args), func(t *testing.T) { got, err := Range(test.Args...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf( "wrong result\nargs: %#v\ngot: %#v\nwant: %#v", test.Args, got, test.Want, ) } }) } } go-cty-1.12.1/cty/function/stdlib/set.go000066400000000000000000000162701433256746400200510ustar00rootroot00000000000000package stdlib import ( "fmt" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) var SetHasElementFunc = function.New(&function.Spec{ Description: `Returns true if the given set contains the given element, or false otherwise.`, Params: []function.Parameter{ { Name: "set", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, { Name: "elem", Type: cty.DynamicPseudoType, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Bool), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return args[0].HasElement(args[1]), nil }, }) var SetUnionFunc = function.New(&function.Spec{ Description: `Returns the union of all given sets.`, Params: []function.Parameter{ { Name: "first_set", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, }, VarParam: &function.Parameter{ Name: "other_sets", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, Type: setOperationReturnType, Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet { return s1.Union(s2) }, true), }) var SetIntersectionFunc = function.New(&function.Spec{ Description: `Returns the intersection of all given sets.`, Params: []function.Parameter{ { Name: "first_set", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, }, VarParam: &function.Parameter{ Name: "other_sets", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, Type: setOperationReturnType, Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet { return s1.Intersection(s2) }, false), }) var SetSubtractFunc = function.New(&function.Spec{ Description: `Returns the relative complement of the two given sets.`, Params: []function.Parameter{ { Name: "a", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, { Name: "b", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, }, Type: setOperationReturnType, Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet { return s1.Subtract(s2) }, false), }) var SetSymmetricDifferenceFunc = function.New(&function.Spec{ Description: `Returns the symmetric difference of the two given sets.`, Params: []function.Parameter{ { Name: "first_set", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, }, VarParam: &function.Parameter{ Name: "other_sets", Type: cty.Set(cty.DynamicPseudoType), AllowDynamicType: true, }, Type: setOperationReturnType, Impl: setOperationImpl(func(s1, s2 cty.ValueSet) cty.ValueSet { return s1.SymmetricDifference(s2) }, false), }) // SetHasElement determines whether the given set contains the given value as an // element. func SetHasElement(set cty.Value, elem cty.Value) (cty.Value, error) { return SetHasElementFunc.Call([]cty.Value{set, elem}) } // SetUnion returns a new set containing all of the elements from the given // sets, which must have element types that can all be converted to some // common type using the standard type unification rules. If conversion // is not possible, an error is returned. // // The union operation is performed after type conversion, which may result // in some previously-distinct values being conflated. // // At least one set must be provided. func SetUnion(sets ...cty.Value) (cty.Value, error) { return SetUnionFunc.Call(sets) } // Intersection returns a new set containing the elements that exist // in all of the given sets, which must have element types that can all be // converted to some common type using the standard type unification rules. // If conversion is not possible, an error is returned. // // The intersection operation is performed after type conversion, which may // result in some previously-distinct values being conflated. // // At least one set must be provided. func SetIntersection(sets ...cty.Value) (cty.Value, error) { return SetIntersectionFunc.Call(sets) } // SetSubtract returns a new set containing the elements from the // first set that are not present in the second set. The sets must have // element types that can both be converted to some common type using the // standard type unification rules. If conversion is not possible, an error // is returned. // // The subtract operation is performed after type conversion, which may // result in some previously-distinct values being conflated. func SetSubtract(a, b cty.Value) (cty.Value, error) { return SetSubtractFunc.Call([]cty.Value{a, b}) } // SetSymmetricDifference returns a new set containing elements that appear // in any of the given sets but not multiple. The sets must have // element types that can all be converted to some common type using the // standard type unification rules. If conversion is not possible, an error // is returned. // // The difference operation is performed after type conversion, which may // result in some previously-distinct values being conflated. func SetSymmetricDifference(sets ...cty.Value) (cty.Value, error) { return SetSymmetricDifferenceFunc.Call(sets) } func setOperationReturnType(args []cty.Value) (ret cty.Type, err error) { var etys []cty.Type for _, arg := range args { ty := arg.Type().ElementType() // Do not unify types for empty dynamic pseudo typed collections. These // will always convert to any other concrete type. if arg.IsKnown() && arg.LengthInt() == 0 && ty.Equals(cty.DynamicPseudoType) { continue } etys = append(etys, ty) } // If all element types were skipped (due to being empty dynamic collections), // the return type should also be a set of dynamic pseudo type. if len(etys) == 0 { return cty.Set(cty.DynamicPseudoType), nil } newEty, _ := convert.UnifyUnsafe(etys) if newEty == cty.NilType { return cty.NilType, fmt.Errorf("given sets must all have compatible element types") } return cty.Set(newEty), nil } func setOperationImpl(f func(s1, s2 cty.ValueSet) cty.ValueSet, allowUnknowns bool) function.ImplFunc { return func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { first := args[0] first, err = convert.Convert(first, retType) if err != nil { return cty.NilVal, function.NewArgError(0, err) } if !allowUnknowns && !first.IsWhollyKnown() { // This set function can produce a correct result only when all // elements are known, because eventually knowing the unknown // values may cause the result to have fewer known elements, or // might cause a result with no unknown elements at all to become // one with a different length. return cty.UnknownVal(retType), nil } set := first.AsValueSet() for i, arg := range args[1:] { arg, err := convert.Convert(arg, retType) if err != nil { return cty.NilVal, function.NewArgError(i+1, err) } if !allowUnknowns && !arg.IsWhollyKnown() { // (For the same reason as we did this check for "first" above.) return cty.UnknownVal(retType), nil } argSet := arg.AsValueSet() set = f(set, argSet) } return cty.SetValFromValueSet(set), nil } } go-cty-1.12.1/cty/function/stdlib/set_test.go000066400000000000000000000175741433256746400211200ustar00rootroot00000000000000package stdlib import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestSetUnion(t *testing.T) { tests := []struct { Input []cty.Value Want cty.Value }{ { []cty.Value{ cty.SetValEmpty(cty.String), }, cty.SetValEmpty(cty.String), }, { []cty.Value{ cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), }, cty.SetValEmpty(cty.String), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.String), }, cty.SetVal([]cty.Value{cty.StringVal("true")}), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetVal([]cty.Value{cty.True}), cty.SetVal([]cty.Value{cty.False}), }, cty.SetVal([]cty.Value{ cty.True, cty.False, }), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("a")}), cty.SetVal([]cty.Value{cty.StringVal("b")}), cty.SetVal([]cty.Value{cty.StringVal("b"), cty.StringVal("c")}), }, cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetVal([]cty.Value{cty.True}), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.EmptyObjectVal}), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetVal([]cty.Value{cty.EmptyObjectVal}), }, { []cty.Value{ cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetValEmpty(cty.DynamicPseudoType), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.UnknownVal(cty.Set(cty.Number)), }, cty.UnknownVal(cty.Set(cty.String)), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)}), }, cty.SetVal([]cty.Value{cty.StringVal("5"), cty.UnknownVal(cty.String)}), }, } for _, test := range tests { t.Run(fmt.Sprintf("SetUnion(%#v...)", test.Input), func(t *testing.T) { got, err := SetUnion(test.Input...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSetIntersection(t *testing.T) { tests := []struct { Input []cty.Value Want cty.Value }{ { []cty.Value{ cty.SetValEmpty(cty.String), }, cty.SetValEmpty(cty.String), }, { []cty.Value{ cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), }, cty.SetValEmpty(cty.String), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.String), }, cty.SetValEmpty(cty.String), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetVal([]cty.Value{cty.True, cty.False}), cty.SetVal([]cty.Value{cty.True, cty.False}), }, cty.SetVal([]cty.Value{ cty.True, }), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("a"), cty.StringVal("b")}), cty.SetVal([]cty.Value{cty.StringVal("b")}), cty.SetVal([]cty.Value{cty.StringVal("b"), cty.StringVal("c")}), }, cty.SetVal([]cty.Value{ cty.StringVal("b"), }), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetValEmpty(cty.Bool), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.EmptyObjectVal}), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetValEmpty(cty.EmptyObject), }, { []cty.Value{ cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), }, cty.SetValEmpty(cty.DynamicPseudoType), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.UnknownVal(cty.Set(cty.Number)), }, cty.UnknownVal(cty.Set(cty.String)), }, { []cty.Value{ cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)}), }, cty.UnknownVal(cty.Set(cty.String)), }, } for _, test := range tests { t.Run(fmt.Sprintf("SetIntersection(%#v...)", test.Input), func(t *testing.T) { got, err := SetIntersection(test.Input...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSetSubtract(t *testing.T) { tests := []struct { InputA cty.Value InputB cty.Value Want cty.Value }{ { cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), }, { cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.String), cty.SetVal([]cty.Value{cty.StringVal("true")}), }, { cty.SetVal([]cty.Value{cty.True}), cty.SetVal([]cty.Value{cty.False}), cty.SetVal([]cty.Value{cty.True}), }, { cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }), cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("c"), }), cty.SetVal([]cty.Value{ cty.StringVal("b"), }), }, { cty.SetVal([]cty.Value{cty.StringVal("a")}), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetVal([]cty.Value{cty.StringVal("a")}), }, { cty.SetVal([]cty.Value{cty.EmptyObjectVal}), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetVal([]cty.Value{cty.EmptyObjectVal}), }, { cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), }, { cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.UnknownVal(cty.Set(cty.Number)), cty.UnknownVal(cty.Set(cty.String)), }, { cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)}), cty.UnknownVal(cty.Set(cty.String)), }, } for _, test := range tests { t.Run(fmt.Sprintf("SetSubtract(%#v, %#v)", test.InputA, test.InputB), func(t *testing.T) { got, err := SetSubtract(test.InputA, test.InputB) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSetSymmetricDifference(t *testing.T) { tests := []struct { InputA cty.Value InputB cty.Value Want cty.Value }{ { cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), cty.SetValEmpty(cty.String), }, { cty.SetVal([]cty.Value{cty.True}), cty.SetValEmpty(cty.String), cty.SetVal([]cty.Value{cty.StringVal("true")}), }, { cty.SetVal([]cty.Value{cty.True}), cty.SetVal([]cty.Value{cty.False}), cty.SetVal([]cty.Value{cty.True, cty.False}), }, { cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("b"), cty.StringVal("c"), }), cty.SetVal([]cty.Value{ cty.StringVal("a"), cty.StringVal("c"), }), cty.SetVal([]cty.Value{ cty.StringVal("b"), }), }, { cty.SetVal([]cty.Value{cty.StringVal("a")}), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetVal([]cty.Value{cty.StringVal("a")}), }, { cty.SetVal([]cty.Value{cty.EmptyObjectVal}), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetVal([]cty.Value{cty.EmptyObjectVal}), }, { cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), cty.SetValEmpty(cty.DynamicPseudoType), }, { cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.UnknownVal(cty.Set(cty.Number)), cty.UnknownVal(cty.Set(cty.String)), }, { cty.SetVal([]cty.Value{cty.StringVal("5")}), cty.SetVal([]cty.Value{cty.UnknownVal(cty.Number)}), cty.UnknownVal(cty.Set(cty.String)), }, } for _, test := range tests { t.Run(fmt.Sprintf("SetSymmetricDifference(%#v, %#v)", test.InputA, test.InputB), func(t *testing.T) { got, err := SetSymmetricDifference(test.InputA, test.InputB) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/string.go000066400000000000000000000417411433256746400205650ustar00rootroot00000000000000package stdlib import ( "fmt" "regexp" "sort" "strings" "github.com/apparentlymart/go-textseg/v13/textseg" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" ) var UpperFunc = function.New(&function.Spec{ Description: "Returns the given string with all Unicode letters translated to their uppercase equivalents.", Params: []function.Parameter{ { Name: "str", Type: cty.String, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { in := args[0].AsString() out := strings.ToUpper(in) return cty.StringVal(out), nil }, }) var LowerFunc = function.New(&function.Spec{ Description: "Returns the given string with all Unicode letters translated to their lowercase equivalents.", Params: []function.Parameter{ { Name: "str", Type: cty.String, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { in := args[0].AsString() out := strings.ToLower(in) return cty.StringVal(out), nil }, }) var ReverseFunc = function.New(&function.Spec{ Description: "Returns the given string with all of its Unicode characters in reverse order.", Params: []function.Parameter{ { Name: "str", Type: cty.String, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { in := []byte(args[0].AsString()) out := make([]byte, len(in)) pos := len(out) inB := []byte(in) for i := 0; i < len(in); { d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) cluster := in[i : i+d] pos -= len(cluster) copy(out[pos:], cluster) i += d } return cty.StringVal(string(out)), nil }, }) var StrlenFunc = function.New(&function.Spec{ Description: "Returns the number of Unicode characters (technically: grapheme clusters) in the given string.", Params: []function.Parameter{ { Name: "str", Type: cty.String, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.Number), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { in := args[0].AsString() l := 0 inB := []byte(in) for i := 0; i < len(in); { d, _, _ := textseg.ScanGraphemeClusters(inB[i:], true) l++ i += d } return cty.NumberIntVal(int64(l)), nil }, }) var SubstrFunc = function.New(&function.Spec{ Description: "Extracts a substring from the given string.", Params: []function.Parameter{ { Name: "str", Description: "The input string.", Type: cty.String, AllowDynamicType: true, }, { Name: "offset", Description: "The starting offset in Unicode characters.", Type: cty.Number, AllowDynamicType: true, }, { Name: "length", Description: "The maximum length of the result in Unicode characters.", Type: cty.Number, AllowDynamicType: true, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { in := []byte(args[0].AsString()) var offset, length int var err error err = gocty.FromCtyValue(args[1], &offset) if err != nil { return cty.NilVal, err } err = gocty.FromCtyValue(args[2], &length) if err != nil { return cty.NilVal, err } if offset < 0 { totalLenNum, err := Strlen(args[0]) if err != nil { // should never happen panic("Stdlen returned an error") } var totalLen int err = gocty.FromCtyValue(totalLenNum, &totalLen) if err != nil { // should never happen panic("Stdlen returned a non-int number") } offset += totalLen } else if length == 0 { // Short circuit here, after error checks, because if a // string of length 0 has been requested it will always // be the empty string return cty.StringVal(""), nil } sub := in pos := 0 var i int // First we'll seek forward to our offset if offset > 0 { for i = 0; i < len(sub); { d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) i += d pos++ if pos == offset { break } if i >= len(in) { return cty.StringVal(""), nil } } sub = sub[i:] } if length < 0 { // Taking the remainder of the string is a fast path since // we can just return the rest of the buffer verbatim. return cty.StringVal(string(sub)), nil } // Otherwise we need to start seeking forward again until we // reach the length we want. pos = 0 for i = 0; i < len(sub); { d, _, _ := textseg.ScanGraphemeClusters(sub[i:], true) i += d pos++ if pos == length { break } } sub = sub[:i] return cty.StringVal(string(sub)), nil }, }) var JoinFunc = function.New(&function.Spec{ Description: "Concatenates together the elements of all given lists with a delimiter, producing a single string.", Params: []function.Parameter{ { Name: "separator", Description: "Delimiter to insert between the given strings.", Type: cty.String, }, }, VarParam: &function.Parameter{ Name: "lists", Description: "One or more lists of strings to join.", Type: cty.List(cty.String), }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { sep := args[0].AsString() listVals := args[1:] if len(listVals) < 1 { return cty.UnknownVal(cty.String), fmt.Errorf("at least one list is required") } l := 0 for _, list := range listVals { if !list.IsWhollyKnown() { return cty.UnknownVal(cty.String), nil } l += list.LengthInt() } items := make([]string, 0, l) for ai, list := range listVals { ei := 0 for it := list.ElementIterator(); it.Next(); { _, val := it.Element() if val.IsNull() { if len(listVals) > 1 { return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d of list %d is null; cannot concatenate null values", ei, ai+1) } return cty.UnknownVal(cty.String), function.NewArgErrorf(ai+1, "element %d is null; cannot concatenate null values", ei) } items = append(items, val.AsString()) ei++ } } return cty.StringVal(strings.Join(items, sep)), nil }, }) var SortFunc = function.New(&function.Spec{ Description: "Applies a lexicographic sort to the elements of the given list.", Params: []function.Parameter{ { Name: "list", Type: cty.List(cty.String), }, }, Type: function.StaticReturnType(cty.List(cty.String)), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { listVal := args[0] if !listVal.IsWhollyKnown() { // If some of the element values aren't known yet then we // can't yet predict the order of the result. return cty.UnknownVal(retType), nil } if listVal.LengthInt() == 0 { // Easy path return listVal, nil } list := make([]string, 0, listVal.LengthInt()) for it := listVal.ElementIterator(); it.Next(); { iv, v := it.Element() if v.IsNull() { return cty.UnknownVal(retType), fmt.Errorf("given list element %s is null; a null string cannot be sorted", iv.AsBigFloat().String()) } list = append(list, v.AsString()) } sort.Strings(list) retVals := make([]cty.Value, len(list)) for i, s := range list { retVals[i] = cty.StringVal(s) } return cty.ListVal(retVals), nil }, }) var SplitFunc = function.New(&function.Spec{ Description: "Produces a list of one or more strings by splitting the given string at all instances of a given separator substring.", Params: []function.Parameter{ { Name: "separator", Description: "The substring that delimits the result strings.", Type: cty.String, }, { Name: "str", Description: "The string to split.", Type: cty.String, }, }, Type: function.StaticReturnType(cty.List(cty.String)), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { sep := args[0].AsString() str := args[1].AsString() elems := strings.Split(str, sep) elemVals := make([]cty.Value, len(elems)) for i, s := range elems { elemVals[i] = cty.StringVal(s) } if len(elemVals) == 0 { return cty.ListValEmpty(cty.String), nil } return cty.ListVal(elemVals), nil }, }) // ChompFunc is a function that removes newline characters at the end of a // string. var ChompFunc = function.New(&function.Spec{ Description: "Removes one or more newline characters from the end of the given string.", Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) return cty.StringVal(newlines.ReplaceAllString(args[0].AsString(), "")), nil }, }) // IndentFunc is a function that adds a given number of spaces to the // beginnings of all but the first line in a given multi-line string. var IndentFunc = function.New(&function.Spec{ Description: "Adds a given number of spaces after each newline character in the given string.", Params: []function.Parameter{ { Name: "spaces", Description: "Number of spaces to add after each newline character.", Type: cty.Number, }, { Name: "str", Description: "The string to transform.", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var spaces int if err := gocty.FromCtyValue(args[0], &spaces); err != nil { return cty.UnknownVal(cty.String), err } data := args[1].AsString() pad := strings.Repeat(" ", spaces) return cty.StringVal(strings.Replace(data, "\n", "\n"+pad, -1)), nil }, }) // TitleFunc is a function that converts the first letter of each word in the // given string to uppercase. var TitleFunc = function.New(&function.Spec{ Description: "Replaces one letter after each non-letter and non-digit character with its uppercase equivalent.", Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return cty.StringVal(strings.Title(args[0].AsString())), nil }, }) // TrimSpaceFunc is a function that removes any space characters from the start // and end of the given string. var TrimSpaceFunc = function.New(&function.Spec{ Description: "Removes any consecutive space characters (as defined by Unicode) from the start and end of the given string.", Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { return cty.StringVal(strings.TrimSpace(args[0].AsString())), nil }, }) // TrimFunc is a function that removes the specified characters from the start // and end of the given string. var TrimFunc = function.New(&function.Spec{ Description: "Removes consecutive sequences of characters in \"cutset\" from the start and end of the given string.", Params: []function.Parameter{ { Name: "str", Description: "The string to trim.", Type: cty.String, }, { Name: "cutset", Description: "A string containing all of the characters to trim. Each character is taken separately, so the order of characters is insignificant.", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { str := args[0].AsString() cutset := args[1].AsString() // NOTE: This doesn't properly handle any character that is encoded // with multiple sequential code units, such as letters with // combining diacritics and emoji modifier sequences. return cty.StringVal(strings.Trim(str, cutset)), nil }, }) // TrimPrefixFunc is a function that removes the specified characters from the // start the given string. var TrimPrefixFunc = function.New(&function.Spec{ Description: "Removes the given prefix from the start of the given string, if present.", Params: []function.Parameter{ { Name: "str", Description: "The string to trim.", Type: cty.String, }, { Name: "prefix", Description: "The prefix to remove, if present.", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { str := args[0].AsString() prefix := args[1].AsString() return cty.StringVal(strings.TrimPrefix(str, prefix)), nil }, }) // TrimSuffixFunc is a function that removes the specified characters from the // end of the given string. var TrimSuffixFunc = function.New(&function.Spec{ Description: "Removes the given suffix from the start of the given string, if present.", Params: []function.Parameter{ { Name: "str", Description: "The string to trim.", Type: cty.String, }, { Name: "suffix", Description: "The suffix to remove, if present.", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { str := args[0].AsString() cutset := args[1].AsString() return cty.StringVal(strings.TrimSuffix(str, cutset)), nil }, }) // Upper is a Function that converts a given string to uppercase. func Upper(str cty.Value) (cty.Value, error) { return UpperFunc.Call([]cty.Value{str}) } // Lower is a Function that converts a given string to lowercase. func Lower(str cty.Value) (cty.Value, error) { return LowerFunc.Call([]cty.Value{str}) } // Reverse is a Function that reverses the order of the characters in the // given string. // // As usual, "character" for the sake of this function is a grapheme cluster, // so combining diacritics (for example) will be considered together as a // single character. func Reverse(str cty.Value) (cty.Value, error) { return ReverseFunc.Call([]cty.Value{str}) } // Strlen is a Function that returns the length of the given string in // characters. // // As usual, "character" for the sake of this function is a grapheme cluster, // so combining diacritics (for example) will be considered together as a // single character. func Strlen(str cty.Value) (cty.Value, error) { return StrlenFunc.Call([]cty.Value{str}) } // Substr is a Function that extracts a sequence of characters from another // string and creates a new string. // // As usual, "character" for the sake of this function is a grapheme cluster, // so combining diacritics (for example) will be considered together as a // single character. // // The "offset" index may be negative, in which case it is relative to the // end of the given string. // // The "length" may be -1, in which case the remainder of the string after // the given offset will be returned. func Substr(str cty.Value, offset cty.Value, length cty.Value) (cty.Value, error) { return SubstrFunc.Call([]cty.Value{str, offset, length}) } // Join concatenates together the string elements of one or more lists with a // given separator. func Join(sep cty.Value, lists ...cty.Value) (cty.Value, error) { args := make([]cty.Value, len(lists)+1) args[0] = sep copy(args[1:], lists) return JoinFunc.Call(args) } // Sort re-orders the elements of a given list of strings so that they are // in ascending lexicographical order. func Sort(list cty.Value) (cty.Value, error) { return SortFunc.Call([]cty.Value{list}) } // Split divides a given string by a given separator, returning a list of // strings containing the characters between the separator sequences. func Split(sep, str cty.Value) (cty.Value, error) { return SplitFunc.Call([]cty.Value{sep, str}) } // Chomp removes newline characters at the end of a string. func Chomp(str cty.Value) (cty.Value, error) { return ChompFunc.Call([]cty.Value{str}) } // Indent adds a given number of spaces to the beginnings of all but the first // line in a given multi-line string. func Indent(spaces, str cty.Value) (cty.Value, error) { return IndentFunc.Call([]cty.Value{spaces, str}) } // Title converts the first letter of each word in the given string to uppercase. func Title(str cty.Value) (cty.Value, error) { return TitleFunc.Call([]cty.Value{str}) } // TrimSpace removes any space characters from the start and end of the given string. func TrimSpace(str cty.Value) (cty.Value, error) { return TrimSpaceFunc.Call([]cty.Value{str}) } // Trim removes the specified characters from the start and end of the given string. func Trim(str, cutset cty.Value) (cty.Value, error) { return TrimFunc.Call([]cty.Value{str, cutset}) } // TrimPrefix removes the specified prefix from the start of the given string. func TrimPrefix(str, prefix cty.Value) (cty.Value, error) { return TrimPrefixFunc.Call([]cty.Value{str, prefix}) } // TrimSuffix removes the specified suffix from the end of the given string. func TrimSuffix(str, suffix cty.Value) (cty.Value, error) { return TrimSuffixFunc.Call([]cty.Value{str, suffix}) } go-cty-1.12.1/cty/function/stdlib/string_replace.go000066400000000000000000000047321433256746400222570ustar00rootroot00000000000000package stdlib import ( "regexp" "strings" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" ) // ReplaceFunc is a function that searches a given string for another given // substring, and replaces each occurence with a given replacement string. // The substr argument is a simple string. var ReplaceFunc = function.New(&function.Spec{ Description: `Replaces all instances of the given substring in the given string with the given replacement string.`, Params: []function.Parameter{ { Name: "str", Description: `The string to search within.`, Type: cty.String, }, { Name: "substr", Description: `The substring to search for.`, Type: cty.String, }, { Name: "replace", Description: `The new substring to replace substr with.`, Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { str := args[0].AsString() substr := args[1].AsString() replace := args[2].AsString() return cty.StringVal(strings.Replace(str, substr, replace, -1)), nil }, }) // RegexReplaceFunc is a function that searches a given string for another // given substring, and replaces each occurence with a given replacement // string. The substr argument must be a valid regular expression. var RegexReplaceFunc = function.New(&function.Spec{ Description: `Applies the given regular expression pattern to the given string and replaces all matches with the given replacement string.`, Params: []function.Parameter{ { Name: "str", Type: cty.String, }, { Name: "pattern", Type: cty.String, }, { Name: "replace", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { str := args[0].AsString() substr := args[1].AsString() replace := args[2].AsString() re, err := regexp.Compile(substr) if err != nil { return cty.UnknownVal(cty.String), err } return cty.StringVal(re.ReplaceAllString(str, replace)), nil }, }) // Replace searches a given string for another given substring, // and replaces all occurrences with a given replacement string. func Replace(str, substr, replace cty.Value) (cty.Value, error) { return ReplaceFunc.Call([]cty.Value{str, substr, replace}) } func RegexReplace(str, substr, replace cty.Value) (cty.Value, error) { return RegexReplaceFunc.Call([]cty.Value{str, substr, replace}) } go-cty-1.12.1/cty/function/stdlib/string_replace_test.go000066400000000000000000000042211433256746400233070ustar00rootroot00000000000000package stdlib import ( "testing" "github.com/zclconf/go-cty/cty" ) func TestReplace(t *testing.T) { tests := []struct { Input cty.Value Substr, Replace cty.Value Want cty.Value }{ { cty.StringVal("hello"), cty.StringVal("l"), cty.StringVal(""), cty.StringVal("heo"), }, { cty.StringVal("😸😸😸😾😾😾"), cty.StringVal("😾"), cty.StringVal("😸"), cty.StringVal("😸😸😸😸😸😸"), }, { cty.StringVal("😸😸😸😸😸😾"), cty.StringVal("😾"), cty.StringVal("😸"), cty.StringVal("😸😸😸😸😸😸"), }, } for _, test := range tests { t.Run(test.Input.GoString()+"_replace", func(t *testing.T) { got, err := Replace(test.Input, test.Substr, test.Replace) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) t.Run(test.Input.GoString()+"_regex_replace", func(t *testing.T) { got, err := Replace(test.Input, test.Substr, test.Replace) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestRegexReplace(t *testing.T) { tests := []struct { Input cty.Value Substr, Replace cty.Value Want cty.Value }{ { cty.StringVal("-ab-axxb-"), cty.StringVal("a(x*)b"), cty.StringVal("T"), cty.StringVal("-T-T-"), }, { cty.StringVal("-ab-axxb-"), cty.StringVal("a(x*)b"), cty.StringVal("${1}W"), cty.StringVal("-W-xxW-"), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := RegexReplace(test.Input, test.Substr, test.Replace) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestRegexReplaceInvalidRegex(t *testing.T) { _, err := RegexReplace(cty.StringVal(""), cty.StringVal("("), cty.StringVal("")) if err == nil { t.Fatal("expected an error") } } go-cty-1.12.1/cty/function/stdlib/string_test.go000066400000000000000000000233411433256746400216200ustar00rootroot00000000000000package stdlib import ( "testing" "github.com/zclconf/go-cty/cty" ) func TestUpper(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.StringVal("hello"), cty.StringVal("HELLO"), }, { cty.StringVal("HELLO"), cty.StringVal("HELLO"), }, { cty.StringVal(""), cty.StringVal(""), }, { cty.StringVal("1"), cty.StringVal("1"), }, { cty.StringVal("жж"), cty.StringVal("ЖЖ"), }, { cty.StringVal("noël"), cty.StringVal("NOËL"), }, { // Go's case conversions don't handle this ligature, which is // unfortunate but is now a compatibility constraint since it // would be potentially-breaking to behave differently here in // future. cty.StringVal("baffle"), cty.StringVal("BAfflE"), }, { cty.StringVal("😸😾"), cty.StringVal("😸😾"), }, { cty.UnknownVal(cty.String), cty.UnknownVal(cty.String), }, { cty.DynamicVal, cty.UnknownVal(cty.String), }, { cty.StringVal("hello").Mark(1), cty.StringVal("HELLO").Mark(1), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Upper(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestLower(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.StringVal("HELLO"), cty.StringVal("hello"), }, { cty.StringVal("hello"), cty.StringVal("hello"), }, { cty.StringVal(""), cty.StringVal(""), }, { cty.StringVal("1"), cty.StringVal("1"), }, { cty.StringVal("ЖЖ"), cty.StringVal("жж"), }, { cty.UnknownVal(cty.String), cty.UnknownVal(cty.String), }, { cty.DynamicVal, cty.UnknownVal(cty.String), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Lower(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestReverse(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.StringVal("hello"), cty.StringVal("olleh"), }, { cty.StringVal(""), cty.StringVal(""), }, { cty.StringVal("1"), cty.StringVal("1"), }, { cty.StringVal("Живой Журнал"), cty.StringVal("ланруЖ йовиЖ"), }, { // note that the dieresis here is intentionally a combining // ligature. cty.StringVal("noël"), cty.StringVal("lëon"), }, { // The Es in this string has three combining acute accents. // This tests something that NFC-normalization cannot collapse // into a single precombined codepoint, since otherwise we might // be cheating and relying on the single-codepoint forms. cty.StringVal("wé́́é́́é́́!"), cty.StringVal("!é́́é́́é́́w"), }, { // Go's normalization forms don't handle this ligature, so we // will produce the wrong result but this is now a compatibility // constraint and so we'll test it. cty.StringVal("baffle"), cty.StringVal("efflab"), }, { cty.StringVal("😸😾"), cty.StringVal("😾😸"), }, { cty.UnknownVal(cty.String), cty.UnknownVal(cty.String), }, { cty.DynamicVal, cty.UnknownVal(cty.String), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Reverse(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestStrlen(t *testing.T) { tests := []struct { Input cty.Value Want cty.Value }{ { cty.StringVal("hello"), cty.NumberIntVal(5), }, { cty.StringVal(""), cty.NumberIntVal(0), }, { cty.StringVal("1"), cty.NumberIntVal(1), }, { cty.StringVal("Живой Журнал"), cty.NumberIntVal(12), }, { // note that the dieresis here is intentionally a combining // ligature. cty.StringVal("noël"), cty.NumberIntVal(4), }, { // The Es in this string has three combining acute accents. // This tests something that NFC-normalization cannot collapse // into a single precombined codepoint, since otherwise we might // be cheating and relying on the single-codepoint forms. cty.StringVal("wé́́é́́é́́!"), cty.NumberIntVal(5), }, { // Go's normalization forms don't handle this ligature, so we // will produce the wrong result but this is now a compatibility // constraint and so we'll test it. cty.StringVal("baffle"), cty.NumberIntVal(4), }, { cty.StringVal("😸😾"), cty.NumberIntVal(2), }, { cty.UnknownVal(cty.String), cty.UnknownVal(cty.Number), }, { cty.DynamicVal, cty.UnknownVal(cty.Number), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Strlen(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestSubstr(t *testing.T) { tests := []struct { Input cty.Value Offset cty.Value Length cty.Value Want cty.Value }{ { cty.StringVal("hello"), cty.NumberIntVal(0), cty.NumberIntVal(2), cty.StringVal("he"), }, { cty.StringVal("hello"), cty.NumberIntVal(1), cty.NumberIntVal(3), cty.StringVal("ell"), }, { cty.StringVal("hello"), cty.NumberIntVal(1), cty.NumberIntVal(-1), cty.StringVal("ello"), }, { cty.StringVal("hello"), cty.NumberIntVal(1), cty.NumberIntVal(-10), // not documented, but <0 is the same as -1 cty.StringVal("ello"), }, { cty.StringVal("hello"), cty.NumberIntVal(1), cty.NumberIntVal(10), cty.StringVal("ello"), }, { cty.StringVal("hello"), cty.NumberIntVal(-3), cty.NumberIntVal(-1), cty.StringVal("llo"), }, { cty.StringVal("hello"), cty.NumberIntVal(-3), cty.NumberIntVal(2), cty.StringVal("ll"), }, { cty.StringVal("hello"), cty.NumberIntVal(10), cty.NumberIntVal(10), cty.StringVal(""), }, { cty.StringVal("hello"), cty.NumberIntVal(0), cty.NumberIntVal(0), cty.StringVal(""), }, { cty.StringVal("noël"), cty.NumberIntVal(0), cty.NumberIntVal(3), cty.StringVal("noë"), }, { cty.StringVal("noël"), cty.NumberIntVal(3), cty.NumberIntVal(-1), cty.StringVal("l"), }, { cty.StringVal("wé́́é́́é́́!"), cty.NumberIntVal(2), cty.NumberIntVal(2), cty.StringVal("é́́é́́"), }, { cty.StringVal("wé́́é́́é́́!"), cty.NumberIntVal(3), cty.NumberIntVal(2), cty.StringVal("é́́!"), }, { cty.StringVal("wé́́é́́é́́!"), cty.NumberIntVal(-2), cty.NumberIntVal(-1), cty.StringVal("é́́!"), }, { cty.StringVal("noël"), cty.NumberIntVal(-2), cty.NumberIntVal(-1), cty.StringVal("ël"), }, { cty.StringVal("😸😾"), cty.NumberIntVal(0), cty.NumberIntVal(1), cty.StringVal("😸"), }, { cty.StringVal("😸😾"), cty.NumberIntVal(1), cty.NumberIntVal(1), cty.StringVal("😾"), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got, err := Substr(test.Input, test.Offset, test.Length) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestJoin(t *testing.T) { tests := map[string]struct { Separator cty.Value Lists []cty.Value Want cty.Value }{ "single two-element list": { cty.StringVal("-"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}), }, cty.StringVal("hello-world"), }, "multiple single-element lists": { cty.StringVal("-"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("chicken")}), cty.ListVal([]cty.Value{cty.StringVal("egg")}), }, cty.StringVal("chicken-egg"), }, "single single-element list": { cty.StringVal("-"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("chicken")}), }, cty.StringVal("chicken"), }, "blank separator": { cty.StringVal(""), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("horse"), cty.StringVal("face")}), }, cty.StringVal("horseface"), }, "marked list": { cty.StringVal("-"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}).Mark("sensitive"), }, cty.StringVal("hello-world").Mark("sensitive"), }, "marked separator": { cty.StringVal("-").Mark("sensitive"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hello"), cty.StringVal("world")}), }, cty.StringVal("hello-world").Mark("sensitive"), }, "list with some marked elements": { cty.StringVal("-"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hello").Mark("sensitive"), cty.StringVal("world")}), }, cty.StringVal("hello-world").Mark("sensitive"), }, "multiple marks": { cty.StringVal("-").Mark("a"), []cty.Value{ cty.ListVal([]cty.Value{cty.StringVal("hello").Mark("b"), cty.StringVal("world").Mark("c")}), }, cty.StringVal("hello-world").WithMarks(cty.NewValueMarks("a", "b", "c")), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { got, err := Join(test.Separator, test.Lists...) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } go-cty-1.12.1/cty/function/stdlib/testdata/000077500000000000000000000000001433256746400205325ustar00rootroot00000000000000go-cty-1.12.1/cty/function/stdlib/testdata/bare.tmpl000066400000000000000000000000061433256746400223350ustar00rootroot00000000000000${val}go-cty-1.12.1/cty/function/stdlib/testdata/func.tmpl000066400000000000000000000000411433256746400223560ustar00rootroot00000000000000The items are ${join(", ", list)}go-cty-1.12.1/cty/function/stdlib/testdata/hello.tmpl000066400000000000000000000000171433256746400225310ustar00rootroot00000000000000Hello, ${name}!go-cty-1.12.1/cty/function/stdlib/testdata/hello.txt000066400000000000000000000000131433256746400223700ustar00rootroot00000000000000Hello Worldgo-cty-1.12.1/cty/function/stdlib/testdata/icon.png000066400000000000000000000014461433256746400221750ustar00rootroot00000000000000PNG  IHDR(-SgAMA a cHRMz&u0`:pQ<PLTE\N^Q\N\N\O[I\N]OII\O\N@@@@\N\N\RAA??@@ZN\O\NFC@@UU\N\NFD^Q[M\N??^M[L\M@@AA@@ZP\M[O\N@@@@]NGG`P`P\N]M]OYN\N@@>a6tRNS&7w_7Acl{Fk0kݻ, BkybKGDH pHYstIMEMLIDAT}PB D̞yFGs$ SwQL9BȢi(+ѫ>zVv^^`c@0 0 8(L|6_,Wkv-tqFJrMpS_,%tEXtdate:create2017-04-05T18:16:15+02:00U%tEXtdate:modify2017-04-05T18:16:15+02:00stEXtSoftwarewww.inkscape.org< tEXtTitleGroup"WzTXtRaw profile type iptcx qV((OIR# .c #K D4d#T ˀHJ.tB5IENDB`go-cty-1.12.1/cty/function/stdlib/testdata/list.tmpl000066400000000000000000000000501433256746400223760ustar00rootroot00000000000000%{ for x in list ~} - ${x} %{ endfor ~} go-cty-1.12.1/cty/function/stdlib/testdata/recursive.tmpl000066400000000000000000000000451433256746400234360ustar00rootroot00000000000000${templatefile("recursive.tmpl", {})}go-cty-1.12.1/cty/function/unpredictable.go000066400000000000000000000024071433256746400206130ustar00rootroot00000000000000package function import ( "github.com/zclconf/go-cty/cty" ) // Unpredictable wraps a given function such that it retains the same arguments // and type checking behavior but will return an unknown value when called. // // It is recommended that most functions be "pure", which is to say that they // will always produce the same value given particular input. However, // sometimes it is necessary to offer functions whose behavior depends on // some external state, such as reading a file or determining the current time. // In such cases, an unpredictable wrapper might be used to stand in for // the function during some sort of prior "checking" phase in order to delay // the actual effect until later. // // While Unpredictable can support a function that isn't pure in its // implementation, it still expects a function to be pure in its type checking // behavior, except for the special case of returning cty.DynamicPseudoType // if it is not yet able to predict its return value based on current argument // information. func Unpredictable(f Function) Function { newSpec := *f.spec // shallow copy newSpec.Impl = unpredictableImpl return New(&newSpec) } func unpredictableImpl(args []cty.Value, retType cty.Type) (cty.Value, error) { return cty.UnknownVal(retType), nil } go-cty-1.12.1/cty/function/unpredictable_test.go000066400000000000000000000032041433256746400216460ustar00rootroot00000000000000package function import ( "testing" "github.com/zclconf/go-cty/cty" ) func TestUnpredictable(t *testing.T) { f := New(&Spec{ Params: []Parameter{ { Name: "fixed", Type: cty.Bool, }, }, VarParam: &Parameter{ Name: "variadic", Type: cty.String, }, Type: func(args []cty.Value) (cty.Type, error) { if len(args) == 1 { return cty.Bool, nil } else { return cty.String, nil } }, Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { return cty.NullVal(retType), nil }, }) uf := Unpredictable(f) { predVal, err := f.Call([]cty.Value{cty.True}) if err != nil { t.Fatal(err) } if !predVal.RawEquals(cty.NullVal(cty.Bool)) { t.Fatal("wrong predictable result") } } t.Run("argument type error", func(t *testing.T) { _, err := uf.Call([]cty.Value{cty.StringVal("hello")}) if err == nil { t.Fatal("call successful; want error") } }) t.Run("type check 1", func(t *testing.T) { ty, err := uf.ReturnTypeForValues([]cty.Value{cty.True}) if err != nil { t.Fatal(err) } if !ty.Equals(cty.Bool) { t.Errorf("wrong type %#v; want %#v", ty, cty.Bool) } }) t.Run("type check 2", func(t *testing.T) { ty, err := uf.ReturnTypeForValues([]cty.Value{cty.True, cty.StringVal("hello")}) if err != nil { t.Fatal(err) } if !ty.Equals(cty.String) { t.Errorf("wrong type %#v; want %#v", ty, cty.String) } }) t.Run("call", func(t *testing.T) { v, err := uf.Call([]cty.Value{cty.True}) if err != nil { t.Fatal(err) } if !v.RawEquals(cty.UnknownVal(cty.Bool)) { t.Errorf("wrong result %#v; want %#v", v, cty.UnknownVal(cty.Bool)) } }) } go-cty-1.12.1/cty/gocty/000077500000000000000000000000001433256746400147405ustar00rootroot00000000000000go-cty-1.12.1/cty/gocty/doc.go000066400000000000000000000004621433256746400160360ustar00rootroot00000000000000// Package gocty deals with converting between cty Values and native go // values. // // It operates under a similar principle to the encoding/json and // encoding/xml packages in the standard library, using reflection to // populate native Go data structures from cty values and vice-versa. package gocty go-cty-1.12.1/cty/gocty/helpers.go000066400000000000000000000020431433256746400167300ustar00rootroot00000000000000package gocty import ( "math/big" "reflect" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/set" ) var valueType = reflect.TypeOf(cty.Value{}) var typeType = reflect.TypeOf(cty.Type{}) var setType = reflect.TypeOf(set.Set[interface{}]{}) var bigFloatType = reflect.TypeOf(big.Float{}) var bigIntType = reflect.TypeOf(big.Int{}) var emptyInterfaceType = reflect.TypeOf(interface{}(nil)) var stringType = reflect.TypeOf("") // structTagIndices interrogates the fields of the given type (which must // be a struct type, or we'll panic) and returns a map from the cty // attribute names declared via struct tags to the indices of the // fields holding those tags. // // This function will panic if two fields within the struct are tagged with // the same cty attribute name. func structTagIndices(st reflect.Type) map[string]int { ct := st.NumField() ret := make(map[string]int, ct) for i := 0; i < ct; i++ { field := st.Field(i) attrName := field.Tag.Get("cty") if attrName != "" { ret[attrName] = i } } return ret } go-cty-1.12.1/cty/gocty/in.go000066400000000000000000000343431433256746400157040ustar00rootroot00000000000000package gocty import ( "math/big" "reflect" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/set" ) // ToCtyValue produces a cty.Value from a Go value. The result will conform // to the given type, or an error will be returned if this is not possible. // // The target type serves as a hint to resolve ambiguities in the mapping. // For example, the Go type set.Set tells us that the value is a set but // does not describe the set's element type. This also allows for convenient // conversions, such as populating a set from a slice rather than having to // first explicitly instantiate a set.Set. // // The audience of this function is assumed to be the developers of Go code // that is integrating with cty, and thus the error messages it returns are // presented from Go's perspective. These messages are thus not appropriate // for display to end-users. An error returned from ToCtyValue represents a // bug in the calling program, not user error. func ToCtyValue(val interface{}, ty cty.Type) (cty.Value, error) { // 'path' starts off as empty but will grow for each level of recursive // call we make, so by the time toCtyValue returns it is likely to have // unused capacity on the end of it, depending on how deeply-recursive // the given Type is. path := make(cty.Path, 0) return toCtyValue(reflect.ValueOf(val), ty, path) } func toCtyValue(val reflect.Value, ty cty.Type, path cty.Path) (cty.Value, error) { if val != (reflect.Value{}) && val.Type().AssignableTo(valueType) { // If the source value is a cty.Value then we'll try to just pass // through to the target type directly. return toCtyPassthrough(val, ty, path) } switch ty { case cty.Bool: return toCtyBool(val, path) case cty.Number: return toCtyNumber(val, path) case cty.String: return toCtyString(val, path) case cty.DynamicPseudoType: return toCtyDynamic(val, path) } switch { case ty.IsListType(): return toCtyList(val, ty.ElementType(), path) case ty.IsMapType(): return toCtyMap(val, ty.ElementType(), path) case ty.IsSetType(): return toCtySet(val, ty.ElementType(), path) case ty.IsObjectType(): return toCtyObject(val, ty.AttributeTypes(), path) case ty.IsTupleType(): return toCtyTuple(val, ty.TupleElementTypes(), path) case ty.IsCapsuleType(): return toCtyCapsule(val, ty, path) } // We should never fall out here return cty.NilVal, path.NewErrorf("unsupported target type %#v", ty) } func toCtyBool(val reflect.Value, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Bool), nil } switch val.Kind() { case reflect.Bool: return cty.BoolVal(val.Bool()), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to bool", val.Kind()) } } func toCtyNumber(val reflect.Value, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Number), nil } switch val.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return cty.NumberIntVal(val.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return cty.NumberUIntVal(val.Uint()), nil case reflect.Float32, reflect.Float64: return cty.NumberFloatVal(val.Float()), nil case reflect.Struct: if val.Type().AssignableTo(bigIntType) { bigInt := val.Interface().(big.Int) bigFloat := (&big.Float{}).SetInt(&bigInt) val = reflect.ValueOf(*bigFloat) } if val.Type().AssignableTo(bigFloatType) { bigFloat := val.Interface().(big.Float) return cty.NumberVal(&bigFloat), nil } fallthrough default: return cty.NilVal, path.NewErrorf("can't convert Go %s to number", val.Kind()) } } func toCtyString(val reflect.Value, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.String), nil } switch val.Kind() { case reflect.String: return cty.StringVal(val.String()), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to string", val.Kind()) } } func toCtyList(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.List(ety)), nil } switch val.Kind() { case reflect.Slice: if val.IsNil() { return cty.NullVal(cty.List(ety)), nil } fallthrough case reflect.Array: if val.Len() == 0 { return cty.ListValEmpty(ety), nil } // While we work on our elements we'll temporarily grow // path to give us a place to put our index step. path = append(path, cty.PathStep(nil)) vals := make([]cty.Value, val.Len()) for i := range vals { var err error path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } vals[i], err = toCtyValue(val.Index(i), ety, path) if err != nil { return cty.NilVal, err } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.ListVal(vals), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.List(ety)) } } func toCtyMap(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Map(ety)), nil } switch val.Kind() { case reflect.Map: if val.IsNil() { return cty.NullVal(cty.Map(ety)), nil } if val.Len() == 0 { return cty.MapValEmpty(ety), nil } keyType := val.Type().Key() if keyType.Kind() != reflect.String { return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType) } // While we work on our elements we'll temporarily grow // path to give us a place to put our index step. path = append(path, cty.PathStep(nil)) vals := make(map[string]cty.Value, val.Len()) for _, kv := range val.MapKeys() { k := kv.String() var err error path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(k), } vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), ety, path) if err != nil { return cty.NilVal, err } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.MapVal(vals), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Map(ety)) } } func toCtySet(val reflect.Value, ety cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Set(ety)), nil } var vals []cty.Value switch val.Kind() { case reflect.Slice: if val.IsNil() { return cty.NullVal(cty.Set(ety)), nil } fallthrough case reflect.Array: if val.Len() == 0 { return cty.SetValEmpty(ety), nil } vals = make([]cty.Value, val.Len()) for i := range vals { var err error vals[i], err = toCtyValue(val.Index(i), ety, path) if err != nil { return cty.NilVal, err } } case reflect.Struct: if !val.Type().AssignableTo(setType) { return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Type(), cty.Set(ety)) } rawSet := val.Interface().(set.Set[interface{}]) inVals := rawSet.Values() if len(inVals) == 0 { return cty.SetValEmpty(ety), nil } vals = make([]cty.Value, len(inVals)) for i := range inVals { var err error vals[i], err = toCtyValue(reflect.ValueOf(inVals[i]), ety, path) if err != nil { return cty.NilVal, err } } default: return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Set(ety)) } return cty.SetVal(vals), nil } func toCtyObject(val reflect.Value, attrTypes map[string]cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Object(attrTypes)), nil } switch val.Kind() { case reflect.Map: if val.IsNil() { return cty.NullVal(cty.Object(attrTypes)), nil } keyType := val.Type().Key() if keyType.Kind() != reflect.String { return cty.NilVal, path.NewErrorf("can't convert Go map with key type %s; key type must be string", keyType) } if len(attrTypes) == 0 { return cty.EmptyObjectVal, nil } // While we work on our elements we'll temporarily grow // path to give us a place to put our GetAttr step. path = append(path, cty.PathStep(nil)) haveKeys := make(map[string]struct{}, val.Len()) for _, kv := range val.MapKeys() { haveKeys[kv.String()] = struct{}{} } vals := make(map[string]cty.Value, len(attrTypes)) for k, at := range attrTypes { var err error path[len(path)-1] = cty.GetAttrStep{ Name: k, } if _, have := haveKeys[k]; !have { vals[k] = cty.NullVal(at) continue } vals[k], err = toCtyValue(val.MapIndex(reflect.ValueOf(k)), at, path) if err != nil { return cty.NilVal, err } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.ObjectVal(vals), nil case reflect.Struct: if len(attrTypes) == 0 { return cty.EmptyObjectVal, nil } // While we work on our elements we'll temporarily grow // path to give us a place to put our GetAttr step. path = append(path, cty.PathStep(nil)) attrFields := structTagIndices(val.Type()) vals := make(map[string]cty.Value, len(attrTypes)) for k, at := range attrTypes { path[len(path)-1] = cty.GetAttrStep{ Name: k, } if fieldIdx, have := attrFields[k]; have { var err error vals[k], err = toCtyValue(val.Field(fieldIdx), at, path) if err != nil { return cty.NilVal, err } } else { vals[k] = cty.NullVal(at) } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.ObjectVal(vals), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Object(attrTypes)) } } func toCtyTuple(val reflect.Value, elemTypes []cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.Tuple(elemTypes)), nil } switch val.Kind() { case reflect.Slice: if val.IsNil() { return cty.NullVal(cty.Tuple(elemTypes)), nil } if val.Len() != len(elemTypes) { return cty.NilVal, path.NewErrorf("wrong number of elements %d; need %d", val.Len(), len(elemTypes)) } if len(elemTypes) == 0 { return cty.EmptyTupleVal, nil } // While we work on our elements we'll temporarily grow // path to give us a place to put our Index step. path = append(path, cty.PathStep(nil)) vals := make([]cty.Value, len(elemTypes)) for i, ety := range elemTypes { var err error path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } vals[i], err = toCtyValue(val.Index(i), ety, path) if err != nil { return cty.NilVal, err } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.TupleVal(vals), nil case reflect.Struct: fieldCount := val.Type().NumField() if fieldCount != len(elemTypes) { return cty.NilVal, path.NewErrorf("wrong number of struct fields %d; need %d", fieldCount, len(elemTypes)) } if len(elemTypes) == 0 { return cty.EmptyTupleVal, nil } // While we work on our elements we'll temporarily grow // path to give us a place to put our Index step. path = append(path, cty.PathStep(nil)) vals := make([]cty.Value, len(elemTypes)) for i, ety := range elemTypes { var err error path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } vals[i], err = toCtyValue(val.Field(i), ety, path) if err != nil { return cty.NilVal, err } } // Discard our extra path segment, retaining it as extra capacity // for future appending to the path. path = path[:len(path)-1] return cty.TupleVal(vals), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s to %#v", val.Kind(), cty.Tuple(elemTypes)) } } func toCtyCapsule(val reflect.Value, capsuleType cty.Type, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(capsuleType), nil } if val.Kind() != reflect.Ptr { if !val.CanAddr() { return cty.NilVal, path.NewErrorf("source value for capsule %#v must be addressable", capsuleType) } val = val.Addr() } if !val.Type().Elem().AssignableTo(capsuleType.EncapsulatedType()) { return cty.NilVal, path.NewErrorf("value of type %T not compatible with capsule %#v", val.Interface(), capsuleType) } return cty.CapsuleVal(capsuleType, val.Interface()), nil } func toCtyDynamic(val reflect.Value, path cty.Path) (cty.Value, error) { if val = toCtyUnwrapPointer(val); !val.IsValid() { return cty.NullVal(cty.DynamicPseudoType), nil } switch val.Kind() { case reflect.Struct: if !val.Type().AssignableTo(valueType) { return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Type()) } return val.Interface().(cty.Value), nil default: return cty.NilVal, path.NewErrorf("can't convert Go %s dynamically; only cty.Value allowed", val.Kind()) } } func toCtyPassthrough(wrappedVal reflect.Value, wantTy cty.Type, path cty.Path) (cty.Value, error) { if wrappedVal = toCtyUnwrapPointer(wrappedVal); !wrappedVal.IsValid() { return cty.NullVal(wantTy), nil } givenVal := wrappedVal.Interface().(cty.Value) val, err := convert.Convert(givenVal, wantTy) if err != nil { return cty.NilVal, path.NewErrorf("unsuitable value: %s", err) } return val, nil } // toCtyUnwrapPointer is a helper for dealing with Go pointers. It has three // possible outcomes: // // - Given value isn't a pointer, so it's just returned as-is. // - Given value is a non-nil pointer, in which case it is dereferenced // and the result returned. // - Given value is a nil pointer, in which case an invalid value is returned. // // For nested pointer types, like **int, they are all dereferenced in turn // until a non-pointer value is found, or until a nil pointer is encountered. func toCtyUnwrapPointer(val reflect.Value) reflect.Value { for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { if val.IsNil() { return reflect.Value{} } val = val.Elem() } return val } go-cty-1.12.1/cty/gocty/in_test.go000066400000000000000000000235131433256746400167400ustar00rootroot00000000000000package gocty import ( "fmt" "math/big" "reflect" "testing" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/set" ) func TestIn(t *testing.T) { capsuleANative := &capsuleType1Native{"capsuleA"} tests := []struct { GoValue interface{} Type cty.Type Want cty.Value }{ // Bool { GoValue: true, Type: cty.Bool, Want: cty.True, }, { GoValue: (*bool)(nil), Type: cty.Bool, Want: cty.NullVal(cty.Bool), }, { GoValue: ptrToBool(true), Type: cty.Bool, Want: cty.True, }, // String { GoValue: "hello", Type: cty.String, Want: cty.StringVal("hello"), }, { GoValue: ptrToString("hello"), Type: cty.String, Want: cty.StringVal("hello"), }, { GoValue: ptrToPtrToString("hello"), Type: cty.String, Want: cty.StringVal("hello"), }, { GoValue: (*string)(nil), Type: cty.String, Want: cty.NullVal(cty.String), }, { GoValue: nil, // any nil is convertable to a null of any type Type: cty.String, Want: cty.NullVal(cty.String), }, { GoValue: (*bool)(nil), // any nil is convertable to a null of any type Type: cty.String, Want: cty.NullVal(cty.String), }, // Number { GoValue: int(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: int8(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: int16(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: int32(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: int64(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: uint(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: uint8(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: uint16(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: uint32(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: uint64(1), Type: cty.Number, Want: cty.NumberIntVal(1), }, { GoValue: float32(1.5), Type: cty.Number, Want: cty.NumberFloatVal(1.5), }, { GoValue: float64(1.5), Type: cty.Number, Want: cty.NumberFloatVal(1.5), }, { GoValue: big.NewFloat(1.5), Type: cty.Number, Want: cty.NumberFloatVal(1.5), }, { GoValue: big.NewInt(5), Type: cty.Number, Want: cty.NumberIntVal(5), }, { GoValue: (*int)(nil), Type: cty.Number, Want: cty.NullVal(cty.Number), }, // Lists { GoValue: []int{}, Type: cty.List(cty.Number), Want: cty.ListValEmpty(cty.Number), }, { GoValue: []int{1, 2}, Type: cty.List(cty.Number), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: &[]int{1, 2}, Type: cty.List(cty.Number), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: []int(nil), Type: cty.List(cty.Number), Want: cty.NullVal(cty.List(cty.Number)), }, { GoValue: (*[]int)(nil), Type: cty.List(cty.Number), Want: cty.NullVal(cty.List(cty.Number)), }, { GoValue: [2]int{1, 2}, Type: cty.List(cty.Number), Want: cty.ListVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: [0]int{}, Type: cty.List(cty.Number), Want: cty.ListValEmpty(cty.Number), }, { GoValue: []int{}, Type: cty.Set(cty.Number), Want: cty.SetValEmpty(cty.Number), }, // Sets { GoValue: []int{1, 2}, Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: []int{2, 2}, Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{ cty.NumberIntVal(2), }), }, { GoValue: &[]int{1, 2}, Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: []int(nil), Type: cty.Set(cty.Number), Want: cty.NullVal(cty.Set(cty.Number)), }, { GoValue: (*[]int)(nil), Type: cty.Set(cty.Number), Want: cty.NullVal(cty.Set(cty.Number)), }, { GoValue: [2]int{1, 2}, Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, { GoValue: [0]int{}, Type: cty.Set(cty.Number), Want: cty.SetValEmpty(cty.Number), }, { GoValue: set.NewSet(set.Rules[interface{}](&testSetRules{})), Type: cty.Set(cty.Number), Want: cty.SetValEmpty(cty.Number), }, { GoValue: set.NewSetFromSlice(set.Rules[interface{}](&testSetRules{}), []interface{}{1, 2}), Type: cty.Set(cty.Number), Want: cty.SetVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), }), }, // Maps { GoValue: map[string]int{}, Type: cty.Map(cty.Number), Want: cty.MapValEmpty(cty.Number), }, { GoValue: map[string]int{"one": 1, "two": 2}, Type: cty.Map(cty.Number), Want: cty.MapVal(map[string]cty.Value{ "one": cty.NumberIntVal(1), "two": cty.NumberIntVal(2), }), }, // Objects { GoValue: struct{}{}, Type: cty.EmptyObject, Want: cty.EmptyObjectVal, }, { GoValue: struct{ Ignored int }{1}, Type: cty.EmptyObject, Want: cty.EmptyObjectVal, }, { GoValue: struct{}{}, Type: cty.Object(map[string]cty.Type{ "name": cty.String, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.NullVal(cty.String), }), }, { GoValue: struct { Name string `cty:"name"` Number int `cty:"number"` }{"Steven", 1}, Type: cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Steven"), "number": cty.NumberIntVal(1), }), }, { GoValue: struct { Name string `cty:"name"` Number int }{"Steven", 1}, Type: cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Steven"), "number": cty.NullVal(cty.Number), }), }, { GoValue: map[string]interface{}{ "name": "Steven", "number": 1, }, Type: cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Steven"), "number": cty.NumberIntVal(1), }), }, { GoValue: map[string]interface{}{ "number": 1, }, Type: cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), Want: cty.ObjectVal(map[string]cty.Value{ "name": cty.NullVal(cty.String), "number": cty.NumberIntVal(1), }), }, // Tuples { GoValue: []interface{}{}, Type: cty.EmptyTuple, Want: cty.EmptyTupleVal, }, { GoValue: struct{}{}, Type: cty.EmptyTuple, Want: cty.EmptyTupleVal, }, { GoValue: testTupleStruct{"Stephen", 23}, Type: cty.Tuple([]cty.Type{cty.String, cty.Number}), Want: cty.TupleVal([]cty.Value{ cty.StringVal("Stephen"), cty.NumberIntVal(23), }), }, { GoValue: []interface{}{1, 2, 3}, Type: cty.Tuple([]cty.Type{ cty.Number, cty.Number, cty.Number, }), Want: cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.NumberIntVal(2), cty.NumberIntVal(3), }), }, { GoValue: []interface{}{1, "hello", 3}, Type: cty.Tuple([]cty.Type{ cty.Number, cty.String, cty.Number, }), Want: cty.TupleVal([]cty.Value{ cty.NumberIntVal(1), cty.StringVal("hello"), cty.NumberIntVal(3), }), }, { GoValue: []interface{}(nil), Type: cty.Tuple([]cty.Type{cty.Number}), Want: cty.NullVal(cty.Tuple([]cty.Type{cty.Number})), }, // Capsules { GoValue: capsuleANative, Type: capsuleType1, Want: cty.CapsuleVal(capsuleType1, capsuleANative), }, // Dynamic { GoValue: cty.NumberIntVal(2), Type: cty.DynamicPseudoType, Want: cty.NumberIntVal(2), }, { GoValue: []cty.Value{cty.NumberIntVal(2)}, Type: cty.List(cty.DynamicPseudoType), Want: cty.ListVal([]cty.Value{cty.NumberIntVal(2)}), }, { GoValue: map[string]cty.Value{"number": cty.NumberIntVal(2)}, Type: cty.Map(cty.DynamicPseudoType), Want: cty.MapVal(map[string]cty.Value{"number": cty.NumberIntVal(2)}), }, // Passthrough { GoValue: cty.NumberIntVal(2), Type: cty.Number, Want: cty.NumberIntVal(2), }, { GoValue: cty.StringVal("hi"), Type: cty.String, Want: cty.StringVal("hi"), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v into %#v", test.GoValue, test.Type), func(t *testing.T) { got, err := ToCtyValue(test.GoValue, test.Type) if err != nil { t.Fatalf("ToCtyValue returned error: %s", err) } if got == cty.NilVal { t.Fatalf("ToCtyValue returned NilVal with no error") } if !got.RawEquals(test.Want) { t.Errorf("wrong result\ninput: %#v\ntarget type: %#v\ngot: %#v\nwant: %#v", test.GoValue, test.Type, got, test.Want) } }) } } func ptrToBool(val bool) *bool { return &val } func ptrToString(val string) *string { return &val } func ptrToInt(val int) *int { return &val } func ptrToPtrToString(val string) **string { pval := &val return &pval } type testSetRules struct{} func (r testSetRules) Hash(v interface{}) int { return v.(int) } func (r testSetRules) Equivalent(v1 interface{}, v2 interface{}) bool { return v1 == v2 } func (r testSetRules) SameRules(other set.Rules[interface{}]) bool { return r == other } type capsuleType1Native struct { name string } var capsuleType1 = cty.Capsule("capsule type 1", reflect.TypeOf(capsuleType1Native{})) go-cty-1.12.1/cty/gocty/out.go000066400000000000000000000420201433256746400160740ustar00rootroot00000000000000package gocty import ( "math" "math/big" "reflect" "github.com/zclconf/go-cty/cty" ) // FromCtyValue assigns a cty.Value to a reflect.Value, which must be a pointer, // using a fixed set of conversion rules. // // This function considers its audience to be the creator of the cty Value // given, and thus the error messages it generates are (unlike with ToCtyValue) // presented in cty terminology that is generally appropriate to return to // end-users in applications where cty data structures are built from // user-provided configuration. In particular this means that if incorrect // target types are provided by the calling application the resulting error // messages are likely to be confusing, since we assume that the given target // type is correct and the cty.Value is where the error lies. // // If an error is returned, the target data structure may have been partially // populated, but the degree to which this is true is an implementation // detail that the calling application should not rely on. // // The function will panic if given a non-pointer as the Go value target, // since that is considered to be a bug in the calling program. func FromCtyValue(val cty.Value, target interface{}) error { tVal := reflect.ValueOf(target) if tVal.Kind() != reflect.Ptr { panic("target value is not a pointer") } if tVal.IsNil() { panic("target value is nil pointer") } // 'path' starts off as empty but will grow for each level of recursive // call we make, so by the time fromCtyValue returns it is likely to have // unused capacity on the end of it, depending on how deeply-recursive // the given cty.Value is. path := make(cty.Path, 0) return fromCtyValue(val, tVal, path) } func fromCtyValue(val cty.Value, target reflect.Value, path cty.Path) error { ty := val.Type() deepTarget := fromCtyPopulatePtr(target, false) // If we're decoding into a cty.Value then we just pass through the // value as-is, to enable partial decoding. This is the only situation // where unknown values are permitted. if deepTarget.Kind() == reflect.Struct && deepTarget.Type().AssignableTo(valueType) { deepTarget.Set(reflect.ValueOf(val)) return nil } // Lists and maps can be nil without indirection, but everything else // requires a pointer and we set it immediately to nil. // We also make an exception for capsule types because we want to handle // pointers specially for these. // (fromCtyList and fromCtyMap must therefore deal with val.IsNull, while // other types can assume no nulls after this point.) if val.IsNull() && !val.Type().IsListType() && !val.Type().IsMapType() && !val.Type().IsCapsuleType() { target = fromCtyPopulatePtr(target, true) if target.Kind() != reflect.Ptr { return path.NewErrorf("null value is not allowed") } target.Set(reflect.Zero(target.Type())) return nil } target = deepTarget if !val.IsKnown() { return path.NewErrorf("value must be known") } switch ty { case cty.Bool: return fromCtyBool(val, target, path) case cty.Number: return fromCtyNumber(val, target, path) case cty.String: return fromCtyString(val, target, path) } switch { case ty.IsListType(): return fromCtyList(val, target, path) case ty.IsMapType(): return fromCtyMap(val, target, path) case ty.IsSetType(): return fromCtySet(val, target, path) case ty.IsObjectType(): return fromCtyObject(val, target, path) case ty.IsTupleType(): return fromCtyTuple(val, target, path) case ty.IsCapsuleType(): return fromCtyCapsule(val, target, path) } // We should never fall out here; reaching here indicates a bug in this // function. return path.NewErrorf("unsupported source type %#v", ty) } func fromCtyBool(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Bool: target.SetBool(val.True()) return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyNumber(val cty.Value, target reflect.Value, path cty.Path) error { bf := val.AsBigFloat() switch target.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return fromCtyNumberInt(bf, target, path) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return fromCtyNumberUInt(bf, target, path) case reflect.Float32, reflect.Float64: return fromCtyNumberFloat(bf, target, path) case reflect.Struct: return fromCtyNumberBig(bf, target, path) default: return likelyRequiredTypesError(path, target) } } func fromCtyNumberInt(bf *big.Float, target reflect.Value, path cty.Path) error { // Doing this with switch rather than << arithmetic because << with // result >32-bits is not portable to 32-bit systems. var min int64 var max int64 switch target.Type().Bits() { case 8: min = math.MinInt8 max = math.MaxInt8 case 16: min = math.MinInt16 max = math.MaxInt16 case 32: min = math.MinInt32 max = math.MaxInt32 case 64: min = math.MinInt64 max = math.MaxInt64 default: panic("weird number of bits in target int") } iv, accuracy := bf.Int64() if accuracy != big.Exact || iv < min || iv > max { return path.NewErrorf("value must be a whole number, between %d and %d", min, max) } target.SetInt(iv) return nil } func fromCtyNumberUInt(bf *big.Float, target reflect.Value, path cty.Path) error { // Doing this with switch rather than << arithmetic because << with // result >32-bits is not portable to 32-bit systems. var max uint64 switch target.Type().Bits() { case 8: max = math.MaxUint8 case 16: max = math.MaxUint16 case 32: max = math.MaxUint32 case 64: max = math.MaxUint64 default: panic("weird number of bits in target uint") } iv, accuracy := bf.Uint64() if accuracy != big.Exact || iv > max { return path.NewErrorf("value must be a whole number, between 0 and %d inclusive", max) } target.SetUint(iv) return nil } func fromCtyNumberFloat(bf *big.Float, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Float32, reflect.Float64: fv, accuracy := bf.Float64() if accuracy != big.Exact { // We allow the precision to be truncated as part of our conversion, // but we don't want to silently introduce infinities. if math.IsInf(fv, 0) { return path.NewErrorf("value must be between %f and %f inclusive", -math.MaxFloat64, math.MaxFloat64) } } target.SetFloat(fv) return nil default: panic("unsupported kind of float") } } func fromCtyNumberBig(bf *big.Float, target reflect.Value, path cty.Path) error { switch { case bigFloatType.ConvertibleTo(target.Type()): // Easy! target.Set(reflect.ValueOf(bf).Elem().Convert(target.Type())) return nil case bigIntType.ConvertibleTo(target.Type()): bi, accuracy := bf.Int(nil) if accuracy != big.Exact { return path.NewErrorf("value must be a whole number") } target.Set(reflect.ValueOf(bi).Elem().Convert(target.Type())) return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyString(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.String: target.SetString(val.AsString()) return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyList(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Slice: if val.IsNull() { target.Set(reflect.Zero(target.Type())) return nil } length := val.LengthInt() tv := reflect.MakeSlice(target.Type(), length, length) path = append(path, nil) i := 0 var err error val.ForEachElement(func(key cty.Value, val cty.Value) bool { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } targetElem := tv.Index(i) err = fromCtyValue(val, targetElem, path) if err != nil { return true } i++ return false }) if err != nil { return err } path = path[:len(path)-1] target.Set(tv) return nil case reflect.Array: if val.IsNull() { return path.NewErrorf("null value is not allowed") } length := val.LengthInt() if length != target.Len() { return path.NewErrorf("must be a list of length %d", target.Len()) } path = append(path, nil) i := 0 var err error val.ForEachElement(func(key cty.Value, val cty.Value) bool { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } targetElem := target.Index(i) err = fromCtyValue(val, targetElem, path) if err != nil { return true } i++ return false }) if err != nil { return err } path = path[:len(path)-1] return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyMap(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Map: if val.IsNull() { target.Set(reflect.Zero(target.Type())) return nil } tv := reflect.MakeMap(target.Type()) et := target.Type().Elem() path = append(path, nil) var err error val.ForEachElement(func(key cty.Value, val cty.Value) bool { path[len(path)-1] = cty.IndexStep{ Key: key, } ks := key.AsString() targetElem := reflect.New(et) err = fromCtyValue(val, targetElem, path) tv.SetMapIndex(reflect.ValueOf(ks), targetElem.Elem()) return err != nil }) if err != nil { return err } path = path[:len(path)-1] target.Set(tv) return nil default: return likelyRequiredTypesError(path, target) } } func fromCtySet(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Slice: if val.IsNull() { target.Set(reflect.Zero(target.Type())) return nil } length := val.LengthInt() tv := reflect.MakeSlice(target.Type(), length, length) i := 0 var err error val.ForEachElement(func(key cty.Value, val cty.Value) bool { targetElem := tv.Index(i) err = fromCtyValue(val, targetElem, path) if err != nil { return true } i++ return false }) if err != nil { return err } target.Set(tv) return nil case reflect.Array: if val.IsNull() { return path.NewErrorf("null value is not allowed") } length := val.LengthInt() if length != target.Len() { return path.NewErrorf("must be a set of length %d", target.Len()) } i := 0 var err error val.ForEachElement(func(key cty.Value, val cty.Value) bool { targetElem := target.Index(i) err = fromCtyValue(val, targetElem, path) if err != nil { return true } i++ return false }) if err != nil { return err } return nil // TODO: decode into set.Set instance default: return likelyRequiredTypesError(path, target) } } func fromCtyObject(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Struct: attrTypes := val.Type().AttributeTypes() targetFields := structTagIndices(target.Type()) path = append(path, nil) for k, i := range targetFields { if _, exists := attrTypes[k]; !exists { // If the field in question isn't able to represent nil, // that's an error. fk := target.Field(i).Kind() switch fk { case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface: // okay default: return path.NewErrorf("missing required attribute %q", k) } } } for k := range attrTypes { path[len(path)-1] = cty.GetAttrStep{ Name: k, } fieldIdx, exists := targetFields[k] if !exists { return path.NewErrorf("unsupported attribute %q", k) } ev := val.GetAttr(k) targetField := target.Field(fieldIdx) err := fromCtyValue(ev, targetField, path) if err != nil { return err } } path = path[:len(path)-1] return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyTuple(val cty.Value, target reflect.Value, path cty.Path) error { switch target.Kind() { case reflect.Struct: elemTypes := val.Type().TupleElementTypes() fieldCount := target.Type().NumField() if fieldCount != len(elemTypes) { return path.NewErrorf("a tuple of %d elements is required", fieldCount) } path = append(path, nil) for i := range elemTypes { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } ev := val.Index(cty.NumberIntVal(int64(i))) targetField := target.Field(i) err := fromCtyValue(ev, targetField, path) if err != nil { return err } } path = path[:len(path)-1] return nil default: return likelyRequiredTypesError(path, target) } } func fromCtyCapsule(val cty.Value, target reflect.Value, path cty.Path) error { if target.Kind() == reflect.Ptr { // Walk through indirection until we get to the last pointer, // which we might set to null below. target = fromCtyPopulatePtr(target, true) if val.IsNull() { target.Set(reflect.Zero(target.Type())) return nil } // Since a capsule contains a pointer to an object, we'll preserve // that pointer on the way out and thus allow the caller to recover // the original object, rather than a copy of it. eType := val.Type().EncapsulatedType() if !eType.AssignableTo(target.Elem().Type()) { // Our interface contract promises that we won't expose Go // implementation details in error messages, so we need to keep // this vague. This can only arise if a calling application has // more than one capsule type in play and a user mixes them up. return path.NewErrorf("incorrect type %s", val.Type().FriendlyName()) } target.Set(reflect.ValueOf(val.EncapsulatedValue())) return nil } else { if val.IsNull() { return path.NewErrorf("null value is not allowed") } // If our target isn't a pointer then we will attempt to copy // the encapsulated value into it. eType := val.Type().EncapsulatedType() if !eType.AssignableTo(target.Type()) { // Our interface contract promises that we won't expose Go // implementation details in error messages, so we need to keep // this vague. This can only arise if a calling application has // more than one capsule type in play and a user mixes them up. return path.NewErrorf("incorrect type %s", val.Type().FriendlyName()) } // We know that EncapsulatedValue is always a pointer, so we // can safely call .Elem on its reflect.Value. target.Set(reflect.ValueOf(val.EncapsulatedValue()).Elem()) return nil } } // fromCtyPopulatePtr recognizes when target is a pointer type and allocates // a value to assign to that pointer, which it returns. // // If the given value has multiple levels of indirection, like **int, these // will be processed in turn so that the return value is guaranteed to be // a non-pointer. // // As an exception, if decodingNull is true then the returned value will be // the final level of pointer, if any, so that the caller can assign it // as nil to represent a null value. If the given target value is not a pointer // at all then the returned value will be just the given target, so the caller // must test if the returned value is a pointer before trying to assign nil // to it. func fromCtyPopulatePtr(target reflect.Value, decodingNull bool) reflect.Value { for { if target.Kind() == reflect.Interface && !target.IsNil() { e := target.Elem() if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { target = e } } if target.Kind() != reflect.Ptr { break } // Stop early if we're decodingNull and we've found our last indirection if target.Elem().Kind() != reflect.Ptr && decodingNull && target.CanSet() { break } if target.IsNil() { target.Set(reflect.New(target.Type().Elem())) } target = target.Elem() } return target } // likelyRequiredTypesError returns an error that states which types are // acceptable by making some assumptions about what types we support for // each target Go kind. It's not a precise science but it allows us to return // an error message that is cty-user-oriented rather than Go-oriented. // // Generally these error messages should be a matter of last resort, since // the calling application should be validating user-provided value types // before decoding anyway. func likelyRequiredTypesError(path cty.Path, target reflect.Value) error { switch target.Kind() { case reflect.Bool: return path.NewErrorf("bool value is required") case reflect.String: return path.NewErrorf("string value is required") case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: fallthrough case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: fallthrough case reflect.Float32, reflect.Float64: return path.NewErrorf("number value is required") case reflect.Slice, reflect.Array: return path.NewErrorf("list or set value is required") case reflect.Map: return path.NewErrorf("map or object value is required") case reflect.Struct: switch { case target.Type().AssignableTo(bigFloatType) || target.Type().AssignableTo(bigIntType): return path.NewErrorf("number value is required") case target.Type().AssignableTo(setType): return path.NewErrorf("set or list value is required") default: return path.NewErrorf("object or tuple value is required") } default: // We should avoid getting into this path, since this error // message is rather useless. return path.NewErrorf("incorrect type") } } go-cty-1.12.1/cty/gocty/out_test.go000066400000000000000000000234111433256746400171360ustar00rootroot00000000000000package gocty import ( "fmt" "math/big" "reflect" "testing" "github.com/zclconf/go-cty/cty" ) func TestOut(t *testing.T) { capsuleANative := &capsuleType1Native{"capsuleA"} type ( stringAlias string boolAlias bool intAlias int float32Alias float32 float64Alias float64 bigIntAlias big.Int listIntAlias []int mapIntAlias map[string]int ) tests := []struct { CtyValue cty.Value TargetType reflect.Type Want interface{} }{ // Bool { CtyValue: cty.True, TargetType: reflect.TypeOf(false), Want: true, }, { CtyValue: cty.False, TargetType: reflect.TypeOf(false), Want: false, }, { CtyValue: cty.True, TargetType: reflect.PtrTo(reflect.TypeOf(false)), Want: testOutAssertPtrVal(true), }, { CtyValue: cty.NullVal(cty.Bool), TargetType: reflect.PtrTo(reflect.TypeOf(false)), Want: (*bool)(nil), }, { CtyValue: cty.True, TargetType: reflect.TypeOf((*boolAlias)(nil)).Elem(), Want: boolAlias(true), }, // String { CtyValue: cty.StringVal("hello"), TargetType: reflect.TypeOf(""), Want: "hello", }, { CtyValue: cty.StringVal(""), TargetType: reflect.TypeOf(""), Want: "", }, { CtyValue: cty.StringVal("hello"), TargetType: reflect.PtrTo(reflect.TypeOf("")), Want: testOutAssertPtrVal("hello"), }, { CtyValue: cty.NullVal(cty.String), TargetType: reflect.PtrTo(reflect.TypeOf("")), Want: (*string)(nil), }, { CtyValue: cty.StringVal("hello"), TargetType: reflect.TypeOf((*stringAlias)(nil)).Elem(), Want: stringAlias("hello"), }, // Number { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(int(0)), Want: int(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(int8(0)), Want: int8(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(int16(0)), Want: int16(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(int32(0)), Want: int32(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(int64(0)), Want: int64(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(uint(0)), Want: uint(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(uint8(0)), Want: uint8(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(uint16(0)), Want: uint16(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(uint32(0)), Want: uint32(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf(uint64(0)), Want: uint64(5), }, { CtyValue: cty.NumberFloatVal(1.5), TargetType: reflect.TypeOf(float32(0)), Want: float32(1.5), }, { CtyValue: cty.NumberFloatVal(1.5), TargetType: reflect.TypeOf(float64(0)), Want: float64(1.5), }, { CtyValue: cty.NumberFloatVal(1.5), TargetType: reflect.PtrTo(bigFloatType), Want: big.NewFloat(1.5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.PtrTo(bigIntType), Want: big.NewInt(5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf((*intAlias)(nil)).Elem(), Want: intAlias(5), }, { CtyValue: cty.NumberFloatVal(1.5), TargetType: reflect.TypeOf((*float32Alias)(nil)).Elem(), Want: float32Alias(1.5), }, { CtyValue: cty.NumberFloatVal(1.5), TargetType: reflect.TypeOf((*float64Alias)(nil)).Elem(), Want: float64Alias(1.5), }, { CtyValue: cty.NumberIntVal(5), TargetType: reflect.TypeOf((*bigIntAlias)(nil)), Want: (*bigIntAlias)(big.NewInt(5)), }, // Lists { CtyValue: cty.ListValEmpty(cty.Number), TargetType: reflect.TypeOf(([]int)(nil)), Want: []int{}, }, { CtyValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5)}), TargetType: reflect.TypeOf(([]int)(nil)), Want: []int{1, 5}, }, { CtyValue: cty.NullVal(cty.List(cty.Number)), TargetType: reflect.TypeOf(([]int)(nil)), Want: ([]int)(nil), }, { CtyValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5)}), TargetType: reflect.ArrayOf(2, reflect.TypeOf(0)), Want: [2]int{1, 5}, }, { CtyValue: cty.ListValEmpty(cty.Number), TargetType: reflect.ArrayOf(0, reflect.TypeOf(0)), Want: [0]int{}, }, { CtyValue: cty.ListValEmpty(cty.Number), TargetType: reflect.PtrTo(reflect.ArrayOf(0, reflect.TypeOf(0))), Want: testOutAssertPtrVal([0]int{}), }, { CtyValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5)}), TargetType: reflect.TypeOf((listIntAlias)(nil)), Want: listIntAlias{1, 5}, }, // Maps { CtyValue: cty.MapValEmpty(cty.Number), TargetType: reflect.TypeOf((map[string]int)(nil)), Want: map[string]int{}, }, { CtyValue: cty.MapVal(map[string]cty.Value{ "one": cty.NumberIntVal(1), "five": cty.NumberIntVal(5), }), TargetType: reflect.TypeOf(map[string]int{}), Want: map[string]int{ "one": 1, "five": 5, }, }, { CtyValue: cty.NullVal(cty.Map(cty.Number)), TargetType: reflect.TypeOf((map[string]int)(nil)), Want: (map[string]int)(nil), }, { CtyValue: cty.MapVal(map[string]cty.Value{ "one": cty.NumberIntVal(1), "five": cty.NumberIntVal(5), }), TargetType: reflect.TypeOf(mapIntAlias(nil)), Want: mapIntAlias{ "one": 1, "five": 5, }, }, // Sets { CtyValue: cty.SetValEmpty(cty.Number), TargetType: reflect.TypeOf(([]int)(nil)), Want: []int{}, }, { CtyValue: cty.SetVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5)}), TargetType: reflect.TypeOf(([]int)(nil)), Want: []int{1, 5}, }, { CtyValue: cty.SetVal([]cty.Value{cty.NumberIntVal(1), cty.NumberIntVal(5)}), TargetType: reflect.TypeOf([2]int{}), Want: [2]int{1, 5}, }, // Objects { CtyValue: cty.EmptyObjectVal, TargetType: reflect.TypeOf(struct{}{}), Want: struct{}{}, }, { CtyValue: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Stephen"), }), TargetType: reflect.TypeOf(testStruct{}), Want: testStruct{ Name: "Stephen", }, }, { CtyValue: cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Stephen"), "number": cty.NumberIntVal(12), }), TargetType: reflect.TypeOf(testStruct{}), Want: testStruct{ Name: "Stephen", Number: ptrToInt(12), }, }, // Tuples { CtyValue: cty.EmptyTupleVal, TargetType: reflect.TypeOf(struct{}{}), Want: struct{}{}, }, { CtyValue: cty.TupleVal([]cty.Value{ cty.StringVal("Stephen"), cty.NumberIntVal(5), }), TargetType: reflect.TypeOf(testTupleStruct{}), Want: testTupleStruct{"Stephen", 5}, }, // Capsules { CtyValue: cty.CapsuleVal(capsuleType1, capsuleANative), TargetType: reflect.TypeOf(capsuleType1Native{}), Want: capsuleType1Native{"capsuleA"}, }, { CtyValue: cty.CapsuleVal(capsuleType1, capsuleANative), TargetType: reflect.PtrTo(reflect.TypeOf(capsuleType1Native{})), Want: capsuleANative, // should recover original pointer }, // Passthrough { CtyValue: cty.NumberIntVal(2), TargetType: valueType, Want: cty.NumberIntVal(2), }, { CtyValue: cty.UnknownVal(cty.Bool), TargetType: valueType, Want: cty.UnknownVal(cty.Bool), }, { CtyValue: cty.NullVal(cty.Bool), TargetType: valueType, Want: cty.NullVal(cty.Bool), }, { CtyValue: cty.DynamicVal, TargetType: valueType, Want: cty.DynamicVal, }, { CtyValue: cty.NullVal(cty.DynamicPseudoType), TargetType: valueType, Want: cty.NullVal(cty.DynamicPseudoType), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v into %s", test.CtyValue, test.TargetType), func(t *testing.T) { target := reflect.New(test.TargetType) err := FromCtyValue(test.CtyValue, target.Interface()) if err != nil { t.Fatalf("FromCtyValue returned error: %s", err) } got := target.Elem().Interface() if assertFunc, ok := test.Want.(testOutAssertFunc); ok { assertFunc(test.CtyValue, test.TargetType, got, t) } else if wantV, ok := test.Want.(cty.Value); ok { if gotV, ok := got.(cty.Value); ok { if !gotV.RawEquals(wantV) { testOutWrongResult(test.CtyValue, test.TargetType, got, test.Want, t) } } else { testOutWrongResult(test.CtyValue, test.TargetType, got, test.Want, t) } } else { if !reflect.DeepEqual(got, test.Want) { testOutWrongResult(test.CtyValue, test.TargetType, got, test.Want, t) } } }) } } type testOutAssertFunc func(cty.Value, reflect.Type, interface{}, *testing.T) func testOutAssertPtrVal(want interface{}) testOutAssertFunc { return func(ctyValue cty.Value, targetType reflect.Type, gotPtr interface{}, t *testing.T) { wantVal := reflect.ValueOf(want) gotVal := reflect.ValueOf(gotPtr) if gotVal.Kind() != reflect.Ptr { t.Fatalf("wrong type %s; want pointer to %T", gotVal.Type(), want) } gotVal = gotVal.Elem() want := wantVal.Interface() got := gotVal.Interface() if got != want { testOutWrongResult( ctyValue, targetType, got, want, t, ) } } } func testOutWrongResult(ctyValue cty.Value, targetType reflect.Type, got interface{}, want interface{}, t *testing.T) { t.Errorf("wrong result\ninput: %#v\ntarget type: %s\ngot: %#v\nwant: %#v", ctyValue, targetType, got, want) } type testStruct struct { Name string `cty:"name"` Number *int `cty:"number"` } type testTupleStruct struct { Name string Number int } go-cty-1.12.1/cty/gocty/type_implied.go000066400000000000000000000057621433256746400177650ustar00rootroot00000000000000package gocty import ( "reflect" "github.com/zclconf/go-cty/cty" ) // ImpliedType takes an arbitrary Go value (as an interface{}) and attempts // to find a suitable cty.Type instance that could be used for a conversion // with ToCtyValue. // // This allows -- for simple situations at least -- types to be defined just // once in Go and the cty types derived from the Go types, but in the process // it makes some assumptions that may be undesirable so applications are // encouraged to build their cty types directly if exacting control is // required. // // Not all Go types can be represented as cty types, so an error may be // returned which is usually considered to be a bug in the calling program. // In particular, ImpliedType will never use capsule types in its returned // type, because it cannot know the capsule types supported by the calling // program. func ImpliedType(gv interface{}) (cty.Type, error) { rt := reflect.TypeOf(gv) var path cty.Path return impliedType(rt, path) } func impliedType(rt reflect.Type, path cty.Path) (cty.Type, error) { switch rt.Kind() { case reflect.Ptr: return impliedType(rt.Elem(), path) // Primitive types case reflect.Bool: return cty.Bool, nil case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return cty.Number, nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return cty.Number, nil case reflect.Float32, reflect.Float64: return cty.Number, nil case reflect.String: return cty.String, nil // Collection types case reflect.Slice: path := append(path, cty.IndexStep{Key: cty.UnknownVal(cty.Number)}) ety, err := impliedType(rt.Elem(), path) if err != nil { return cty.NilType, err } return cty.List(ety), nil case reflect.Map: if !stringType.AssignableTo(rt.Key()) { return cty.NilType, path.NewErrorf("no cty.Type for %s (must have string keys)", rt) } path := append(path, cty.IndexStep{Key: cty.UnknownVal(cty.String)}) ety, err := impliedType(rt.Elem(), path) if err != nil { return cty.NilType, err } return cty.Map(ety), nil // Structural types case reflect.Struct: return impliedStructType(rt, path) default: return cty.NilType, path.NewErrorf("no cty.Type for %s", rt) } } func impliedStructType(rt reflect.Type, path cty.Path) (cty.Type, error) { if valueType.AssignableTo(rt) { // Special case: cty.Value represents cty.DynamicPseudoType, for // type conformance checking. return cty.DynamicPseudoType, nil } fieldIdxs := structTagIndices(rt) if len(fieldIdxs) == 0 { return cty.NilType, path.NewErrorf("no cty.Type for %s (no cty field tags)", rt) } atys := make(map[string]cty.Type, len(fieldIdxs)) { // Temporary extension of path for attributes path := append(path, nil) for k, fi := range fieldIdxs { path[len(path)-1] = cty.GetAttrStep{Name: k} ft := rt.Field(fi).Type aty, err := impliedType(ft, path) if err != nil { return cty.NilType, err } atys[k] = aty } } return cty.Object(atys), nil } go-cty-1.12.1/cty/gocty/type_implied_test.go000066400000000000000000000035371433256746400210220ustar00rootroot00000000000000package gocty import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestImpliedType(t *testing.T) { tests := []struct { Input interface{} Want cty.Type }{ // Primitive types { int(0), cty.Number, }, { int8(0), cty.Number, }, { int16(0), cty.Number, }, { int32(0), cty.Number, }, { int64(0), cty.Number, }, { uint(0), cty.Number, }, { uint8(0), cty.Number, }, { uint16(0), cty.Number, }, { uint32(0), cty.Number, }, { uint64(0), cty.Number, }, { float32(0), cty.Number, }, { float64(0), cty.Number, }, { false, cty.Bool, }, { "", cty.String, }, // Collection types { []int(nil), cty.List(cty.Number), }, { [][]int(nil), cty.List(cty.List(cty.Number)), }, { map[string]int(nil), cty.Map(cty.Number), }, { map[string]map[string]int(nil), cty.Map(cty.Map(cty.Number)), }, { map[string][]int(nil), cty.Map(cty.List(cty.Number)), }, // Structs { testStruct{}, cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), }, // Pointers (unwrapped and ignored) { ptrToInt(0), cty.Number, }, { ptrToBool(false), cty.Bool, }, { ptrToString(""), cty.String, }, { &testStruct{}, cty.Object(map[string]cty.Type{ "name": cty.String, "number": cty.Number, }), }, // Dynamic { cty.NilVal, cty.DynamicPseudoType, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v", test.Input), func(t *testing.T) { got, err := ImpliedType(test.Input) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.Equals(test.Want) { t.Fatalf( "wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) } }) } } go-cty-1.12.1/cty/helper.go000066400000000000000000000052361433256746400154270ustar00rootroot00000000000000package cty import ( "fmt" ) // anyUnknown is a helper to easily check if a set of values contains any // unknowns, for operations that short-circuit to return unknown in that case. func anyUnknown(values ...Value) bool { for _, val := range values { if val.v == unknown { return true } } return false } // typeCheck tests whether all of the given values belong to the given type. // If the given types are a mixture of the given type and the dynamic // pseudo-type then a short-circuit dynamic value is returned. If the given // values are all of the correct type but at least one is unknown then // a short-circuit unknown value is returned. If any other types appear then // an error is returned. Otherwise (finally!) the result is nil, nil. func typeCheck(required Type, ret Type, values ...Value) (shortCircuit *Value, err error) { hasDynamic := false hasUnknown := false for i, val := range values { if val.ty == DynamicPseudoType { hasDynamic = true continue } if !val.Type().Equals(required) { return nil, fmt.Errorf( "type mismatch: want %s but value %d is %s", required.FriendlyName(), i, val.ty.FriendlyName(), ) } if val.v == unknown { hasUnknown = true } } if hasDynamic { return &DynamicVal, nil } if hasUnknown { ret := UnknownVal(ret) return &ret, nil } return nil, nil } // mustTypeCheck is a wrapper around typeCheck that immediately panics if // any error is returned. func mustTypeCheck(required Type, ret Type, values ...Value) *Value { shortCircuit, err := typeCheck(required, ret, values...) if err != nil { panic(err) } return shortCircuit } // shortCircuitForceType takes the return value from mustTypeCheck and // replaces it with an unknown of the given type if the original value was // DynamicVal. // // This is useful for operations that are specified to always return a // particular type, since then a dynamic result can safely be "upgrade" to // a strongly-typed unknown, which then allows subsequent operations to // be actually type-checked. // // It is safe to use this only if the operation in question is defined as // returning either a value of the given type or panicking, since we know // then that subsequent operations won't run if the operation panics. // // If the given short-circuit value is *not* DynamicVal then it must be // of the given type, or this function will panic. func forceShortCircuitType(shortCircuit *Value, ty Type) *Value { if shortCircuit == nil { return nil } if shortCircuit.ty == DynamicPseudoType { ret := UnknownVal(ty) return &ret } if !shortCircuit.ty.Equals(ty) { panic("forceShortCircuitType got value of wrong type") } return shortCircuit } go-cty-1.12.1/cty/json.go000066400000000000000000000105111433256746400151110ustar00rootroot00000000000000package cty import ( "bytes" "encoding/json" "fmt" "sort" ) // MarshalJSON is an implementation of json.Marshaler that allows Type // instances to be serialized as JSON. // // All standard types can be serialized, but capsule types cannot since there // is no way to automatically recover the original pointer and capsule types // compare by equality. func (t Type) MarshalJSON() ([]byte, error) { switch impl := t.typeImpl.(type) { case primitiveType: switch impl.Kind { case primitiveTypeBool: return []byte{'"', 'b', 'o', 'o', 'l', '"'}, nil case primitiveTypeNumber: return []byte{'"', 'n', 'u', 'm', 'b', 'e', 'r', '"'}, nil case primitiveTypeString: return []byte{'"', 's', 't', 'r', 'i', 'n', 'g', '"'}, nil default: panic("unknown primitive type kind") } case typeList, typeMap, typeSet: buf := &bytes.Buffer{} etyJSON, err := t.ElementType().MarshalJSON() if err != nil { return nil, err } buf.WriteRune('[') switch impl.(type) { case typeList: buf.WriteString(`"list"`) case typeMap: buf.WriteString(`"map"`) case typeSet: buf.WriteString(`"set"`) } buf.WriteRune(',') buf.Write(etyJSON) buf.WriteRune(']') return buf.Bytes(), nil case typeObject: buf := &bytes.Buffer{} atysJSON, err := json.Marshal(t.AttributeTypes()) if err != nil { return nil, err } buf.WriteString(`["object",`) buf.Write(atysJSON) if optionals := t.OptionalAttributes(); len(optionals) > 0 { buf.WriteByte(',') optionalNames := make([]string, 0, len(optionals)) for k := range optionals { optionalNames = append(optionalNames, k) } sort.Strings(optionalNames) optionalsJSON, err := json.Marshal(optionalNames) if err != nil { return nil, err } buf.Write(optionalsJSON) } buf.WriteRune(']') return buf.Bytes(), nil case typeTuple: buf := &bytes.Buffer{} etysJSON, err := json.Marshal(t.TupleElementTypes()) if err != nil { return nil, err } buf.WriteString(`["tuple",`) buf.Write(etysJSON) buf.WriteRune(']') return buf.Bytes(), nil case pseudoTypeDynamic: return []byte{'"', 'd', 'y', 'n', 'a', 'm', 'i', 'c', '"'}, nil case *capsuleType: return nil, fmt.Errorf("type not allowed: %s", t.FriendlyName()) default: // should never happen panic("unknown type implementation") } } // UnmarshalJSON is the opposite of MarshalJSON. See the documentation of // MarshalJSON for information on the limitations of JSON serialization of // types. func (t *Type) UnmarshalJSON(buf []byte) error { r := bytes.NewReader(buf) dec := json.NewDecoder(r) tok, err := dec.Token() if err != nil { return err } switch v := tok.(type) { case string: switch v { case "bool": *t = Bool case "number": *t = Number case "string": *t = String case "dynamic": *t = DynamicPseudoType default: return fmt.Errorf("invalid primitive type name %q", v) } if dec.More() { return fmt.Errorf("extraneous data after type description") } return nil case json.Delim: if rune(v) != '[' { return fmt.Errorf("invalid complex type description") } tok, err = dec.Token() if err != nil { return err } kind, ok := tok.(string) if !ok { return fmt.Errorf("invalid complex type kind name") } switch kind { case "list": var ety Type err = dec.Decode(&ety) if err != nil { return err } *t = List(ety) case "map": var ety Type err = dec.Decode(&ety) if err != nil { return err } *t = Map(ety) case "set": var ety Type err = dec.Decode(&ety) if err != nil { return err } *t = Set(ety) case "object": var atys map[string]Type err = dec.Decode(&atys) if err != nil { return err } if dec.More() { var optionals []string err = dec.Decode(&optionals) if err != nil { return err } *t = ObjectWithOptionalAttrs(atys, optionals) } else { *t = Object(atys) } case "tuple": var etys []Type err = dec.Decode(&etys) if err != nil { return err } *t = Tuple(etys) default: return fmt.Errorf("invalid complex type kind name") } tok, err = dec.Token() if err != nil { return err } if delim, ok := tok.(json.Delim); !ok || rune(delim) != ']' || dec.More() { return fmt.Errorf("unexpected extra data in type description") } return nil default: return fmt.Errorf("invalid type description") } } go-cty-1.12.1/cty/json/000077500000000000000000000000001433256746400145645ustar00rootroot00000000000000go-cty-1.12.1/cty/json/doc.go000066400000000000000000000011641433256746400156620ustar00rootroot00000000000000// Package json provides functions for serializing cty types and values in // JSON format, and for decoding them again. // // Since the cty type system is a superset of the JSON type system, // round-tripping through JSON is lossy unless type information is provided // both at encoding time and decoding time. Callers of this package are // therefore suggested to define their expected structure as a cty.Type // and pass it in consistently both when encoding and when decoding, though // default (type-lossy) behavior is provided for situations where the precise // representation of the data is not significant. package json go-cty-1.12.1/cty/json/marshal.go000066400000000000000000000104031433256746400165400ustar00rootroot00000000000000package json import ( "bytes" "encoding/json" "sort" "github.com/zclconf/go-cty/cty" ) func marshal(val cty.Value, t cty.Type, path cty.Path, b *bytes.Buffer) error { if val.IsMarked() { return path.NewErrorf("value has marks, so it cannot be serialized as JSON") } // If we're going to decode as DynamicPseudoType then we need to save // dynamic type information to recover the real type. if t == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType { return marshalDynamic(val, path, b) } if val.IsNull() { b.WriteString("null") return nil } if !val.IsKnown() { return path.NewErrorf("value is not known") } // The caller should've guaranteed that the given val is conformant with // the given type t, so we'll proceed under that assumption here. switch { case t.IsPrimitiveType(): switch t { case cty.String: json, err := json.Marshal(val.AsString()) if err != nil { return path.NewErrorf("failed to serialize value: %s", err) } b.Write(json) return nil case cty.Number: if val.RawEquals(cty.PositiveInfinity) || val.RawEquals(cty.NegativeInfinity) { return path.NewErrorf("cannot serialize infinity as JSON") } b.WriteString(val.AsBigFloat().Text('f', -1)) return nil case cty.Bool: if val.True() { b.WriteString("true") } else { b.WriteString("false") } return nil default: panic("unsupported primitive type") } case t.IsListType(), t.IsSetType(): b.WriteRune('[') first := true ety := t.ElementType() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element for it.Next() { if !first { b.WriteRune(',') } ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } err := marshal(ev, ety, path, b) if err != nil { return err } first = false } b.WriteRune(']') return nil case t.IsMapType(): b.WriteRune('{') first := true ety := t.ElementType() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element for it.Next() { if !first { b.WriteRune(',') } ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } var err error err = marshal(ek, ek.Type(), path, b) if err != nil { return err } b.WriteRune(':') err = marshal(ev, ety, path, b) if err != nil { return err } first = false } b.WriteRune('}') return nil case t.IsTupleType(): b.WriteRune('[') etys := t.TupleElementTypes() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element i := 0 for it.Next() { if i > 0 { b.WriteRune(',') } ety := etys[i] ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } err := marshal(ev, ety, path, b) if err != nil { return err } i++ } b.WriteRune(']') return nil case t.IsObjectType(): b.WriteRune('{') atys := t.AttributeTypes() path := append(path, nil) // local override of 'path' with extra element names := make([]string, 0, len(atys)) for k := range atys { names = append(names, k) } sort.Strings(names) for i, k := range names { aty := atys[k] if i > 0 { b.WriteRune(',') } av := val.GetAttr(k) path[len(path)-1] = cty.GetAttrStep{ Name: k, } var err error err = marshal(cty.StringVal(k), cty.String, path, b) if err != nil { return err } b.WriteRune(':') err = marshal(av, aty, path, b) if err != nil { return err } } b.WriteRune('}') return nil case t.IsCapsuleType(): rawVal := val.EncapsulatedValue() jsonVal, err := json.Marshal(rawVal) if err != nil { return path.NewError(err) } b.Write(jsonVal) return nil default: // should never happen return path.NewErrorf("cannot JSON-serialize %s", t.FriendlyName()) } } // marshalDynamic adds an extra wrapping object containing dynamic type // information for the given value. func marshalDynamic(val cty.Value, path cty.Path, b *bytes.Buffer) error { typeJSON, err := MarshalType(val.Type()) if err != nil { return path.NewErrorf("failed to serialize type: %s", err) } b.WriteString(`{"value":`) marshal(val, val.Type(), path, b) b.WriteString(`,"type":`) b.Write(typeJSON) b.WriteRune('}') return nil } go-cty-1.12.1/cty/json/simple.go000066400000000000000000000025661433256746400164150ustar00rootroot00000000000000package json import ( "github.com/zclconf/go-cty/cty" ) // SimpleJSONValue is a wrapper around cty.Value that adds implementations of // json.Marshaler and json.Unmarshaler for simple-but-type-lossy automatic // encoding and decoding of values. // // The couplet Marshal and Unmarshal both take extra type information to // inform the encoding and decoding process so that all of the cty types // can be represented even though JSON's type system is a subset. // // SimpleJSONValue instead takes the approach of discarding the value's type // information and then deriving a new type from the stored structure when // decoding. This results in the same data being returned but not necessarily // with exactly the same type. // // For information on how types are inferred when decoding, see the // documentation of the function ImpliedType. type SimpleJSONValue struct { cty.Value } // MarshalJSON is an implementation of json.Marshaler. See the documentation // of SimpleJSONValue for more information. func (v SimpleJSONValue) MarshalJSON() ([]byte, error) { return Marshal(v.Value, v.Type()) } // UnmarshalJSON is an implementation of json.Unmarshaler. See the // documentation of SimpleJSONValue for more information. func (v *SimpleJSONValue) UnmarshalJSON(buf []byte) error { t, err := ImpliedType(buf) if err != nil { return err } v.Value, err = Unmarshal(buf, t) return err } go-cty-1.12.1/cty/json/simple_test.go000066400000000000000000000041021433256746400174400ustar00rootroot00000000000000package json import ( "encoding/json" "testing" "github.com/zclconf/go-cty/cty" ) func TestSimpleJSONValue(t *testing.T) { tests := []struct { Input cty.Value JSON string Want cty.Value }{ { cty.NumberIntVal(5), `5`, cty.NumberIntVal(5), }, { cty.True, `true`, cty.True, }, { cty.StringVal("hello"), `"hello"`, cty.StringVal("hello"), }, { cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), `["hello",true]`, cty.TupleVal([]cty.Value{cty.StringVal("hello"), cty.True}), }, { cty.ListVal([]cty.Value{cty.False, cty.True}), `[false,true]`, cty.TupleVal([]cty.Value{cty.False, cty.True}), }, { cty.SetVal([]cty.Value{cty.False, cty.True}), `[false,true]`, cty.TupleVal([]cty.Value{cty.False, cty.True}), }, { cty.ObjectVal(map[string]cty.Value{"true": cty.True, "greet": cty.StringVal("hello")}), `{"greet":"hello","true":true}`, cty.ObjectVal(map[string]cty.Value{"true": cty.True, "greet": cty.StringVal("hello")}), }, { cty.MapVal(map[string]cty.Value{"true": cty.True, "false": cty.False}), `{"false":false,"true":true}`, cty.ObjectVal(map[string]cty.Value{"true": cty.True, "false": cty.False}), }, { cty.NullVal(cty.Bool), `null`, cty.NullVal(cty.DynamicPseudoType), // type is lost in the round-trip }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { wrappedInput := SimpleJSONValue{test.Input} buf, err := json.Marshal(wrappedInput) if err != nil { t.Fatalf("unexpected error from json.Marshal: %s", err) } if string(buf) != test.JSON { t.Fatalf( "incorrect JSON\ninput: %#v\ngot: %s\nwant: %s", test.Input, buf, test.JSON, ) } var wrappedOutput SimpleJSONValue err = json.Unmarshal(buf, &wrappedOutput) if err != nil { t.Fatalf("unexpected error from json.Unmarshal: %s", err) } if !wrappedOutput.Value.RawEquals(test.Want) { t.Fatalf( "incorrect result\nJSON: %s\ngot: %#v\nwant: %#v", buf, wrappedOutput.Value, test.Want, ) } }) } } go-cty-1.12.1/cty/json/type.go000066400000000000000000000011121433256746400160670ustar00rootroot00000000000000package json import ( "github.com/zclconf/go-cty/cty" ) // MarshalType returns a JSON serialization of the given type. // // This is just a thin wrapper around t.MarshalJSON, for symmetry with // UnmarshalType. func MarshalType(t cty.Type) ([]byte, error) { return t.MarshalJSON() } // UnmarshalType decodes a JSON serialization of the given type as produced // by either Type.MarshalJSON or MarshalType. // // This is a convenience wrapper around Type.UnmarshalJSON. func UnmarshalType(buf []byte) (cty.Type, error) { var t cty.Type err := t.UnmarshalJSON(buf) return t, err } go-cty-1.12.1/cty/json/type_implied.go000066400000000000000000000071621433256746400176050ustar00rootroot00000000000000package json import ( "bytes" "encoding/json" "fmt" "github.com/zclconf/go-cty/cty" ) // ImpliedType returns the cty Type implied by the structure of the given // JSON-compliant buffer. This function implements the default type mapping // behavior used when decoding arbitrary JSON without explicit cty Type // information. // // The rules are as follows: // // JSON strings, numbers and bools map to their equivalent primitive type in // cty. // // JSON objects map to cty object types, with the attributes defined by the // object keys and the types of their values. // // JSON arrays map to cty tuple types, with the elements defined by the // types of the array members. // // Any nulls are typed as DynamicPseudoType, so callers of this function // must be prepared to deal with this. Callers that do not wish to deal with // dynamic typing should not use this function and should instead describe // their required types explicitly with a cty.Type instance when decoding. // // Any JSON syntax errors will be returned as an error, and the type will // be the invalid value cty.NilType. func ImpliedType(buf []byte) (cty.Type, error) { r := bytes.NewReader(buf) dec := json.NewDecoder(r) dec.UseNumber() ty, err := impliedType(dec) if err != nil { return cty.NilType, err } if dec.More() { return cty.NilType, fmt.Errorf("extraneous data after JSON object") } return ty, nil } func impliedType(dec *json.Decoder) (cty.Type, error) { tok, err := dec.Token() if err != nil { return cty.NilType, err } return impliedTypeForTok(tok, dec) } func impliedTypeForTok(tok json.Token, dec *json.Decoder) (cty.Type, error) { if tok == nil { return cty.DynamicPseudoType, nil } switch ttok := tok.(type) { case bool: return cty.Bool, nil case json.Number: return cty.Number, nil case string: return cty.String, nil case json.Delim: switch rune(ttok) { case '{': return impliedObjectType(dec) case '[': return impliedTupleType(dec) default: return cty.NilType, fmt.Errorf("unexpected token %q", ttok) } default: return cty.NilType, fmt.Errorf("unsupported JSON token %#v", tok) } } func impliedObjectType(dec *json.Decoder) (cty.Type, error) { // By the time we get in here, we've already consumed the { delimiter // and so our next token should be the first object key. var atys map[string]cty.Type for { // Read the object key first tok, err := dec.Token() if err != nil { return cty.NilType, err } if ttok, ok := tok.(json.Delim); ok { if rune(ttok) != '}' { return cty.NilType, fmt.Errorf("unexpected delimiter %q", ttok) } break } key, ok := tok.(string) if !ok { return cty.NilType, fmt.Errorf("expected string but found %T", tok) } // Now read the value tok, err = dec.Token() if err != nil { return cty.NilType, err } aty, err := impliedTypeForTok(tok, dec) if err != nil { return cty.NilType, err } if atys == nil { atys = make(map[string]cty.Type) } atys[key] = aty } if len(atys) == 0 { return cty.EmptyObject, nil } return cty.Object(atys), nil } func impliedTupleType(dec *json.Decoder) (cty.Type, error) { // By the time we get in here, we've already consumed the [ delimiter // and so our next token should be the first value. var etys []cty.Type for { tok, err := dec.Token() if err != nil { return cty.NilType, err } if ttok, ok := tok.(json.Delim); ok { if rune(ttok) == ']' { break } } ety, err := impliedTypeForTok(tok, dec) if err != nil { return cty.NilType, err } etys = append(etys, ety) } if len(etys) == 0 { return cty.EmptyTuple, nil } return cty.Tuple(etys), nil } go-cty-1.12.1/cty/json/type_implied_test.go000066400000000000000000000035051433256746400206410ustar00rootroot00000000000000package json import ( "testing" "github.com/zclconf/go-cty/cty" ) func TestImpliedType(t *testing.T) { tests := []struct { Input string Want cty.Type }{ { "null", cty.DynamicPseudoType, }, { "1", cty.Number, }, { "1.2222222222222222222222222222222222", cty.Number, }, { "999999999999999999999999999999999999999999999999999999999999", cty.Number, }, { `""`, cty.String, }, { `"hello"`, cty.String, }, { "true", cty.Bool, }, { "false", cty.Bool, }, { "{}", cty.EmptyObject, }, { `{"true": true}`, cty.Object(map[string]cty.Type{ "true": cty.Bool, }), }, { `{"true": true, "name": "Ermintrude", "null": null}`, cty.Object(map[string]cty.Type{ "true": cty.Bool, "name": cty.String, "null": cty.DynamicPseudoType, }), }, { "[]", cty.EmptyTuple, }, { "[true, 1.2, null]", cty.Tuple([]cty.Type{cty.Bool, cty.Number, cty.DynamicPseudoType}), }, { `[[true], [1.2], [null]]`, cty.Tuple([]cty.Type{ cty.Tuple([]cty.Type{cty.Bool}), cty.Tuple([]cty.Type{cty.Number}), cty.Tuple([]cty.Type{cty.DynamicPseudoType}), }), }, { `[{"true": true}, {"name": "Ermintrude"}, {"null": null}]`, cty.Tuple([]cty.Type{ cty.Object(map[string]cty.Type{ "true": cty.Bool, }), cty.Object(map[string]cty.Type{ "name": cty.String, }), cty.Object(map[string]cty.Type{ "null": cty.DynamicPseudoType, }), }), }, } for _, test := range tests { t.Run(test.Input, func(t *testing.T) { got, err := ImpliedType([]byte(test.Input)) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.Equals(test.Want) { t.Errorf( "wrong type\ninput: %s\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) } }) } } go-cty-1.12.1/cty/json/unmarshal.go000066400000000000000000000232411433256746400171070ustar00rootroot00000000000000package json import ( "bytes" "encoding/json" "fmt" "reflect" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" ) func unmarshal(buf []byte, t cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) tok, err := dec.Token() if err != nil { return cty.NilVal, path.NewError(err) } if tok == nil { return cty.NullVal(t), nil } if t == cty.DynamicPseudoType { return unmarshalDynamic(buf, path) } switch { case t.IsPrimitiveType(): val, err := unmarshalPrimitive(tok, t, path) if err != nil { return cty.NilVal, err } return val, nil case t.IsListType(): return unmarshalList(buf, t.ElementType(), path) case t.IsSetType(): return unmarshalSet(buf, t.ElementType(), path) case t.IsMapType(): return unmarshalMap(buf, t.ElementType(), path) case t.IsTupleType(): return unmarshalTuple(buf, t.TupleElementTypes(), path) case t.IsObjectType(): return unmarshalObject(buf, t.AttributeTypes(), path) case t.IsCapsuleType(): return unmarshalCapsule(buf, t, path) default: return cty.NilVal, path.NewErrorf("unsupported type %s", t.FriendlyName()) } } func unmarshalPrimitive(tok json.Token, t cty.Type, path cty.Path) (cty.Value, error) { switch t { case cty.Bool: switch v := tok.(type) { case bool: return cty.BoolVal(v), nil case string: val, err := convert.Convert(cty.StringVal(v), t) if err != nil { return cty.NilVal, path.NewError(err) } return val, nil default: return cty.NilVal, path.NewErrorf("bool is required") } case cty.Number: if v, ok := tok.(json.Number); ok { tok = string(v) } switch v := tok.(type) { case string: val, err := cty.ParseNumberVal(v) if err != nil { return cty.NilVal, path.NewError(err) } return val, nil default: return cty.NilVal, path.NewErrorf("number is required") } case cty.String: switch v := tok.(type) { case string: return cty.StringVal(v), nil case json.Number: return cty.StringVal(string(v)), nil case bool: val, err := convert.Convert(cty.BoolVal(v), t) if err != nil { return cty.NilVal, path.NewError(err) } return val, nil default: return cty.NilVal, path.NewErrorf("string is required") } default: // should never happen panic("unsupported primitive type") } } func unmarshalList(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '['); err != nil { return cty.NilVal, path.NewError(err) } var vals []cty.Value { path := append(path, nil) var idx int64 for dec.More() { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(idx), } idx++ rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read list value: %s", err) } el, err := unmarshal(rawVal, ety, path) if err != nil { return cty.NilVal, err } vals = append(vals, el) } } if err := requireDelim(dec, ']'); err != nil { return cty.NilVal, path.NewError(err) } if len(vals) == 0 { return cty.ListValEmpty(ety), nil } return cty.ListVal(vals), nil } func unmarshalSet(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '['); err != nil { return cty.NilVal, path.NewError(err) } var vals []cty.Value { path := append(path, nil) for dec.More() { path[len(path)-1] = cty.IndexStep{ Key: cty.UnknownVal(ety), } rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read set value: %s", err) } el, err := unmarshal(rawVal, ety, path) if err != nil { return cty.NilVal, err } vals = append(vals, el) } } if err := requireDelim(dec, ']'); err != nil { return cty.NilVal, path.NewError(err) } if len(vals) == 0 { return cty.SetValEmpty(ety), nil } return cty.SetVal(vals), nil } func unmarshalMap(buf []byte, ety cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '{'); err != nil { return cty.NilVal, path.NewError(err) } vals := make(map[string]cty.Value) { path := append(path, nil) for dec.More() { path[len(path)-1] = cty.IndexStep{ Key: cty.UnknownVal(cty.String), } var err error k, err := requireObjectKey(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read map key: %s", err) } path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(k), } rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read map value: %s", err) } el, err := unmarshal(rawVal, ety, path) if err != nil { return cty.NilVal, err } vals[k] = el } } if err := requireDelim(dec, '}'); err != nil { return cty.NilVal, path.NewError(err) } if len(vals) == 0 { return cty.MapValEmpty(ety), nil } return cty.MapVal(vals), nil } func unmarshalTuple(buf []byte, etys []cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '['); err != nil { return cty.NilVal, path.NewError(err) } var vals []cty.Value { path := append(path, nil) var idx int for dec.More() { if idx >= len(etys) { return cty.NilVal, path[:len(path)-1].NewErrorf("too many tuple elements (need %d)", len(etys)) } path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(idx)), } ety := etys[idx] idx++ rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read tuple value: %s", err) } el, err := unmarshal(rawVal, ety, path) if err != nil { return cty.NilVal, err } vals = append(vals, el) } } if err := requireDelim(dec, ']'); err != nil { return cty.NilVal, path.NewError(err) } if len(vals) != len(etys) { return cty.NilVal, path[:len(path)-1].NewErrorf("not enough tuple elements (need %d)", len(etys)) } if len(vals) == 0 { return cty.EmptyTupleVal, nil } return cty.TupleVal(vals), nil } func unmarshalObject(buf []byte, atys map[string]cty.Type, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '{'); err != nil { return cty.NilVal, path.NewError(err) } vals := make(map[string]cty.Value) { objPath := path // some errors report from the object's perspective path := append(path, nil) // path to a specific attribute for dec.More() { var err error k, err := requireObjectKey(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read object key: %s", err) } aty, ok := atys[k] if !ok { return cty.NilVal, objPath.NewErrorf("unsupported attribute %q", k) } path[len(path)-1] = cty.GetAttrStep{ Name: k, } rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read object value: %s", err) } el, err := unmarshal(rawVal, aty, path) if err != nil { return cty.NilVal, err } vals[k] = el } } if err := requireDelim(dec, '}'); err != nil { return cty.NilVal, path.NewError(err) } // Make sure we have a value for every attribute for k, aty := range atys { if _, exists := vals[k]; !exists { vals[k] = cty.NullVal(aty) } } if len(vals) == 0 { return cty.EmptyObjectVal, nil } return cty.ObjectVal(vals), nil } func unmarshalCapsule(buf []byte, t cty.Type, path cty.Path) (cty.Value, error) { rawType := t.EncapsulatedType() ptrPtr := reflect.New(reflect.PtrTo(rawType)) ptrPtr.Elem().Set(reflect.New(rawType)) ptr := ptrPtr.Elem().Interface() err := json.Unmarshal(buf, ptr) if err != nil { return cty.NilVal, path.NewError(err) } return cty.CapsuleVal(t, ptr), nil } func unmarshalDynamic(buf []byte, path cty.Path) (cty.Value, error) { dec := bufDecoder(buf) if err := requireDelim(dec, '{'); err != nil { return cty.NilVal, path.NewError(err) } var t cty.Type var valBody []byte // defer actual decoding until we know the type for dec.More() { var err error key, err := requireObjectKey(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read dynamic type descriptor key: %s", err) } rawVal, err := readRawValue(dec) if err != nil { return cty.NilVal, path.NewErrorf("failed to read dynamic type descriptor value: %s", err) } switch key { case "type": err := json.Unmarshal(rawVal, &t) if err != nil { return cty.NilVal, path.NewErrorf("failed to decode type for dynamic value: %s", err) } case "value": valBody = rawVal default: return cty.NilVal, path.NewErrorf("invalid key %q in dynamically-typed value", key) } } if err := requireDelim(dec, '}'); err != nil { return cty.NilVal, path.NewError(err) } if t == cty.NilType { return cty.NilVal, path.NewErrorf("missing type in dynamically-typed value") } if valBody == nil { return cty.NilVal, path.NewErrorf("missing value in dynamically-typed value") } val, err := Unmarshal([]byte(valBody), t) if err != nil { return cty.NilVal, path.NewError(err) } return val, nil } func requireDelim(dec *json.Decoder, d rune) error { tok, err := dec.Token() if err != nil { return err } if tok != json.Delim(d) { return fmt.Errorf("missing expected %c", d) } return nil } func requireObjectKey(dec *json.Decoder) (string, error) { tok, err := dec.Token() if err != nil { return "", err } if s, ok := tok.(string); ok { return s, nil } return "", fmt.Errorf("missing expected object key") } func readRawValue(dec *json.Decoder) ([]byte, error) { var rawVal json.RawMessage err := dec.Decode(&rawVal) if err != nil { return nil, err } return []byte(rawVal), nil } func bufDecoder(buf []byte) *json.Decoder { r := bytes.NewReader(buf) dec := json.NewDecoder(r) dec.UseNumber() return dec } go-cty-1.12.1/cty/json/value.go000066400000000000000000000043051433256746400162310ustar00rootroot00000000000000package json import ( "bytes" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" ) // Marshal produces a JSON representation of the given value that can later // be decoded into a value of the given type. // // A type is specified separately to allow for the given type to include // cty.DynamicPseudoType to represent situations where any type is permitted // and so type information must be included to allow recovery of the stored // structure when decoding. // // The given type will also be used to attempt automatic conversions of any // non-conformant types in the given value, although this will not always // be possible. If the value cannot be made to be conformant then an error is // returned, which may be a cty.PathError. // // Capsule-typed values can be marshalled, but with some caveats. Since // capsule values are compared by pointer equality, it is impossible to recover // a value that will compare equal to the original value. Additionally, // it's not possible to JSON-serialize the capsule type itself, so it's not // valid to use capsule types within parts of the value that are conformed to // cty.DynamicPseudoType. Otherwise, a capsule value can be used as long as // the encapsulated type itself is serializable with the Marshal function // in encoding/json. func Marshal(val cty.Value, t cty.Type) ([]byte, error) { errs := val.Type().TestConformance(t) if errs != nil { // Attempt a conversion var err error val, err = convert.Convert(val, t) if err != nil { return nil, err } } // From this point onward, val can be assumed to be conforming to t. buf := &bytes.Buffer{} var path cty.Path err := marshal(val, t, path, buf) if err != nil { return nil, err } return buf.Bytes(), nil } // Unmarshal decodes a JSON representation of the given value into a cty Value // conforming to the given type. // // While decoding, type conversions will be done where possible to make // the result conformant even if the types given in JSON are not exactly // correct. If conversion isn't possible then an error is returned, which // may be a cty.PathError. func Unmarshal(buf []byte, t cty.Type) (cty.Value, error) { var path cty.Path return unmarshal(buf, t, path) } go-cty-1.12.1/cty/json/value_test.go000066400000000000000000000133711433256746400172730ustar00rootroot00000000000000package json import ( "fmt" "reflect" "testing" "github.com/zclconf/go-cty/cty" ) func TestValueJSONable(t *testing.T) { bytesType := cty.Capsule("bytes", reflect.TypeOf([]byte(nil))) buf := []byte("hello") bytesVal := cty.CapsuleVal(bytesType, &buf) tests := []struct { Value cty.Value Type cty.Type Want string DecVal cty.Value }{ // Primitives { cty.StringVal("hello"), cty.String, `"hello"`, cty.StringVal("hello"), }, { cty.StringVal(""), cty.String, `""`, cty.StringVal(""), }, { cty.StringVal("15"), cty.Number, `15`, cty.NumberIntVal(15), }, { cty.StringVal("true"), cty.Bool, `true`, cty.True, }, { cty.StringVal("1"), cty.Bool, `true`, cty.True, }, { cty.NullVal(cty.String), cty.String, `null`, cty.NullVal(cty.String), }, { cty.NumberIntVal(2), cty.Number, `2`, cty.NumberIntVal(2), }, { cty.NumberFloatVal(2.5), cty.Number, `2.5`, cty.NumberFloatVal(2.5), }, { cty.NumberIntVal(5), cty.String, `"5"`, cty.StringVal("5"), }, { cty.True, cty.Bool, `true`, cty.True, }, { cty.False, cty.Bool, `false`, cty.False, }, { cty.True, cty.String, `"true"`, cty.StringVal("true"), }, // Lists { cty.ListVal([]cty.Value{cty.True, cty.False}), cty.List(cty.Bool), `[true,false]`, cty.ListVal([]cty.Value{cty.True, cty.False}), }, { cty.ListValEmpty(cty.Bool), cty.List(cty.Bool), `[]`, cty.ListValEmpty(cty.Bool), }, { cty.ListVal([]cty.Value{cty.True, cty.False}), cty.List(cty.String), `["true","false"]`, cty.ListVal([]cty.Value{cty.StringVal("true"), cty.StringVal("false")}), }, // Sets { cty.SetVal([]cty.Value{cty.True, cty.False}), cty.Set(cty.Bool), `[false,true]`, cty.SetVal([]cty.Value{cty.True, cty.False}), }, { cty.SetValEmpty(cty.Bool), cty.Set(cty.Bool), `[]`, cty.SetValEmpty(cty.Bool), }, // Tuples { cty.TupleVal([]cty.Value{cty.True, cty.NumberIntVal(5)}), cty.Tuple([]cty.Type{cty.Bool, cty.Number}), `[true,5]`, cty.TupleVal([]cty.Value{cty.True, cty.NumberIntVal(5)}), }, { cty.EmptyTupleVal, cty.EmptyTuple, `[]`, cty.EmptyTupleVal, }, // Maps { cty.MapValEmpty(cty.Bool), cty.Map(cty.Bool), `{}`, cty.MapValEmpty(cty.Bool), }, { cty.MapVal(map[string]cty.Value{"yes": cty.True, "no": cty.False}), cty.Map(cty.Bool), `{"no":false,"yes":true}`, cty.MapVal(map[string]cty.Value{"yes": cty.True, "no": cty.False}), }, { cty.NullVal(cty.Map(cty.Bool)), cty.Map(cty.Bool), `null`, cty.NullVal(cty.Map(cty.Bool)), }, // Objects { cty.EmptyObjectVal, cty.EmptyObject, `{}`, cty.EmptyObjectVal, }, { cty.ObjectVal(map[string]cty.Value{"bool": cty.True, "number": cty.Zero}), cty.Object(map[string]cty.Type{"bool": cty.Bool, "number": cty.Number}), `{"bool":true,"number":0}`, cty.ObjectVal(map[string]cty.Value{"bool": cty.True, "number": cty.Zero}), }, // Capsules { bytesVal, bytesType, `"aGVsbG8="`, bytesVal, }, // Encoding into dynamic produces type information wrapper { cty.True, cty.DynamicPseudoType, `{"value":true,"type":"bool"}`, cty.True, }, { cty.StringVal("hello"), cty.DynamicPseudoType, `{"value":"hello","type":"string"}`, cty.StringVal("hello"), }, { cty.NumberIntVal(5), cty.DynamicPseudoType, `{"value":5,"type":"number"}`, cty.NumberIntVal(5), }, { cty.ListVal([]cty.Value{cty.True, cty.False}), cty.DynamicPseudoType, `{"value":[true,false],"type":["list","bool"]}`, cty.ListVal([]cty.Value{cty.True, cty.False}), }, { cty.ListVal([]cty.Value{cty.True, cty.False}), cty.List(cty.DynamicPseudoType), `[{"value":true,"type":"bool"},{"value":false,"type":"bool"}]`, cty.ListVal([]cty.Value{cty.True, cty.False}), }, { cty.ObjectVal(map[string]cty.Value{"static": cty.True, "dynamic": cty.True}), cty.Object(map[string]cty.Type{"static": cty.Bool, "dynamic": cty.DynamicPseudoType}), `{"dynamic":{"value":true,"type":"bool"},"static":true}`, cty.ObjectVal(map[string]cty.Value{"static": cty.True, "dynamic": cty.True}), }, { cty.ObjectVal(map[string]cty.Value{"static": cty.True, "dynamic": cty.True}), cty.DynamicPseudoType, `{"value":{"dynamic":true,"static":true},"type":["object",{"dynamic":"bool","static":"bool"}]}`, cty.ObjectVal(map[string]cty.Value{"static": cty.True, "dynamic": cty.True}), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v to %#v", test.Value, test.Type), func(t *testing.T) { gotBuf, err := Marshal(test.Value, test.Type) if err != nil { t.Fatalf("unexpected error from Marshal: %s", err) } got := string(gotBuf) if got != test.Want { t.Errorf( "wrong serialization\nvalue: %#v\ntype: %#v\ngot: %s\nwant: %s", test.Value, test.Type, got, test.Want, ) } newVal, err := Unmarshal(gotBuf, test.Type) if err != nil { t.Fatalf("unexpected error from Unmarshal: %s", err) } // If we're dealing with our capsule type then we need to do some // more manual comparison because capsule values compare by // pointer identity but pointers don't survive marshalling. if newVal.Type().Equals(bytesType) { gotBuf := newVal.EncapsulatedValue() wantBuf := test.DecVal.EncapsulatedValue() if !reflect.DeepEqual(gotBuf, wantBuf) { t.Errorf( "mismatch after Unmarshal\njson: %s\ntype: %#v\ngot: %#v\nwant: %#v", got, test.Type, newVal, test.Value, ) } } else if !newVal.RawEquals(test.DecVal) { t.Errorf( "mismatch after Unmarshal\njson: %s\ntype: %#v\ngot: %#v\nwant: %#v", got, test.Type, newVal, test.Value, ) } }) } } go-cty-1.12.1/cty/json_test.go000066400000000000000000000030171433256746400161530ustar00rootroot00000000000000package cty import ( "encoding/json" "testing" ) func TestTypeJSONable(t *testing.T) { tests := []struct { Type Type Want string }{ { String, `"string"`, }, { Number, `"number"`, }, { Bool, `"bool"`, }, { List(Bool), `["list","bool"]`, }, { Map(Bool), `["map","bool"]`, }, { Set(Bool), `["set","bool"]`, }, { List(Map(Bool)), `["list",["map","bool"]]`, }, { Tuple([]Type{Bool, String}), `["tuple",["bool","string"]]`, }, { Object(map[string]Type{"bool": Bool, "string": String}), `["object",{"bool":"bool","string":"string"}]`, }, { ObjectWithOptionalAttrs(map[string]Type{"bool": Bool, "string": String}, []string{"string", "bool"}), `["object",{"bool":"bool","string":"string"},["bool","string"]]`, }, { DynamicPseudoType, `"dynamic"`, }, } for _, test := range tests { t.Run(test.Type.GoString(), func(t *testing.T) { result, err := json.Marshal(test.Type) if err != nil { t.Fatalf("unexpected error from Marshal: %s", err) } resultStr := string(result) if resultStr != test.Want { t.Errorf( "wrong result\ntype: %#v\ngot: %s\nwant: %s", test.Type, resultStr, test.Want, ) } var ty Type err = json.Unmarshal(result, &ty) if err != nil { t.Fatalf("unexpected error from Unmarshal: %s", err) } if !ty.Equals(test.Type) { t.Errorf( "type did not unmarshal correctly\njson: %s\ngot: %#v\nwant: %#v", resultStr, ty, test.Type, ) } }) } } go-cty-1.12.1/cty/list_type.go000066400000000000000000000033251433256746400161610ustar00rootroot00000000000000package cty import ( "fmt" ) // TypeList instances represent specific list types. Each distinct ElementType // creates a distinct, non-equal list type. type typeList struct { typeImplSigil ElementTypeT Type } // List creates a map type with the given element Type. // // List types are CollectionType implementations. func List(elem Type) Type { return Type{ typeList{ ElementTypeT: elem, }, } } // Equals returns true if the other Type is a list whose element type is // equal to that of the receiver. func (t typeList) Equals(other Type) bool { ot, isList := other.typeImpl.(typeList) if !isList { return false } return t.ElementTypeT.Equals(ot.ElementTypeT) } func (t typeList) FriendlyName(mode friendlyTypeNameMode) string { elemName := t.ElementTypeT.friendlyNameMode(mode) if mode == friendlyTypeConstraintName { if t.ElementTypeT == DynamicPseudoType { elemName = "any single type" } } return "list of " + elemName } func (t typeList) ElementType() Type { return t.ElementTypeT } func (t typeList) GoString() string { return fmt.Sprintf("cty.List(%#v)", t.ElementTypeT) } // IsListType returns true if the given type is a list type, regardless of its // element type. func (t Type) IsListType() bool { _, ok := t.typeImpl.(typeList) return ok } // ListElementType is a convenience method that checks if the given type is // a list type, returning a pointer to its element type if so and nil // otherwise. This is intended to allow convenient conditional branches, // like so: // // if et := t.ListElementType(); et != nil { // // Do something with *et // } func (t Type) ListElementType() *Type { if lt, ok := t.typeImpl.(typeList); ok { return <.ElementTypeT } return nil } go-cty-1.12.1/cty/map_type.go000066400000000000000000000032751433256746400157670ustar00rootroot00000000000000package cty import ( "fmt" ) // TypeList instances represent specific list types. Each distinct ElementType // creates a distinct, non-equal list type. type typeMap struct { typeImplSigil ElementTypeT Type } // Map creates a map type with the given element Type. // // Map types are CollectionType implementations. func Map(elem Type) Type { return Type{ typeMap{ ElementTypeT: elem, }, } } // Equals returns true if the other Type is a map whose element type is // equal to that of the receiver. func (t typeMap) Equals(other Type) bool { ot, isMap := other.typeImpl.(typeMap) if !isMap { return false } return t.ElementTypeT.Equals(ot.ElementTypeT) } func (t typeMap) FriendlyName(mode friendlyTypeNameMode) string { elemName := t.ElementTypeT.friendlyNameMode(mode) if mode == friendlyTypeConstraintName { if t.ElementTypeT == DynamicPseudoType { elemName = "any single type" } } return "map of " + elemName } func (t typeMap) ElementType() Type { return t.ElementTypeT } func (t typeMap) GoString() string { return fmt.Sprintf("cty.Map(%#v)", t.ElementTypeT) } // IsMapType returns true if the given type is a map type, regardless of its // element type. func (t Type) IsMapType() bool { _, ok := t.typeImpl.(typeMap) return ok } // MapElementType is a convenience method that checks if the given type is // a map type, returning a pointer to its element type if so and nil // otherwise. This is intended to allow convenient conditional branches, // like so: // // if et := t.MapElementType(); et != nil { // // Do something with *et // } func (t Type) MapElementType() *Type { if lt, ok := t.typeImpl.(typeMap); ok { return <.ElementTypeT } return nil } go-cty-1.12.1/cty/marks.go000066400000000000000000000243761433256746400152730ustar00rootroot00000000000000package cty import ( "fmt" "strings" ) // marker is an internal wrapper type used to add special "marks" to values. // // A "mark" is an annotation that can be used to represent additional // characteristics of values that propagate through operation methods to // result values. However, a marked value cannot be used with integration // methods normally associated with its type, in order to ensure that // calling applications don't inadvertently drop marks as they round-trip // values out of cty and back in again. // // Marked values are created only explicitly by the calling application, so // an application that never marks a value does not need to worry about // encountering marked values. type marker struct { realV interface{} marks ValueMarks } // ValueMarks is a map, representing a set, of "mark" values associated with // a Value. See Value.Mark for more information on the usage of mark values. type ValueMarks map[interface{}]struct{} // NewValueMarks constructs a new ValueMarks set with the given mark values. // // If any of the arguments are already ValueMarks values then they'll be merged // into the result, rather than used directly as individual marks. func NewValueMarks(marks ...interface{}) ValueMarks { if len(marks) == 0 { return nil } ret := make(ValueMarks, len(marks)) for _, v := range marks { if vm, ok := v.(ValueMarks); ok { // Constructing a new ValueMarks with an existing ValueMarks // implements a merge operation. (This can cause our result to // have a larger size than we expected, but that's okay.) for v := range vm { ret[v] = struct{}{} } continue } ret[v] = struct{}{} } if len(ret) == 0 { // If we were merging ValueMarks values together and they were all // empty then we'll avoid returning a zero-length map and return a // nil instead, as is conventional. return nil } return ret } // Equal returns true if the receiver and the given ValueMarks both contain // the same marks. func (m ValueMarks) Equal(o ValueMarks) bool { if len(m) != len(o) { return false } for v := range m { if _, ok := o[v]; !ok { return false } } return true } func (m ValueMarks) GoString() string { var s strings.Builder s.WriteString("cty.NewValueMarks(") i := 0 for mv := range m { if i != 0 { s.WriteString(", ") } s.WriteString(fmt.Sprintf("%#v", mv)) i++ } s.WriteString(")") return s.String() } // PathValueMarks is a structure that enables tracking marks // and the paths where they are located in one type type PathValueMarks struct { Path Path Marks ValueMarks } func (p PathValueMarks) Equal(o PathValueMarks) bool { if !p.Path.Equals(o.Path) { return false } if !p.Marks.Equal(o.Marks) { return false } return true } // IsMarked returns true if and only if the receiving value carries at least // one mark. A marked value cannot be used directly with integration methods // without explicitly unmarking it (and retrieving the markings) first. func (val Value) IsMarked() bool { _, ok := val.v.(marker) return ok } // HasMark returns true if and only if the receiving value has the given mark. func (val Value) HasMark(mark interface{}) bool { if mr, ok := val.v.(marker); ok { _, ok := mr.marks[mark] return ok } return false } // ContainsMarked returns true if the receiving value or any value within it // is marked. // // This operation is relatively expensive. If you only need a shallow result, // use IsMarked instead. func (val Value) ContainsMarked() bool { ret := false Walk(val, func(_ Path, v Value) (bool, error) { if v.IsMarked() { ret = true return false, nil } return true, nil }) return ret } func (val Value) assertUnmarked() { if val.IsMarked() { panic("value is marked, so must be unmarked first") } } // Marks returns a map (representing a set) of all of the mark values // associated with the receiving value, without changing the marks. Returns nil // if the value is not marked at all. func (val Value) Marks() ValueMarks { if mr, ok := val.v.(marker); ok { // copy so that the caller can't mutate our internals ret := make(ValueMarks, len(mr.marks)) for k, v := range mr.marks { ret[k] = v } return ret } return nil } // HasSameMarks returns true if an only if the receiver and the given other // value have identical marks. func (val Value) HasSameMarks(other Value) bool { vm, vmOK := val.v.(marker) om, omOK := other.v.(marker) if vmOK != omOK { return false } if vmOK { return vm.marks.Equal(om.marks) } return true } // Mark returns a new value that as the same type and underlying value as // the receiver but that also carries the given value as a "mark". // // Marks are used to carry additional application-specific characteristics // associated with values. A marked value can be used with operation methods, // in which case the marks are propagated to the operation results. A marked // value _cannot_ be used with integration methods, so callers of those // must derive an unmarked value using Unmark (and thus explicitly handle // the markings) before calling the integration methods. // // The mark value can be any value that would be valid to use as a map key. // The mark value should be of a named type in order to use the type itself // as a namespace for markings. That type can be unexported if desired, in // order to ensure that the mark can only be handled through the defining // package's own functions. // // An application that never calls this method does not need to worry about // handling marked values. func (val Value) Mark(mark interface{}) Value { var newMarker marker newMarker.realV = val.v if mr, ok := val.v.(marker); ok { // It's already a marker, so we'll retain existing marks. newMarker.marks = make(ValueMarks, len(mr.marks)+1) for k, v := range mr.marks { newMarker.marks[k] = v } // unwrap the inner marked value, so we don't get multiple layers // of marking. newMarker.realV = mr.realV } else { // It's not a marker yet, so we're creating the first mark. newMarker.marks = make(ValueMarks, 1) } newMarker.marks[mark] = struct{}{} return Value{ ty: val.ty, v: newMarker, } } type applyPathValueMarksTransformer struct { pvm []PathValueMarks } func (t *applyPathValueMarksTransformer) Enter(p Path, v Value) (Value, error) { return v, nil } func (t *applyPathValueMarksTransformer) Exit(p Path, v Value) (Value, error) { for _, path := range t.pvm { if p.Equals(path.Path) { return v.WithMarks(path.Marks), nil } } return v, nil } // MarkWithPaths accepts a slice of PathValueMarks to apply // markers to particular paths and returns the marked // Value. func (val Value) MarkWithPaths(pvm []PathValueMarks) Value { ret, _ := TransformWithTransformer(val, &applyPathValueMarksTransformer{pvm}) return ret } // Unmark separates the marks of the receiving value from the value itself, // removing a new unmarked value and a map (representing a set) of the marks. // // If the receiver isn't marked, Unmark returns it verbatim along with a nil // map of marks. func (val Value) Unmark() (Value, ValueMarks) { if !val.IsMarked() { return val, nil } mr := val.v.(marker) marks := val.Marks() // copy so that the caller can't mutate our internals return Value{ ty: val.ty, v: mr.realV, }, marks } type unmarkTransformer struct { pvm []PathValueMarks } func (t *unmarkTransformer) Enter(p Path, v Value) (Value, error) { unmarkedVal, marks := v.Unmark() if len(marks) > 0 { path := make(Path, len(p), len(p)+1) copy(path, p) t.pvm = append(t.pvm, PathValueMarks{path, marks}) } return unmarkedVal, nil } func (t *unmarkTransformer) Exit(p Path, v Value) (Value, error) { return v, nil } // UnmarkDeep is similar to Unmark, but it works with an entire nested structure // rather than just the given value directly. // // The result is guaranteed to contain no nested values that are marked, and // the returned marks set includes the superset of all of the marks encountered // during the operation. func (val Value) UnmarkDeep() (Value, ValueMarks) { t := unmarkTransformer{} ret, _ := TransformWithTransformer(val, &t) marks := make(ValueMarks) for _, pvm := range t.pvm { for m, s := range pvm.Marks { marks[m] = s } } return ret, marks } // UnmarkDeepWithPaths is like UnmarkDeep, except it returns a slice // of PathValueMarks rather than a superset of all marks. This allows // a caller to know which marks are associated with which paths // in the Value. func (val Value) UnmarkDeepWithPaths() (Value, []PathValueMarks) { t := unmarkTransformer{} ret, _ := TransformWithTransformer(val, &t) return ret, t.pvm } func (val Value) unmarkForce() Value { unw, _ := val.Unmark() return unw } // WithMarks returns a new value that has the same type and underlying value // as the receiver and also has the marks from the given maps (representing // sets). func (val Value) WithMarks(marks ...ValueMarks) Value { if len(marks) == 0 { return val } ownMarks := val.Marks() markCount := len(ownMarks) for _, s := range marks { markCount += len(s) } if markCount == 0 { return val } newMarks := make(ValueMarks, markCount) for m := range ownMarks { newMarks[m] = struct{}{} } for _, s := range marks { for m := range s { newMarks[m] = struct{}{} } } v := val.v if mr, ok := v.(marker); ok { v = mr.realV } return Value{ ty: val.ty, v: marker{ realV: v, marks: newMarks, }, } } // WithSameMarks returns a new value that has the same type and underlying // value as the receiver and also has the marks from the given source values. // // Use this if you are implementing your own higher-level operations against // cty using the integration methods, to re-introduce the marks from the // source values of the operation. func (val Value) WithSameMarks(srcs ...Value) Value { if len(srcs) == 0 { return val } ownMarks := val.Marks() markCount := len(ownMarks) for _, sv := range srcs { if mr, ok := sv.v.(marker); ok { markCount += len(mr.marks) } } if markCount == 0 { return val } newMarks := make(ValueMarks, markCount) for m := range ownMarks { newMarks[m] = struct{}{} } for _, sv := range srcs { if mr, ok := sv.v.(marker); ok { for m := range mr.marks { newMarks[m] = struct{}{} } } } v := val.v if mr, ok := v.(marker); ok { v = mr.realV } return Value{ ty: val.ty, v: marker{ realV: v, marks: newMarks, }, } } go-cty-1.12.1/cty/marks_test.go000066400000000000000000000327151433256746400163260ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestContainsMarked(t *testing.T) { testCases := []struct { val Value want bool }{ { StringVal("a"), false, }, { NumberIntVal(1).Mark("a"), true, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), false, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2).Mark("a")}), true, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}).Mark("a"), true, }, { ListValEmpty(String).Mark("c"), true, }, { MapVal(map[string]Value{"a": StringVal("b").Mark("c"), "x": StringVal("y").Mark("z")}), true, }, { TupleVal([]Value{NumberIntVal(1).Mark("a"), StringVal("y").Mark("z")}), true, }, { SetVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2).Mark("z")}), true, }, { ObjectVal(map[string]Value{ "x": ListVal([]Value{ NumberIntVal(1).Mark("a"), NumberIntVal(2), }), "y": StringVal("y"), "z": BoolVal(true), }), true, }, } for _, tc := range testCases { if got, want := tc.val.ContainsMarked(), tc.want; got != want { t.Errorf("wrong result (got %v, want %v) for %#v", got, want, tc.val) } } } func TestIsMarked(t *testing.T) { testCases := []struct { val Value want bool }{ { StringVal("a"), false, }, { NumberIntVal(1).Mark("a"), true, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), false, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2).Mark("a")}), false, }, { ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}).Mark("a"), true, }, } for _, tc := range testCases { if got, want := tc.val.IsMarked(), tc.want; got != want { t.Errorf("wrong result (got %v, want %v) for %#v", got, want, tc.val) } } } func TestValueMarks(t *testing.T) { v := True v1 := v.Mark(1) v2 := v.Mark(2) if got, want := v.Marks(), NewValueMarks(); !want.Equal(got) { t.Errorf("wrong v marks\ngot: %#v\nwant: %#v", got, want) } if got, want := v1.Marks(), NewValueMarks(1); !want.Equal(got) { t.Errorf("wrong v1 marks\ngot: %#v\nwant: %#v", got, want) } if got, want := v2.Marks(), NewValueMarks(2); !want.Equal(got) { t.Errorf("wrong v2 marks\ngot: %#v\nwant: %#v", got, want) } v12 := False.WithSameMarks(v, v1, v2) if got, want := v12.Marks(), NewValueMarks(1, 2); !want.Equal(got) { t.Errorf("wrong v12 marks\ngot: %#v\nwant: %#v", got, want) } v12Again := v12.Mark(1) if got, want := v12Again.Marks(), NewValueMarks(1, 2); !want.Equal(got) { t.Errorf("wrong v12Again marks\ngot: %#v\nwant: %#v", got, want) } v1234 := v12.WithMarks(NewValueMarks(2, 3, 4)) if got, want := v1234.Marks(), NewValueMarks(1, 2, 3, 4); !want.Equal(got) { t.Errorf("wrong v1234 marks\ngot: %#v\nwant: %#v", got, want) } if !v1234.HasMark(2) { t.Errorf("v1234 should have mark 2") } if v1234.HasMark(5) { t.Errorf("v1234 should not have mark 5") } v, marks1234 := v1234.Unmark() if got, want := v.Marks(), NewValueMarks(); !want.Equal(got) { t.Errorf("wrong v marks after unmarking\ngot: %#v\nwant: %#v", got, want) } if got, want := marks1234, NewValueMarks(1, 2, 3, 4); !want.Equal(got) { t.Errorf("wrong marks1234\ngot: %#v\nwant: %#v", got, want) } if got, want := v, False; !want.RawEquals(got) { t.Errorf("wrong v after unmarking\ngot: %#v\nwant: %#v", got, want) } // One more test for a more interesting/realistic situation involving // a number of different operations. a := NumberIntVal(2).Mark("a") b := NumberIntVal(5).Mark("b") c := NumberIntVal(1).Mark("c") d := NumberIntVal(12).Mark("d") result := a.Multiply(b).Subtract(c).GreaterThanOrEqualTo(d) if got, want := result, False.WithMarks(NewValueMarks("a", "b", "c", "d")); !want.RawEquals(got) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) } // Unmark the result and capture the paths unmarkedResult, pvm := result.UnmarkDeepWithPaths() // Remark the result with those paths remarked := unmarkedResult.MarkWithPaths(pvm) if got, want := remarked, False.WithMarks(NewValueMarks("a", "b", "c", "d")); !want.RawEquals(got) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) } // If we call MarkWithPaths without any matching paths, we should get the unmarked result markedWithNoPaths := unmarkedResult.MarkWithPaths([]PathValueMarks{{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("z")}}) if got, want := markedWithNoPaths, False; !want.RawEquals(got) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) } } func TestPathValueMarksEqual(t *testing.T) { tests := []struct { original PathValueMarks compare PathValueMarks want bool }{ { PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("a")}, PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("a")}, true, }, { PathValueMarks{Path{IndexStep{Key: StringVal("p")}}, NewValueMarks(123)}, PathValueMarks{Path{IndexStep{Key: StringVal("p")}}, NewValueMarks(123)}, true, }, { PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("a")}, PathValueMarks{Path{IndexStep{Key: NumberIntVal(1)}}, NewValueMarks("a")}, false, }, { PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("a")}, PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("b")}, false, }, { PathValueMarks{Path{IndexStep{Key: NumberIntVal(0)}}, NewValueMarks("a")}, PathValueMarks{Path{IndexStep{Key: NumberIntVal(1)}}, NewValueMarks("b")}, false, }, } for _, test := range tests { t.Run(fmt.Sprintf("Comparing %#v to %#v", test.original, test.compare), func(t *testing.T) { got := test.original.Equal(test.compare) if got != test.want { t.Errorf("wrong result\ngot: %v\nwant: %v", got, test.want) } }) } } func TestMarks(t *testing.T) { wantMarks := func(marks ValueMarks, expected ...string) { if len(marks) != len(expected) { t.Fatalf("wrong marks: %#v", marks) } for _, mark := range expected { if _, ok := marks[mark]; !ok { t.Fatalf("missing mark %q: %#v", mark, marks) } } } // Single mark val := StringVal("foo").Mark("a") wantMarks(val.Marks(), "a") val, marks := val.Unmark() if val.IsMarked() { t.Fatalf("still marked after unmark: %#v", marks) } wantMarks(marks, "a") // Multiple marks val = val.WithMarks(NewValueMarks("a", "b", "c")) wantMarks(val.Marks(), "a", "b", "c") val, marks = val.Unmark() if val.IsMarked() { t.Fatalf("still marked after unmark: %#v", marks) } wantMarks(marks, "a", "b", "c") // Multiple marks, applied separately val = val.Mark("a").Mark("b") wantMarks(val.Marks(), "a", "b") val, marks = val.Unmark() if val.IsMarked() { t.Fatalf("still marked after unmark: %#v", marks) } wantMarks(marks, "a", "b") } func TestUnmarkDeep(t *testing.T) { testCases := map[string]struct { val Value want Value marks ValueMarks }{ "unmarked string": { StringVal("a"), StringVal("a"), NewValueMarks(), }, "marked number": { NumberIntVal(1).Mark("a"), NumberIntVal(1), NewValueMarks("a"), }, "unmarked list": { ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), NewValueMarks(), }, "list with some elements marked": { ListVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2)}), ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), NewValueMarks("a"), }, "marked list with all elements marked": { ListVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2).Mark("b")}).Mark("c"), ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), NewValueMarks("a", "b", "c"), }, "marked empty list": { ListValEmpty(String).Mark("c"), ListValEmpty(String), NewValueMarks("c"), }, "map with elements marked": { MapVal(map[string]Value{"a": StringVal("b").Mark("c"), "x": StringVal("y").Mark("z")}), MapVal(map[string]Value{"a": StringVal("b"), "x": StringVal("y")}), NewValueMarks("c", "z"), }, "tuple with elements marked": { TupleVal([]Value{NumberIntVal(1).Mark("a"), StringVal("y").Mark("z")}), TupleVal([]Value{NumberIntVal(1), StringVal("y")}), NewValueMarks("a", "z"), }, "set with elements marked": { SetVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2).Mark("z")}), SetVal([]Value{NumberIntVal(1), NumberIntVal(2)}), NewValueMarks("a", "z"), }, "complex marked object with lots of marks": { ObjectVal(map[string]Value{ "x": ListVal([]Value{ NumberIntVal(3).Mark("a"), NumberIntVal(5).Mark("b"), }).WithMarks(NewValueMarks("c", "d")), "y": StringVal("y").Mark("e"), "z": BoolVal(true).Mark("f"), }).Mark("g"), ObjectVal(map[string]Value{ "x": ListVal([]Value{ NumberIntVal(3), NumberIntVal(5), }), "y": StringVal("y"), "z": BoolVal(true), }), NewValueMarks("a", "b", "c", "d", "e", "f", "g"), }, } for name, tc := range testCases { t.Run(name, func(t *testing.T) { got, marks := tc.val.UnmarkDeep() if !got.RawEquals(tc.want) { t.Errorf("wrong value\n got: %#v\nwant: %#v", got, tc.want) } if !marks.Equal(tc.marks) { t.Errorf("wrong marks\n got: %#v\nwant: %#v", got, tc.want) } }) } } func TestPathValueMarks(t *testing.T) { testCases := map[string]struct { marked Value unmarked Value pvms []PathValueMarks }{ "unmarked string": { StringVal("a"), StringVal("a"), nil, }, "marked number": { NumberIntVal(1).Mark("a"), NumberIntVal(1), []PathValueMarks{ {Path{}, NewValueMarks("a")}, }, }, "list with some elements marked": { ListVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2)}), ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), []PathValueMarks{ {IndexIntPath(0), NewValueMarks("a")}, }, }, "marked list with all elements marked": { ListVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2).Mark("b")}).Mark("c"), ListVal([]Value{NumberIntVal(1), NumberIntVal(2)}), []PathValueMarks{ {Path{}, NewValueMarks("c")}, {IndexIntPath(0), NewValueMarks("a")}, {IndexIntPath(1), NewValueMarks("b")}, }, }, "marked empty list": { ListValEmpty(String).Mark("c"), ListValEmpty(String), []PathValueMarks{ {Path{}, NewValueMarks("c")}, }, }, "map with elements marked": { MapVal(map[string]Value{"a": StringVal("b").Mark("c"), "x": StringVal("y").Mark("z")}), MapVal(map[string]Value{"a": StringVal("b"), "x": StringVal("y")}), []PathValueMarks{ {IndexStringPath("a"), NewValueMarks("c")}, {IndexStringPath("x"), NewValueMarks("z")}, }, }, "tuple with elements marked": { TupleVal([]Value{NumberIntVal(1).Mark("a"), StringVal("y").Mark("z"), ObjectVal(map[string]Value{"x": True}).Mark("o")}), TupleVal([]Value{NumberIntVal(1), StringVal("y"), ObjectVal(map[string]Value{"x": True})}), []PathValueMarks{ {IndexIntPath(0), NewValueMarks("a")}, {IndexIntPath(1), NewValueMarks("z")}, {IndexIntPath(2), NewValueMarks("o")}, }, }, "set with elements marked": { SetVal([]Value{NumberIntVal(1).Mark("a"), NumberIntVal(2).Mark("z")}), SetVal([]Value{NumberIntVal(1), NumberIntVal(2)}), []PathValueMarks{ {Path{}, NewValueMarks("a", "z")}, }, }, "complex marked object with lots of marks": { ObjectVal(map[string]Value{ "x": ListVal([]Value{ NumberIntVal(3).Mark("a"), NumberIntVal(5).Mark("b"), }).WithMarks(NewValueMarks("c", "d")), "y": StringVal("y").Mark("e"), "z": BoolVal(true).Mark("f"), }).Mark("g"), ObjectVal(map[string]Value{ "x": ListVal([]Value{ NumberIntVal(3), NumberIntVal(5), }), "y": StringVal("y"), "z": BoolVal(true), }), []PathValueMarks{ {Path{}, NewValueMarks("g")}, {GetAttrPath("x"), NewValueMarks("c", "d")}, {GetAttrPath("x").IndexInt(0), NewValueMarks("a")}, {GetAttrPath("x").IndexInt(1), NewValueMarks("b")}, {GetAttrPath("y"), NewValueMarks("e")}, {GetAttrPath("z"), NewValueMarks("f")}, }, }, "path array reuse regression test": { ObjectVal(map[string]Value{ "environment": ListVal([]Value{ ObjectVal(map[string]Value{ "variables": MapVal(map[string]Value{ "bar": StringVal("secret").Mark("sensitive"), "foo": StringVal("secret").Mark("sensitive"), }), }), }), }), ObjectVal(map[string]Value{ "environment": ListVal([]Value{ ObjectVal(map[string]Value{ "variables": MapVal(map[string]Value{ "bar": StringVal("secret"), "foo": StringVal("secret"), }), }), }), }), []PathValueMarks{ {GetAttrPath("environment").IndexInt(0).GetAttr("variables").IndexString("bar"), NewValueMarks("sensitive")}, {GetAttrPath("environment").IndexInt(0).GetAttr("variables").IndexString("foo"), NewValueMarks("sensitive")}, }, }, } for name, tc := range testCases { t.Run(fmt.Sprintf("unmark: %s", name), func(t *testing.T) { got, pvms := tc.marked.UnmarkDeepWithPaths() if !got.RawEquals(tc.unmarked) { t.Errorf("wrong value\n got: %#v\nwant: %#v", got, tc.unmarked) } if len(pvms) != len(tc.pvms) { t.Errorf("wrong length\n got: %d\nwant: %d", len(pvms), len(tc.pvms)) } findPvm: for _, wantPvm := range tc.pvms { for _, gotPvm := range pvms { if gotPvm.Path.Equals(wantPvm.Path) && gotPvm.Marks.Equal(wantPvm.Marks) { continue findPvm } } t.Errorf("missing %#v\nnot found in: %#v", wantPvm, pvms) } }) t.Run(fmt.Sprintf("mark: %s", name), func(t *testing.T) { got := tc.unmarked.MarkWithPaths(tc.pvms) if !got.RawEquals(tc.marked) { t.Errorf("wrong value\n got: %#v\nwant: %#v", got, tc.marked) } }) } } go-cty-1.12.1/cty/msgpack/000077500000000000000000000000001433256746400152405ustar00rootroot00000000000000go-cty-1.12.1/cty/msgpack/doc.go000066400000000000000000000013151433256746400163340ustar00rootroot00000000000000// Package msgpack provides functions for serializing cty values in the // msgpack encoding, and decoding them again. // // If the same type information is provided both at encoding and decoding time // then values can be round-tripped without loss, except for capsule types // which are not currently supported. // // If any unknown values are passed to Marshal then they will be represented // using a msgpack extension with type code zero, which is understood by // the Unmarshal function within this package but will not be understood by // a generic (non-cty-aware) msgpack decoder. Ensure that no unknown values // are used if interoperability with other msgpack implementations is // required. package msgpack go-cty-1.12.1/cty/msgpack/dynamic.go000066400000000000000000000013061433256746400172130ustar00rootroot00000000000000package msgpack import ( "bytes" "github.com/vmihailenco/msgpack/v4" "github.com/zclconf/go-cty/cty" ) type dynamicVal struct { Value cty.Value Path cty.Path } func (dv *dynamicVal) MarshalMsgpack() ([]byte, error) { // Rather than defining a msgpack-specific serialization of types, // instead we use the existing JSON serialization. typeJSON, err := dv.Value.Type().MarshalJSON() if err != nil { return nil, dv.Path.NewErrorf("failed to serialize type: %s", err) } var buf bytes.Buffer enc := msgpack.NewEncoder(&buf) enc.EncodeArrayLen(2) enc.EncodeBytes(typeJSON) err = marshal(dv.Value, dv.Value.Type(), dv.Path, enc) if err != nil { return nil, err } return buf.Bytes(), nil } go-cty-1.12.1/cty/msgpack/infinity.go000066400000000000000000000001541433256746400174200ustar00rootroot00000000000000package msgpack import ( "math" ) var negativeInfinity = math.Inf(-1) var positiveInfinity = math.Inf(1) go-cty-1.12.1/cty/msgpack/marshal.go000066400000000000000000000116641433256746400172260ustar00rootroot00000000000000package msgpack import ( "bytes" "math/big" "sort" "github.com/vmihailenco/msgpack/v4" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" ) // Marshal produces a msgpack serialization of the given value that // can be decoded into the given type later using Unmarshal. // // The given value must conform to the given type, or an error will // be returned. func Marshal(val cty.Value, ty cty.Type) ([]byte, error) { errs := val.Type().TestConformance(ty) if errs != nil { // Attempt a conversion var err error val, err = convert.Convert(val, ty) if err != nil { return nil, err } } // From this point onward, val can be assumed to be conforming to t. var path cty.Path var buf bytes.Buffer enc := msgpack.NewEncoder(&buf) enc.UseCompactEncoding(true) err := marshal(val, ty, path, enc) if err != nil { return nil, err } return buf.Bytes(), nil } func marshal(val cty.Value, ty cty.Type, path cty.Path, enc *msgpack.Encoder) error { if val.IsMarked() { return path.NewErrorf("value has marks, so it cannot be serialized") } // If we're going to decode as DynamicPseudoType then we need to save // dynamic type information to recover the real type. if ty == cty.DynamicPseudoType && val.Type() != cty.DynamicPseudoType { return marshalDynamic(val, path, enc) } if !val.IsKnown() { err := enc.Encode(unknownVal) if err != nil { return path.NewError(err) } return nil } if val.IsNull() { err := enc.EncodeNil() if err != nil { return path.NewError(err) } return nil } // The caller should've guaranteed that the given val is conformant with // the given type ty, so we'll proceed under that assumption here. switch { case ty.IsPrimitiveType(): switch ty { case cty.String: err := enc.EncodeString(val.AsString()) if err != nil { return path.NewError(err) } return nil case cty.Number: var err error switch { case val.RawEquals(cty.PositiveInfinity): err = enc.EncodeFloat64(positiveInfinity) case val.RawEquals(cty.NegativeInfinity): err = enc.EncodeFloat64(negativeInfinity) default: bf := val.AsBigFloat() if iv, acc := bf.Int64(); acc == big.Exact { err = enc.EncodeInt(iv) } else if fv, acc := bf.Float64(); acc == big.Exact { err = enc.EncodeFloat64(fv) } else { err = enc.EncodeString(bf.Text('f', -1)) } } if err != nil { return path.NewError(err) } return nil case cty.Bool: err := enc.EncodeBool(val.True()) if err != nil { return path.NewError(err) } return nil default: panic("unsupported primitive type") } case ty.IsListType(), ty.IsSetType(): enc.EncodeArrayLen(val.LengthInt()) ety := ty.ElementType() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element for it.Next() { ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } err := marshal(ev, ety, path, enc) if err != nil { return err } } return nil case ty.IsMapType(): enc.EncodeMapLen(val.LengthInt()) ety := ty.ElementType() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element for it.Next() { ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } var err error err = marshal(ek, ek.Type(), path, enc) if err != nil { return err } err = marshal(ev, ety, path, enc) if err != nil { return err } } return nil case ty.IsTupleType(): etys := ty.TupleElementTypes() it := val.ElementIterator() path := append(path, nil) // local override of 'path' with extra element i := 0 enc.EncodeArrayLen(len(etys)) for it.Next() { ety := etys[i] ek, ev := it.Element() path[len(path)-1] = cty.IndexStep{ Key: ek, } err := marshal(ev, ety, path, enc) if err != nil { return err } i++ } return nil case ty.IsObjectType(): atys := ty.AttributeTypes() path := append(path, nil) // local override of 'path' with extra element names := make([]string, 0, len(atys)) for k := range atys { names = append(names, k) } sort.Strings(names) enc.EncodeMapLen(len(names)) for _, k := range names { aty := atys[k] av := val.GetAttr(k) path[len(path)-1] = cty.GetAttrStep{ Name: k, } var err error err = marshal(cty.StringVal(k), cty.String, path, enc) if err != nil { return err } err = marshal(av, aty, path, enc) if err != nil { return err } } return nil case ty.IsCapsuleType(): return path.NewErrorf("capsule types not supported for msgpack encoding") default: // should never happen return path.NewErrorf("cannot msgpack-serialize %s", ty.FriendlyName()) } } // marshalDynamic adds an extra wrapping object containing dynamic type // information for the given value. func marshalDynamic(val cty.Value, path cty.Path, enc *msgpack.Encoder) error { dv := dynamicVal{ Value: val, Path: path, } return enc.Encode(&dv) } go-cty-1.12.1/cty/msgpack/roundtrip_test.go000066400000000000000000000110401433256746400206500ustar00rootroot00000000000000package msgpack import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestRoundTrip(t *testing.T) { bigNumberVal, err := cty.ParseNumberVal("9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999") if err != nil { t.Fatal(err) } awkwardFractionVal, err := cty.ParseNumberVal("0.8") // awkward because it can't be represented exactly in binary if err != nil { t.Fatal(err) } tests := []struct { Value cty.Value Type cty.Type }{ { cty.StringVal("hello"), cty.String, }, { cty.StringVal(""), cty.String, }, { cty.NullVal(cty.String), cty.String, }, { cty.UnknownVal(cty.String), cty.String, }, { cty.True, cty.Bool, }, { cty.False, cty.Bool, }, { cty.NullVal(cty.Bool), cty.Bool, }, { cty.UnknownVal(cty.Bool), cty.Bool, }, { cty.NumberIntVal(1), cty.Number, }, { cty.NumberFloatVal(1.5), cty.Number, }, { bigNumberVal, cty.Number, }, { awkwardFractionVal, cty.Number, }, { cty.PositiveInfinity, cty.Number, }, { cty.NegativeInfinity, cty.Number, }, { cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), cty.List(cty.String), }, { cty.ListVal([]cty.Value{ cty.UnknownVal(cty.String), }), cty.List(cty.String), }, { cty.ListVal([]cty.Value{ cty.NullVal(cty.String), }), cty.List(cty.String), }, { cty.NullVal(cty.List(cty.String)), cty.List(cty.String), }, { cty.ListValEmpty(cty.String), cty.List(cty.String), }, { cty.SetVal([]cty.Value{ cty.StringVal("hello"), }), cty.Set(cty.String), }, { cty.SetVal([]cty.Value{ cty.UnknownVal(cty.String), }), cty.Set(cty.String), }, { cty.SetVal([]cty.Value{ cty.NullVal(cty.String), }), cty.Set(cty.String), }, { cty.SetValEmpty(cty.String), cty.Set(cty.String), }, { cty.MapVal(map[string]cty.Value{ "greeting": cty.StringVal("hello"), }), cty.Map(cty.String), }, { cty.MapVal(map[string]cty.Value{ "greeting": cty.UnknownVal(cty.String), }), cty.Map(cty.String), }, { cty.MapVal(map[string]cty.Value{ "greeting": cty.NullVal(cty.String), }), cty.Map(cty.String), }, { cty.MapValEmpty(cty.String), cty.Map(cty.String), }, { cty.TupleVal([]cty.Value{ cty.StringVal("hello"), }), cty.Tuple([]cty.Type{cty.String}), }, { cty.TupleVal([]cty.Value{ cty.UnknownVal(cty.String), }), cty.Tuple([]cty.Type{cty.String}), }, { cty.TupleVal([]cty.Value{ cty.NullVal(cty.String), }), cty.Tuple([]cty.Type{cty.String}), }, { cty.EmptyTupleVal, cty.EmptyTuple, }, { cty.ObjectVal(map[string]cty.Value{ "greeting": cty.StringVal("hello"), }), cty.Object(map[string]cty.Type{ "greeting": cty.String, }), }, { cty.ObjectVal(map[string]cty.Value{ "greeting": cty.UnknownVal(cty.String), }), cty.Object(map[string]cty.Type{ "greeting": cty.String, }), }, { cty.ObjectVal(map[string]cty.Value{ "greeting": cty.NullVal(cty.String), }), cty.Object(map[string]cty.Type{ "greeting": cty.String, }), }, { cty.ObjectVal(map[string]cty.Value{ "a": cty.NullVal(cty.String), "b": cty.NullVal(cty.String), }), cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }, { cty.ObjectVal(map[string]cty.Value{ "a": cty.UnknownVal(cty.String), "b": cty.UnknownVal(cty.String), }), cty.Object(map[string]cty.Type{ "a": cty.String, "b": cty.String, }), }, { cty.EmptyObjectVal, cty.EmptyObject, }, { cty.NullVal(cty.String), cty.DynamicPseudoType, }, { cty.DynamicVal, cty.DynamicPseudoType, }, { cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), cty.List(cty.DynamicPseudoType), }, { cty.ListVal([]cty.Value{ cty.NullVal(cty.String), }), cty.List(cty.DynamicPseudoType), }, { cty.ListVal([]cty.Value{ cty.DynamicVal, }), cty.List(cty.DynamicPseudoType), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v as %#v", test.Value, test.Type), func(t *testing.T) { b, err := Marshal(test.Value, test.Type) if err != nil { t.Fatal(err) } t.Logf("encoded as %x", b) got, err := Unmarshal(b, test.Type) if err != nil { t.Fatal(err) } if !got.RawEquals(test.Value) { t.Errorf( "value did not round-trip\ninput: %#v\nresult: %#v", test.Value, got, ) } }) } } go-cty-1.12.1/cty/msgpack/type_implied.go000066400000000000000000000107121433256746400202540ustar00rootroot00000000000000package msgpack import ( "bytes" "fmt" "io" "github.com/vmihailenco/msgpack/v4" msgpackcodes "github.com/vmihailenco/msgpack/v4/codes" "github.com/zclconf/go-cty/cty" ) // ImpliedType returns the cty Type implied by the structure of the given // msgpack-compliant buffer. This function implements the default type mapping // behavior used when decoding arbitrary msgpack without explicit cty Type // information. // // The rules are as follows: // // msgpack strings, numbers and bools map to their equivalent primitive type in // cty. // // msgpack maps become cty object types, with the attributes defined by the // map keys and the types of their values. // // msgpack arrays become cty tuple types, with the elements defined by the // types of the array members. // // Any nulls are typed as DynamicPseudoType, so callers of this function // must be prepared to deal with this. Callers that do not wish to deal with // dynamic typing should not use this function and should instead describe // their required types explicitly with a cty.Type instance when decoding. // // Any unknown values are similarly typed as DynamicPseudoType, because these // do not carry type information on the wire. // // Any parse errors will be returned as an error, and the type will be the // invalid value cty.NilType. func ImpliedType(buf []byte) (cty.Type, error) { r := bytes.NewReader(buf) dec := msgpack.NewDecoder(r) ty, err := impliedType(dec) if err != nil { return cty.NilType, err } // We must now be at the end of the buffer err = dec.Skip() if err != io.EOF { return ty, fmt.Errorf("extra bytes after msgpack value") } return ty, nil } func impliedType(dec *msgpack.Decoder) (cty.Type, error) { // If this function returns with a nil error then it must have already // consumed the next value from the decoder, since when called recursively // the caller will be expecting to find a following value here. code, err := dec.PeekCode() if err != nil { return cty.NilType, err } switch { case code == msgpackcodes.Nil || msgpackcodes.IsExt(code): err := dec.Skip() return cty.DynamicPseudoType, err case code == msgpackcodes.True || code == msgpackcodes.False: _, err := dec.DecodeBool() return cty.Bool, err case msgpackcodes.IsFixedNum(code): _, err := dec.DecodeInt64() return cty.Number, err case code == msgpackcodes.Int8 || code == msgpackcodes.Int16 || code == msgpackcodes.Int32 || code == msgpackcodes.Int64: _, err := dec.DecodeInt64() return cty.Number, err case code == msgpackcodes.Uint8 || code == msgpackcodes.Uint16 || code == msgpackcodes.Uint32 || code == msgpackcodes.Uint64: _, err := dec.DecodeUint64() return cty.Number, err case code == msgpackcodes.Float || code == msgpackcodes.Double: _, err := dec.DecodeFloat64() return cty.Number, err case msgpackcodes.IsString(code): _, err := dec.DecodeString() return cty.String, err case msgpackcodes.IsFixedMap(code) || code == msgpackcodes.Map16 || code == msgpackcodes.Map32: return impliedObjectType(dec) case msgpackcodes.IsFixedArray(code) || code == msgpackcodes.Array16 || code == msgpackcodes.Array32: return impliedTupleType(dec) default: return cty.NilType, fmt.Errorf("unsupported msgpack code %#v", code) } } func impliedObjectType(dec *msgpack.Decoder) (cty.Type, error) { // If we get in here then we've already peeked the next code and know // it's some sort of map. l, err := dec.DecodeMapLen() if err != nil { return cty.DynamicPseudoType, nil } var atys map[string]cty.Type for i := 0; i < l; i++ { // Read the map key first. We require maps to be strings, but msgpack // doesn't so we're prepared to error here if not. k, err := dec.DecodeString() if err != nil { return cty.DynamicPseudoType, err } aty, err := impliedType(dec) if err != nil { return cty.DynamicPseudoType, err } if atys == nil { atys = make(map[string]cty.Type) } atys[k] = aty } if len(atys) == 0 { return cty.EmptyObject, nil } return cty.Object(atys), nil } func impliedTupleType(dec *msgpack.Decoder) (cty.Type, error) { // If we get in here then we've already peeked the next code and know // it's some sort of array. l, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicPseudoType, nil } if l == 0 { return cty.EmptyTuple, nil } etys := make([]cty.Type, l) for i := 0; i < l; i++ { ety, err := impliedType(dec) if err != nil { return cty.DynamicPseudoType, err } etys[i] = ety } return cty.Tuple(etys), nil } go-cty-1.12.1/cty/msgpack/type_implied_test.go000066400000000000000000000062311433256746400213140ustar00rootroot00000000000000package msgpack import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestImpliedType(t *testing.T) { tests := []struct { Input string Want cty.Type }{ { "\xc0", cty.DynamicPseudoType, }, { "\x01", // positive fixnum cty.Number, }, { "\xff", // negative fixnum cty.Number, }, { "\xcc\x04", // uint8 cty.Number, }, { "\xcd\x00\x04", // uint16 cty.Number, }, { "\xce\x00\x04\x02\x01", // uint32 cty.Number, }, { "\xcf\x00\x04\x02\x01\x00\x04\x02\x01", // uint64 cty.Number, }, { "\xd0\x04", // int8 cty.Number, }, { "\xd1\x00\x04", // int16 cty.Number, }, { "\xd2\x00\x04\x02\x01", // int32 cty.Number, }, { "\xd3\x00\x04\x02\x01\x00\x04\x02\x01", // int64 cty.Number, }, { "\xca\x01\x01\x01\x01", // float32 cty.Number, }, { "\xcb\x01\x01\x01\x01\x01\x01\x01\x01", // float64 cty.Number, }, { "\xd4\x00\x00", // fixext1 (unknown value) cty.DynamicPseudoType, }, { "\xd5\x00\x00\x00", // fixext2 (unknown value) cty.DynamicPseudoType, }, { "\xa0", // fixstr (length zero) cty.String, }, { "\xa1\xff", // fixstr (length one) cty.String, }, { "\xd9\x00", // str8 (length zero) cty.String, }, { "\xd9\x01\xff", // str8 (length one) cty.String, }, { "\xda\x00\x00", // str16 (length zero) cty.String, }, { "\xda\x00\x01\xff", // str16 (length one) cty.String, }, { "\xdb\x00\x00\x00\x00", // str32 (length zero) cty.String, }, { "\xdb\x00\x00\x00\x01\xff", // str32 (length one) cty.String, }, { "\xc2", // false cty.Bool, }, { "\xc3", // true cty.Bool, }, { "\x90", // fixarray (length zero) cty.EmptyTuple, }, { "\x91\xa0", // fixarray (length one, element is empty string) cty.Tuple([]cty.Type{cty.String}), }, { "\xdc\x00\x00", // array16 (length zero) cty.EmptyTuple, }, { "\xdc\x00\x01\xc2", // array16 (length one, element is bool) cty.Tuple([]cty.Type{cty.Bool}), }, { "\xdd\x00\x00\x00\x00", // array32 (length zero) cty.EmptyTuple, }, { "\xdd\x00\x00\x00\x01\xc2", // array32 (length one, element is bool) cty.Tuple([]cty.Type{cty.Bool}), }, { "\x80", // fixmap (length zero) cty.EmptyObject, }, { "\x81\xa1a\xc2", // fixmap (length one, "a" => bool) cty.Object(map[string]cty.Type{"a": cty.Bool}), }, { "\xde\x00\x00", // map16 (length zero) cty.EmptyObject, }, { "\xde\x00\x01\xa1a\xc2", // map16 (length one, "a" => bool) cty.Object(map[string]cty.Type{"a": cty.Bool}), }, { "\xdf\x00\x00\x00\x00", // map32 (length zero) cty.EmptyObject, }, { "\xdf\x00\x00\x00\x01\xa1a\xc2", // map32 (length one, "a" => bool) cty.Object(map[string]cty.Type{"a": cty.Bool}), }, } for _, test := range tests { t.Run(fmt.Sprintf("%x", test.Input), func(t *testing.T) { got, err := ImpliedType([]byte(test.Input)) if err != nil { t.Fatalf("unexpected error: %s", err) } if !got.Equals(test.Want) { t.Errorf( "wrong type\ninput: %q\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) } }) } } go-cty-1.12.1/cty/msgpack/unknown.go000066400000000000000000000010061433256746400172630ustar00rootroot00000000000000package msgpack type unknownType struct{} var unknownVal = unknownType{} // unknownValBytes is the raw bytes of the msgpack fixext1 value we // write to represent an unknown value. It's an extension value of // type zero whose value is irrelevant. Since it's irrelevant, we // set it to a single byte whose value is also zero, since that's // the most compact possible representation. var unknownValBytes = []byte{0xd4, 0, 0} func (uv unknownType) MarshalMsgpack() ([]byte, error) { return unknownValBytes, nil } go-cty-1.12.1/cty/msgpack/unmarshal.go000066400000000000000000000204531433256746400175650ustar00rootroot00000000000000package msgpack import ( "bytes" "github.com/vmihailenco/msgpack/v4" msgpackCodes "github.com/vmihailenco/msgpack/v4/codes" "github.com/zclconf/go-cty/cty" ) // Unmarshal interprets the given bytes as a msgpack-encoded cty Value of // the given type, returning the result. // // If an error is returned, the error is written with a hypothetical // end-user that wrote the msgpack file as its audience, using cty type // system concepts rather than Go type system concepts. func Unmarshal(b []byte, ty cty.Type) (cty.Value, error) { r := bytes.NewReader(b) dec := msgpack.NewDecoder(r) var path cty.Path return unmarshal(dec, ty, path) } func unmarshal(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) { peek, err := dec.PeekCode() if err != nil { return cty.DynamicVal, path.NewError(err) } if msgpackCodes.IsExt(peek) { // We just assume _all_ extensions are unknown values, // since we don't have any other extensions. dec.Skip() // skip what we've peeked return cty.UnknownVal(ty), nil } if ty == cty.DynamicPseudoType { return unmarshalDynamic(dec, path) } if peek == msgpackCodes.Nil { dec.Skip() // skip what we've peeked return cty.NullVal(ty), nil } switch { case ty.IsPrimitiveType(): val, err := unmarshalPrimitive(dec, ty, path) if err != nil { return cty.NilVal, err } return val, nil case ty.IsListType(): return unmarshalList(dec, ty.ElementType(), path) case ty.IsSetType(): return unmarshalSet(dec, ty.ElementType(), path) case ty.IsMapType(): return unmarshalMap(dec, ty.ElementType(), path) case ty.IsTupleType(): return unmarshalTuple(dec, ty.TupleElementTypes(), path) case ty.IsObjectType(): return unmarshalObject(dec, ty.AttributeTypes(), path) default: return cty.NilVal, path.NewErrorf("unsupported type %s", ty.FriendlyName()) } } func unmarshalPrimitive(dec *msgpack.Decoder, ty cty.Type, path cty.Path) (cty.Value, error) { switch ty { case cty.Bool: rv, err := dec.DecodeBool() if err != nil { return cty.DynamicVal, path.NewErrorf("bool is required") } return cty.BoolVal(rv), nil case cty.Number: // Marshal will try int and float first, if the value can be // losslessly represented in these encodings, and then fall // back on a string if the number is too large or too precise. peek, err := dec.PeekCode() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } if msgpackCodes.IsFixedNum(peek) { rv, err := dec.DecodeInt64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberIntVal(rv), nil } switch peek { case msgpackCodes.Int8, msgpackCodes.Int16, msgpackCodes.Int32, msgpackCodes.Int64: rv, err := dec.DecodeInt64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberIntVal(rv), nil case msgpackCodes.Uint8, msgpackCodes.Uint16, msgpackCodes.Uint32, msgpackCodes.Uint64: rv, err := dec.DecodeUint64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberUIntVal(rv), nil case msgpackCodes.Float, msgpackCodes.Double: rv, err := dec.DecodeFloat64() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return cty.NumberFloatVal(rv), nil default: rv, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } v, err := cty.ParseNumberVal(rv) if err != nil { return cty.DynamicVal, path.NewErrorf("number is required") } return v, nil } case cty.String: rv, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path.NewErrorf("string is required") } return cty.StringVal(rv), nil default: // should never happen panic("unsupported primitive type") } } func unmarshalList(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a list is required") } switch { case length < 0: return cty.NullVal(cty.List(ety)), nil case length == 0: return cty.ListValEmpty(ety), nil } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.ListVal(vals), nil } func unmarshalSet(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a set is required") } switch { case length < 0: return cty.NullVal(cty.Set(ety)), nil case length == 0: return cty.SetValEmpty(ety), nil } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.SetVal(vals), nil } func unmarshalMap(dec *msgpack.Decoder, ety cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeMapLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a map is required") } switch { case length < 0: return cty.NullVal(cty.Map(ety)), nil case length == 0: return cty.MapValEmpty(ety), nil } vals := make(map[string]cty.Value, length) path = append(path, nil) for i := 0; i < length; i++ { key, err := dec.DecodeString() if err != nil { path[:len(path)-1].NewErrorf("non-string key in map") } path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(key), } val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals[key] = val } return cty.MapVal(vals), nil } func unmarshalTuple(dec *msgpack.Decoder, etys []cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewErrorf("a tuple is required") } switch { case length < 0: return cty.NullVal(cty.Tuple(etys)), nil case length == 0: return cty.TupleVal(nil), nil case length != len(etys): return cty.DynamicVal, path.NewErrorf("a tuple of length %d is required", len(etys)) } vals := make([]cty.Value, 0, length) path = append(path, nil) for i := 0; i < length; i++ { path[len(path)-1] = cty.IndexStep{ Key: cty.NumberIntVal(int64(i)), } ety := etys[i] val, err := unmarshal(dec, ety, path) if err != nil { return cty.DynamicVal, err } vals = append(vals, val) } return cty.TupleVal(vals), nil } func unmarshalObject(dec *msgpack.Decoder, atys map[string]cty.Type, path cty.Path) (cty.Value, error) { length, err := dec.DecodeMapLen() if err != nil { return cty.DynamicVal, path.NewErrorf("an object is required") } switch { case length < 0: return cty.NullVal(cty.Object(atys)), nil case length == 0: return cty.ObjectVal(nil), nil case length != len(atys): return cty.DynamicVal, path.NewErrorf("an object with %d attributes is required (%d given)", len(atys), length) } vals := make(map[string]cty.Value, length) path = append(path, nil) for i := 0; i < length; i++ { key, err := dec.DecodeString() if err != nil { return cty.DynamicVal, path[:len(path)-1].NewErrorf("all keys must be strings") } path[len(path)-1] = cty.IndexStep{ Key: cty.StringVal(key), } aty, exists := atys[key] if !exists { return cty.DynamicVal, path.NewErrorf("unsupported attribute") } val, err := unmarshal(dec, aty, path) if err != nil { return cty.DynamicVal, err } vals[key] = val } return cty.ObjectVal(vals), nil } func unmarshalDynamic(dec *msgpack.Decoder, path cty.Path) (cty.Value, error) { length, err := dec.DecodeArrayLen() if err != nil { return cty.DynamicVal, path.NewError(err) } switch { case length == -1: return cty.NullVal(cty.DynamicPseudoType), nil case length != 2: return cty.DynamicVal, path.NewErrorf( "dynamic value array must have exactly two elements", ) } typeJSON, err := dec.DecodeBytes() if err != nil { return cty.DynamicVal, path.NewError(err) } var ty cty.Type err = (&ty).UnmarshalJSON(typeJSON) if err != nil { return cty.DynamicVal, path.NewError(err) } return unmarshal(dec, ty, path) } go-cty-1.12.1/cty/null.go000066400000000000000000000007401433256746400151150ustar00rootroot00000000000000package cty // NullVal returns a null value of the given type. A null can be created of any // type, but operations on such values will always panic. Calling applications // are encouraged to use nulls only sparingly, particularly when user-provided // expressions are to be evaluated, since the precence of nulls creates a // much higher chance of evaluation errors that can't be caught by a type // checker. func NullVal(t Type) Value { return Value{ ty: t, v: nil, } } go-cty-1.12.1/cty/object_type.go000066400000000000000000000156441433256746400164630ustar00rootroot00000000000000package cty import ( "fmt" "sort" ) type typeObject struct { typeImplSigil AttrTypes map[string]Type AttrOptional map[string]struct{} } // Object creates an object type with the given attribute types. // // After a map is passed to this function the caller must no longer access it, // since ownership is transferred to this library. func Object(attrTypes map[string]Type) Type { return ObjectWithOptionalAttrs(attrTypes, nil) } // ObjectWithOptionalAttrs creates an object type where some of its attributes // are optional. // // This function is EXPERIMENTAL. The behavior of the function or of any other // functions working either directly or indirectly with a type created by // this function is not currently considered as a compatibility constraint, and // is subject to change even in minor-version releases of this module. Other // modules that work with cty types and values may or may not support object // types with optional attributes; if they do not, their behavior when // receiving one may be non-ideal. // // Optional attributes are significant only when an object type is being used // as a target type for conversion in the "convert" package. A value of an // object type always has a value for each of the attributes in the attribute // types table, with optional values replaced with null during conversion. // // All keys in the optional slice must also exist in the attrTypes map. If not, // this function will panic. // // After a map or array is passed to this function the caller must no longer // access it, since ownership is transferred to this library. func ObjectWithOptionalAttrs(attrTypes map[string]Type, optional []string) Type { attrTypesNorm := make(map[string]Type, len(attrTypes)) for k, v := range attrTypes { attrTypesNorm[NormalizeString(k)] = v } var optionalSet map[string]struct{} if len(optional) > 0 { optionalSet = make(map[string]struct{}, len(optional)) for _, k := range optional { k = NormalizeString(k) if _, exists := attrTypesNorm[k]; !exists { panic(fmt.Sprintf("optional contains undeclared attribute %q", k)) } optionalSet[k] = struct{}{} } } return Type{ typeObject{ AttrTypes: attrTypesNorm, AttrOptional: optionalSet, }, } } func (t typeObject) Equals(other Type) bool { if ot, ok := other.typeImpl.(typeObject); ok { if len(t.AttrTypes) != len(ot.AttrTypes) { // Fast path: if we don't have the same number of attributes // then we can't possibly be equal. This also avoids the need // to test attributes in both directions below, since we know // there can't be extras in "other". return false } for attr, ty := range t.AttrTypes { oty, ok := ot.AttrTypes[attr] if !ok { return false } if !oty.Equals(ty) { return false } _, opt := t.AttrOptional[attr] _, oopt := ot.AttrOptional[attr] if opt != oopt { return false } } return true } return false } func (t typeObject) FriendlyName(mode friendlyTypeNameMode) string { // There isn't really a friendly way to write an object type due to its // complexity, so we'll just do something English-ish. Callers will // probably want to make some extra effort to avoid ever printing out // an object type FriendlyName in its entirety. For example, could // produce an error message by diffing two object types and saying // something like "Expected attribute foo to be string, but got number". // TODO: Finish this return "object" } func (t typeObject) GoString() string { if len(t.AttrTypes) == 0 { return "cty.EmptyObject" } if len(t.AttrOptional) > 0 { var opt []string for k := range t.AttrOptional { opt = append(opt, k) } sort.Strings(opt) return fmt.Sprintf("cty.ObjectWithOptionalAttrs(%#v, %#v)", t.AttrTypes, opt) } return fmt.Sprintf("cty.Object(%#v)", t.AttrTypes) } // EmptyObject is a shorthand for Object(map[string]Type{}), to more // easily talk about the empty object type. var EmptyObject Type // EmptyObjectVal is the only possible non-null, non-unknown value of type // EmptyObject. var EmptyObjectVal Value func init() { EmptyObject = Object(map[string]Type{}) EmptyObjectVal = Value{ ty: EmptyObject, v: map[string]interface{}{}, } } // IsObjectType returns true if the given type is an object type, regardless // of its element type. func (t Type) IsObjectType() bool { _, ok := t.typeImpl.(typeObject) return ok } // HasAttribute returns true if the receiver has an attribute with the given // name, regardless of its type. Will panic if the reciever isn't an object // type; use IsObjectType to determine whether this operation will succeed. func (t Type) HasAttribute(name string) bool { name = NormalizeString(name) if ot, ok := t.typeImpl.(typeObject); ok { _, hasAttr := ot.AttrTypes[name] return hasAttr } panic("HasAttribute on non-object Type") } // AttributeType returns the type of the attribute with the given name. Will // panic if the receiver is not an object type (use IsObjectType to confirm) // or if the object type has no such attribute (use HasAttribute to confirm). func (t Type) AttributeType(name string) Type { name = NormalizeString(name) if ot, ok := t.typeImpl.(typeObject); ok { aty, hasAttr := ot.AttrTypes[name] if !hasAttr { panic("no such attribute") } return aty } panic("AttributeType on non-object Type") } // AttributeTypes returns a map from attribute names to their associated // types. Will panic if the receiver is not an object type (use IsObjectType // to confirm). // // The returned map is part of the internal state of the type, and is provided // for read access only. It is forbidden for any caller to modify the returned // map. For many purposes the attribute-related methods of Value are more // appropriate and more convenient to use. func (t Type) AttributeTypes() map[string]Type { if ot, ok := t.typeImpl.(typeObject); ok { return ot.AttrTypes } panic("AttributeTypes on non-object Type") } // OptionalAttributes returns a map representing the set of attributes // that are optional. Will panic if the receiver is not an object type // (use IsObjectType to confirm). // // The returned map is part of the internal state of the type, and is provided // for read access only. It is forbidden for any caller to modify the returned // map. func (t Type) OptionalAttributes() map[string]struct{} { if ot, ok := t.typeImpl.(typeObject); ok { return ot.AttrOptional } panic("OptionalAttributes on non-object Type") } // AttributeOptional returns true if the attribute of the given name is // optional. // // Will panic if the receiver is not an object type (use IsObjectType to // confirm) or if the object type has no such attribute (use HasAttribute to // confirm). func (t Type) AttributeOptional(name string) bool { name = NormalizeString(name) if ot, ok := t.typeImpl.(typeObject); ok { if _, hasAttr := ot.AttrTypes[name]; !hasAttr { panic("no such attribute") } _, exists := ot.AttrOptional[name] return exists } panic("AttributeDefaultValue on non-object Type") } go-cty-1.12.1/cty/object_type_test.go000066400000000000000000000047441433256746400175210ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestObjectTypeEquals(t *testing.T) { tests := []struct { LHS Type // Must be typeObject RHS Type Expected bool }{ { Object(map[string]Type{}), Object(map[string]Type{}), true, }, { Object(map[string]Type{ "name": String, }), Object(map[string]Type{ "name": String, }), true, }, { // Attribute names should be normalized Object(map[string]Type{ "h\u00e9llo": String, // precombined é }), Object(map[string]Type{ "he\u0301llo": String, // e with combining acute accent }), true, }, { Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, }), }), Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, }), }), true, }, { Object(map[string]Type{ "name": String, }), Object(map[string]Type{}), false, }, { Object(map[string]Type{ "name": String, }), Object(map[string]Type{ "name": Number, }), false, }, { Object(map[string]Type{ "name": String, }), Object(map[string]Type{ "nombre": String, }), false, }, { Object(map[string]Type{ "name": String, }), Object(map[string]Type{ "name": String, "age": Number, }), false, }, { Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, }), }), Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, "age": Number, }), }), false, }, { ObjectWithOptionalAttrs( map[string]Type{ "person": Bool, }, []string{"person"}, ), ObjectWithOptionalAttrs( map[string]Type{ "person": Bool, }, []string{"person"}, ), true, }, { Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, }), }), ObjectWithOptionalAttrs( map[string]Type{ "person": Bool, }, []string{"person"}, ), false, }, { ObjectWithOptionalAttrs( map[string]Type{ "person": Bool, }, []string{"person"}, ), Object(map[string]Type{ "person": Object(map[string]Type{ "name": String, }), }), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Equals(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Equals(test.RHS) if got != test.Expected { t.Errorf("Equals returned %#v; want %#v", got, test.Expected) } }) } } go-cty-1.12.1/cty/path.go000066400000000000000000000165011433256746400151010ustar00rootroot00000000000000package cty import ( "errors" "fmt" ) // A Path is a sequence of operations to locate a nested value within a // data structure. // // The empty Path represents the given item. Any PathSteps within represent // taking a single step down into a data structure. // // Path has some convenience methods for gradually constructing a path, // but callers can also feel free to just produce a slice of PathStep manually // and convert to this type, which may be more appropriate in environments // where memory pressure is a concern. // // Although a Path is technically mutable, by convention callers should not // mutate a path once it has been built and passed to some other subsystem. // Instead, use Copy and then mutate the copy before using it. type Path []PathStep // PathStep represents a single step down into a data structure, as part // of a Path. PathStep is a closed interface, meaning that the only // permitted implementations are those within this package. type PathStep interface { pathStepSigil() pathStepImpl Apply(Value) (Value, error) } // embed pathImpl into a struct to declare it a PathStep implementation type pathStepImpl struct{} func (p pathStepImpl) pathStepSigil() pathStepImpl { return p } // Index returns a new Path that is the reciever with an IndexStep appended // to the end. // // This is provided as a convenient way to construct paths, but each call // will create garbage so it should not be used where memory pressure is a // concern. func (p Path) Index(v Value) Path { ret := make(Path, len(p)+1) copy(ret, p) ret[len(p)] = IndexStep{ Key: v, } return ret } // IndexInt is a typed convenience method for Index. func (p Path) IndexInt(v int) Path { return p.Index(NumberIntVal(int64(v))) } // IndexString is a typed convenience method for Index. func (p Path) IndexString(v string) Path { return p.Index(StringVal(v)) } // IndexPath is a convenience method to start a new Path with an IndexStep. func IndexPath(v Value) Path { return Path{}.Index(v) } // IndexIntPath is a typed convenience method for IndexPath. func IndexIntPath(v int) Path { return IndexPath(NumberIntVal(int64(v))) } // IndexStringPath is a typed convenience method for IndexPath. func IndexStringPath(v string) Path { return IndexPath(StringVal(v)) } // GetAttr returns a new Path that is the reciever with a GetAttrStep appended // to the end. // // This is provided as a convenient way to construct paths, but each call // will create garbage so it should not be used where memory pressure is a // concern. func (p Path) GetAttr(name string) Path { ret := make(Path, len(p)+1) copy(ret, p) ret[len(p)] = GetAttrStep{ Name: name, } return ret } // Equals compares 2 Paths for exact equality. func (p Path) Equals(other Path) bool { if len(p) != len(other) { return false } for i := range p { pv := p[i] switch pv := pv.(type) { case GetAttrStep: ov, ok := other[i].(GetAttrStep) if !ok || pv != ov { return false } case IndexStep: ov, ok := other[i].(IndexStep) if !ok { return false } if !pv.Key.RawEquals(ov.Key) { return false } default: // Any invalid steps default to evaluating false. return false } } return true } // HasPrefix determines if the path p contains the provided prefix. func (p Path) HasPrefix(prefix Path) bool { if len(prefix) > len(p) { return false } return p[:len(prefix)].Equals(prefix) } // GetAttrPath is a convenience method to start a new Path with a GetAttrStep. func GetAttrPath(name string) Path { return Path{}.GetAttr(name) } // Apply applies each of the steps in turn to successive values starting with // the given value, and returns the result. If any step returns an error, // the whole operation returns an error. func (p Path) Apply(val Value) (Value, error) { var err error for i, step := range p { val, err = step.Apply(val) if err != nil { return NilVal, fmt.Errorf("at step %d: %s", i, err) } } return val, nil } // LastStep applies the given path up to the last step and then returns // the resulting value and the final step. // // This is useful when dealing with assignment operations, since in that // case the *value* of the last step is not important (and may not, in fact, // present at all) and we care only about its location. // // Since LastStep applies all steps except the last, it will return errors // for those steps in the same way as Apply does. // // If the path has *no* steps then the returned PathStep will be nil, // representing that any operation should be applied directly to the // given value. func (p Path) LastStep(val Value) (Value, PathStep, error) { var err error if len(p) == 0 { return val, nil, nil } journey := p[:len(p)-1] val, err = journey.Apply(val) if err != nil { return NilVal, nil, err } return val, p[len(p)-1], nil } // Copy makes a shallow copy of the receiver. Often when paths are passed to // caller code they come with the constraint that they are valid only until // the caller returns, due to how they are constructed internally. Callers // can use Copy to conveniently produce a copy of the value that _they_ control // the validity of. func (p Path) Copy() Path { ret := make(Path, len(p)) copy(ret, p) return ret } // IndexStep is a Step implementation representing applying the index operation // to a value, which must be of either a list, map, or set type. // // When describing a path through a *type* rather than a concrete value, // the Key may be an unknown value, indicating that the step applies to // *any* key of the given type. // // When indexing into a set, the Key is actually the element being accessed // itself, since in sets elements are their own identity. type IndexStep struct { pathStepImpl Key Value } // Apply returns the value resulting from indexing the given value with // our key value. func (s IndexStep) Apply(val Value) (Value, error) { if val == NilVal || val.IsNull() { return NilVal, errors.New("cannot index a null value") } switch s.Key.Type() { case Number: if !(val.Type().IsListType() || val.Type().IsTupleType()) { return NilVal, errors.New("not a list type") } case String: if !val.Type().IsMapType() { return NilVal, errors.New("not a map type") } default: return NilVal, errors.New("key value not number or string") } has := val.HasIndex(s.Key) if !has.IsKnown() { return UnknownVal(val.Type().ElementType()), nil } if !has.True() { return NilVal, errors.New("value does not have given index key") } return val.Index(s.Key), nil } func (s IndexStep) GoString() string { return fmt.Sprintf("cty.IndexStep{Key:%#v}", s.Key) } // GetAttrStep is a Step implementation representing retrieving an attribute // from a value, which must be of an object type. type GetAttrStep struct { pathStepImpl Name string } // Apply returns the value of our named attribute from the given value, which // must be of an object type that has a value of that name. func (s GetAttrStep) Apply(val Value) (Value, error) { if val == NilVal || val.IsNull() { return NilVal, errors.New("cannot access attributes on a null value") } if !val.Type().IsObjectType() { return NilVal, errors.New("not an object type") } if !val.Type().HasAttribute(s.Name) { return NilVal, fmt.Errorf("object has no attribute %q", s.Name) } return val.GetAttr(s.Name), nil } func (s GetAttrStep) GoString() string { return fmt.Sprintf("cty.GetAttrStep{Name:%q}", s.Name) } go-cty-1.12.1/cty/path_set.go000066400000000000000000000122431433256746400157530ustar00rootroot00000000000000package cty import ( "fmt" "hash/crc64" "github.com/zclconf/go-cty/cty/set" ) // PathSet represents a set of Path objects. This can be used, for example, // to talk about a subset of paths within a value that meet some criteria, // without directly modifying the values at those paths. type PathSet struct { set set.Set[Path] } // NewPathSet creates and returns a PathSet, with initial contents optionally // set by the given arguments. func NewPathSet(paths ...Path) PathSet { ret := PathSet{ set: set.NewSet(set.Rules[Path](pathSetRules{})), } for _, path := range paths { ret.Add(path) } return ret } // Add inserts a single given path into the set. // // Paths are immutable after construction by convention. It is particularly // important not to mutate a path after it has been placed into a PathSet. // If a Path is mutated while in a set, behavior is undefined. func (s PathSet) Add(path Path) { s.set.Add(path) } // AddAllSteps is like Add but it also adds all of the steps leading to // the given path. // // For example, if given a path representing "foo.bar", it will add both // "foo" and "bar". func (s PathSet) AddAllSteps(path Path) { for i := 1; i <= len(path); i++ { s.Add(path[:i]) } } // Has returns true if the given path is in the receiving set. func (s PathSet) Has(path Path) bool { return s.set.Has(path) } // List makes and returns a slice of all of the paths in the receiving set, // in an undefined but consistent order. func (s PathSet) List() []Path { if s.Empty() { return nil } ret := make([]Path, 0, s.set.Length()) for it := s.set.Iterator(); it.Next(); { ret = append(ret, it.Value()) } return ret } // Remove modifies the receving set to no longer include the given path. // If the given path was already absent, this is a no-op. func (s PathSet) Remove(path Path) { s.set.Remove(path) } // Empty returns true if the length of the receiving set is zero. func (s PathSet) Empty() bool { return s.set.Length() == 0 } // Union returns a new set whose contents are the union of the receiver and // the given other set. func (s PathSet) Union(other PathSet) PathSet { return PathSet{ set: s.set.Union(other.set), } } // Intersection returns a new set whose contents are the intersection of the // receiver and the given other set. func (s PathSet) Intersection(other PathSet) PathSet { return PathSet{ set: s.set.Intersection(other.set), } } // Subtract returns a new set whose contents are those from the receiver with // any elements of the other given set subtracted. func (s PathSet) Subtract(other PathSet) PathSet { return PathSet{ set: s.set.Subtract(other.set), } } // SymmetricDifference returns a new set whose contents are the symmetric // difference of the receiver and the given other set. func (s PathSet) SymmetricDifference(other PathSet) PathSet { return PathSet{ set: s.set.SymmetricDifference(other.set), } } // Equal returns true if and only if both the receiver and the given other // set contain exactly the same paths. func (s PathSet) Equal(other PathSet) bool { if s.set.Length() != other.set.Length() { return false } // Now we know the lengths are the same we only need to test in one // direction whether everything in one is in the other. for it := s.set.Iterator(); it.Next(); { if !other.set.Has(it.Value()) { return false } } return true } var crc64Table = crc64.MakeTable(crc64.ISO) var indexStepPlaceholder = []byte("#") // pathSetRules is an implementation of set.Rules from the set package, // used internally within PathSet. type pathSetRules struct { } func (r pathSetRules) Hash(path Path) int { hash := crc64.New(crc64Table) for _, rawStep := range path { switch step := rawStep.(type) { case GetAttrStep: // (this creates some garbage converting the string name to a // []byte, but that's okay since cty is not designed to be // used in tight loops under memory pressure.) hash.Write([]byte(step.Name)) default: // For any other step type we just append a predefined value, // which means that e.g. all indexes into a given collection will // hash to the same value but we assume that collections are // small and thus this won't hurt too much. hash.Write(indexStepPlaceholder) } } // We discard half of the hash on 32-bit platforms; collisions just make // our lookups take marginally longer, so not a big deal. return int(hash.Sum64()) } func (r pathSetRules) Equivalent(aPath, bPath Path) bool { if len(aPath) != len(bPath) { return false } for i := range aPath { switch aStep := aPath[i].(type) { case GetAttrStep: bStep, ok := bPath[i].(GetAttrStep) if !ok { return false } if aStep.Name != bStep.Name { return false } case IndexStep: bStep, ok := bPath[i].(IndexStep) if !ok { return false } eq := aStep.Key.Equals(bStep.Key) if !eq.IsKnown() || eq.False() { return false } default: // Should never happen, since we document PathStep as a closed type. panic(fmt.Errorf("unsupported step type %T", aStep)) } } return true } // SameRules is true if both Rules instances are pathSetRules structs. func (r pathSetRules) SameRules(other set.Rules[Path]) bool { _, ok := other.(pathSetRules) return ok } go-cty-1.12.1/cty/path_set_test.go000066400000000000000000000040041433256746400170060ustar00rootroot00000000000000package cty import ( "reflect" "testing" ) func TestPathSet(t *testing.T) { helloWorld := Path{ GetAttrStep{Name: "hello"}, GetAttrStep{Name: "world"}, } s := NewPathSet(helloWorld) if got, want := s.Has(helloWorld), true; got != want { t.Errorf("set does not have hello.world; should have it") } if got, want := s.Has(helloWorld[:1]), false; got != want { t.Errorf("set has hello; should not have it") } if got, want := s.List(), []Path{helloWorld}; !reflect.DeepEqual(got, want) { t.Errorf("wrong list result\ngot: %#v\nwant: %#v", got, want) } fooBarBaz := Path{ GetAttrStep{Name: "foo"}, IndexStep{Key: StringVal("bar")}, GetAttrStep{Name: "baz"}, } s.AddAllSteps(fooBarBaz) if got, want := s.Has(helloWorld), true; got != want { t.Errorf("set does not have hello.world; should have it") } if got, want := s.Has(fooBarBaz), true; got != want { t.Errorf("set does not have foo['bar'].baz; should have it") } if got, want := s.Has(fooBarBaz[:2]), true; got != want { t.Errorf("set does not have foo['bar']; should have it") } if got, want := s.Has(fooBarBaz[:1]), true; got != want { t.Errorf("set does not have foo; should have it") } s.Remove(fooBarBaz[:2]) if got, want := s.Has(fooBarBaz[:2]), false; got != want { t.Errorf("set has foo['bar']; should not have it") } if got, want := s.Has(fooBarBaz), true; got != want { t.Errorf("set does not have foo['bar'].baz; should have it") } if got, want := s.Has(fooBarBaz[:1]), true; got != want { t.Errorf("set does not have foo; should have it") } new := NewPathSet(s.List()...) if got, want := s.Equal(new), true; got != want { t.Errorf("new set does not equal original; want equal sets") } new.Remove(helloWorld) if got, want := s.Equal(new), false; got != want { t.Errorf("new set equals original; want non-equal sets") } new.Add(Path{ GetAttrStep{Name: "goodbye"}, GetAttrStep{Name: "world"}, }) if got, want := s.Equal(new), false; got != want { t.Errorf("new set equals original; want non-equal sets") } } go-cty-1.12.1/cty/path_test.go000066400000000000000000000152011433256746400161340ustar00rootroot00000000000000package cty_test import ( "fmt" "testing" "github.com/zclconf/go-cty/cty" ) func TestPathApply(t *testing.T) { tests := []struct { Start cty.Value Path cty.Path Want cty.Value WantErr string }{ { cty.StringVal("hello"), nil, cty.StringVal("hello"), ``, }, { cty.StringVal("hello"), (cty.Path)(nil).Index(cty.StringVal("boop")), cty.NilVal, `at step 0: not a map type`, }, { cty.StringVal("hello"), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.NilVal, `at step 0: not a list type`, }, { cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.StringVal("hello"), ``, }, { cty.TupleVal([]cty.Value{ cty.StringVal("hello"), }), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.StringVal("hello"), ``, }, { cty.ListValEmpty(cty.String), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.NilVal, `at step 0: value does not have given index key`, }, { cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), (cty.Path)(nil).Index(cty.NumberIntVal(1)), cty.NilVal, `at step 0: value does not have given index key`, }, { cty.ListVal([]cty.Value{ cty.StringVal("hello"), }), (cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"), cty.NilVal, `at step 1: not an object type`, }, { cty.ListVal([]cty.Value{ cty.EmptyObjectVal, }), (cty.Path)(nil).Index(cty.NumberIntVal(0)).GetAttr("foo"), cty.NilVal, `at step 1: object has no attribute "foo"`, }, { cty.NullVal(cty.List(cty.String)), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.NilVal, `at step 0: cannot index a null value`, }, { cty.NullVal(cty.Map(cty.String)), (cty.Path)(nil).Index(cty.NumberIntVal(0)), cty.NilVal, `at step 0: cannot index a null value`, }, { cty.NullVal(cty.EmptyObject), (cty.Path)(nil).GetAttr("foo"), cty.NilVal, `at step 0: cannot access attributes on a null value`, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v %#v", test.Start, test.Path), func(t *testing.T) { got, gotErr := test.Path.Apply(test.Start) t.Logf("testing path apply\nstart: %#v\npath: %#v", test.Start, test.Path) if test.WantErr != "" { if gotErr == nil { t.Fatalf("succeeded, but want error\nwant error: %s", test.WantErr) } if gotErrStr := gotErr.Error(); gotErrStr != test.WantErr { t.Fatalf("wrong error\ngot error: %s\nwant error: %s", gotErrStr, test.WantErr) } return } if gotErr != nil { t.Fatalf("failed, but want success\ngot error: %s", gotErr.Error()) } if !test.Want.RawEquals(got) { t.Fatalf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) } }) } } func TestPathEquals(t *testing.T) { tests := []struct { A, B cty.Path Equal bool Prefix bool }{ { A: nil, B: nil, Equal: true, Prefix: true, }, { A: cty.Path{}, B: cty.Path{}, Equal: true, Prefix: true, }, { A: cty.Path{nil}, B: cty.Path{cty.GetAttrStep{Name: "attr"}}, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.UnknownVal(cty.String)}, cty.GetAttrStep{Name: "attr"}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("key")}, cty.GetAttrStep{Name: "attr"}, }, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)})}, cty.GetAttrStep{Name: "attr"}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.ListVal([]cty.Value{cty.StringVal("known")})}, cty.GetAttrStep{Name: "attr"}, }, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.UnknownVal(cty.String)}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("known")}, cty.GetAttrStep{Name: "attr"}, }, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("known")}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("known")}, cty.GetAttrStep{Name: "attr"}, }, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("known")}, cty.GetAttrStep{Name: "attr"}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("known")}, }, Prefix: true, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.UnknownVal(cty.String)}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.UnknownVal(cty.String)}, }, Prefix: true, Equal: true, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.NumberFloatVal(0)}, cty.GetAttrStep{Name: "attr"}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.NumberIntVal(0)}, cty.GetAttrStep{Name: "attr"}, }, Equal: true, Prefix: true, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.NumberIntVal(1)}, cty.GetAttrStep{Name: "attr"}, }, B: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.NumberIntVal(0)}, cty.GetAttrStep{Name: "attr"}, }, }, // tests for convenience methods { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, }, B: cty.GetAttrPath("attr"), Prefix: true, Equal: true, }, { A: cty.Path{ cty.IndexStep{Key: cty.NumberIntVal(0)}, }, B: cty.IndexPath(cty.NumberIntVal(0)), Prefix: true, Equal: true, }, { A: cty.Path{ cty.IndexStep{Key: cty.NumberIntVal(0)}, }, B: cty.IndexIntPath(0), Prefix: true, Equal: true, }, { A: cty.Path{ cty.IndexStep{Key: cty.StringVal("key")}, }, B: cty.IndexStringPath("key"), Prefix: true, Equal: true, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.NumberIntVal(0)}, }, B: cty.GetAttrPath("attr").IndexInt(0), Prefix: true, Equal: true, }, { A: cty.Path{ cty.GetAttrStep{Name: "attr"}, cty.IndexStep{Key: cty.StringVal("key")}, }, B: cty.GetAttrPath("attr").IndexString("key"), Prefix: true, Equal: true, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d-%#v", i, test.A), func(t *testing.T) { if test.Equal != test.A.Equals(test.B) { t.Fatalf("%#v.Equals(%#v) != %t", test.A, test.B, test.Equal) } if test.Prefix != test.A.HasPrefix(test.B) { t.Fatalf("%#v.HasPrefix(%#v) != %t", test.A, test.B, test.Prefix) } }) } } go-cty-1.12.1/cty/primitive_type.go000066400000000000000000000111661433256746400172200ustar00rootroot00000000000000package cty import "math/big" // primitiveType is the hidden implementation of the various primitive types // that are exposed as variables in this package. type primitiveType struct { typeImplSigil Kind primitiveTypeKind } type primitiveTypeKind byte const ( primitiveTypeBool primitiveTypeKind = 'B' primitiveTypeNumber primitiveTypeKind = 'N' primitiveTypeString primitiveTypeKind = 'S' ) func (t primitiveType) Equals(other Type) bool { if otherP, ok := other.typeImpl.(primitiveType); ok { return otherP.Kind == t.Kind } return false } func (t primitiveType) FriendlyName(mode friendlyTypeNameMode) string { switch t.Kind { case primitiveTypeBool: return "bool" case primitiveTypeNumber: return "number" case primitiveTypeString: return "string" default: // should never happen panic("invalid primitive type") } } func (t primitiveType) GoString() string { switch t.Kind { case primitiveTypeBool: return "cty.Bool" case primitiveTypeNumber: return "cty.Number" case primitiveTypeString: return "cty.String" default: // should never happen panic("invalid primitive type") } } // rawNumberEqual is our cty-specific definition of whether two big floats // underlying cty.Number are "equal" for the purposes of the Value.Equals and // Value.RawEquals methods. // // The built-in equality for big.Float is a direct comparison of the mantissa // bits and the exponent, but that's too precise a check for cty because we // routinely send numbers through decimal approximations and back and so // we only promise to accurately represent the subset of binary floating point // numbers that can be derived from a decimal string representation. // // In respect of the fact that cty only tries to preserve numbers that can // reasonably be written in JSON documents, we use the string representation of // a decimal approximation of the number as our comparison, relying on the // big.Float type's heuristic for discarding extraneous mantissa bits that seem // likely to only be there as a result of an earlier decimal-to-binary // approximation during parsing, e.g. in ParseNumberVal. func rawNumberEqual(a, b *big.Float) bool { switch { case (a == nil) != (b == nil): return false case a == nil: // b == nil too then, due to previous case return true case a.Sign() != b.Sign(): return false default: // This format and precision matches that used by cty/json.Marshal, // and thus achieves our definition of "two numbers are equal if // we'd use the same JSON serialization for both of them". const format = 'f' const prec = -1 aStr := a.Text(format, prec) bStr := b.Text(format, prec) // The one exception to our rule about equality-by-stringification is // negative zero, because we want -0 to always be equal to +0. const posZero = "0" const negZero = "-0" if aStr == negZero { aStr = posZero } if bStr == negZero { bStr = posZero } return aStr == bStr } } // Number is the numeric type. Number values are arbitrary-precision // decimal numbers, which can then be converted into Go's various numeric // types only if they are in the appropriate range. var Number Type // String is the string type. String values are sequences of unicode codepoints // encoded internally as UTF-8. var String Type // Bool is the boolean type. The two values of this type are True and False. var Bool Type // True is the truthy value of type Bool var True Value // False is the falsey value of type Bool var False Value // Zero is a number value representing exactly zero. var Zero Value // PositiveInfinity is a Number value representing positive infinity var PositiveInfinity Value // NegativeInfinity is a Number value representing negative infinity var NegativeInfinity Value func init() { Number = Type{ primitiveType{Kind: primitiveTypeNumber}, } String = Type{ primitiveType{Kind: primitiveTypeString}, } Bool = Type{ primitiveType{Kind: primitiveTypeBool}, } True = Value{ ty: Bool, v: true, } False = Value{ ty: Bool, v: false, } Zero = Value{ ty: Number, v: big.NewFloat(0), } PositiveInfinity = Value{ ty: Number, v: (&big.Float{}).SetInf(false), } NegativeInfinity = Value{ ty: Number, v: (&big.Float{}).SetInf(true), } } // IsPrimitiveType returns true if and only if the reciever is a primitive // type, which means it's either number, string, or bool. Any two primitive // types can be safely compared for equality using the standard == operator // without panic, which is not a guarantee that holds for all types. Primitive // types can therefore also be used in switch statements. func (t Type) IsPrimitiveType() bool { _, ok := t.typeImpl.(primitiveType) return ok } go-cty-1.12.1/cty/primitive_type_test.go000066400000000000000000000013431433256746400202530ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestTypeIsPrimitiveType(t *testing.T) { tests := []struct { Type Type Want bool }{ {String, true}, {Number, true}, {Bool, true}, {DynamicPseudoType, false}, {List(String), false}, // Make sure our primitive constants are correctly constructed {True.Type(), true}, {False.Type(), true}, {Zero.Type(), true}, {PositiveInfinity.Type(), true}, {NegativeInfinity.Type(), true}, } for i, test := range tests { t.Run(fmt.Sprintf("%d %#v", i, test.Type), func(t *testing.T) { got := test.Type.IsPrimitiveType() if got != test.Want { t.Errorf( "wrong result\ntype: %#v\ngot: %#v\nwant: %#v", test.Type, test.Want, got, ) } }) } } go-cty-1.12.1/cty/set/000077500000000000000000000000001433256746400144065ustar00rootroot00000000000000go-cty-1.12.1/cty/set/iterator.go000066400000000000000000000003171433256746400165670ustar00rootroot00000000000000package set type Iterator[T any] struct { vals []T idx int } func (it *Iterator[T]) Value() T { return it.vals[it.idx] } func (it *Iterator[T]) Next() bool { it.idx++ return it.idx < len(it.vals) } go-cty-1.12.1/cty/set/ops.go000066400000000000000000000121101433256746400155310ustar00rootroot00000000000000package set import ( "sort" ) // Add inserts the given value into the receiving Set. // // This mutates the set in-place. This operation is not thread-safe. func (s Set[T]) Add(val T) { hv := s.rules.Hash(val) if _, ok := s.vals[hv]; !ok { s.vals[hv] = make([]T, 0, 1) } bucket := s.vals[hv] // See if an equivalent value is already present for _, ev := range bucket { if s.rules.Equivalent(val, ev) { return } } s.vals[hv] = append(bucket, val) } // Remove deletes the given value from the receiving set, if indeed it was // there in the first place. If the value is not present, this is a no-op. func (s Set[T]) Remove(val T) { hv := s.rules.Hash(val) bucket, ok := s.vals[hv] if !ok { return } for i, ev := range bucket { if s.rules.Equivalent(val, ev) { newBucket := make([]T, 0, len(bucket)-1) newBucket = append(newBucket, bucket[:i]...) newBucket = append(newBucket, bucket[i+1:]...) if len(newBucket) > 0 { s.vals[hv] = newBucket } else { delete(s.vals, hv) } return } } } // Has returns true if the given value is in the receiving set, or false if // it is not. func (s Set[T]) Has(val T) bool { hv := s.rules.Hash(val) bucket, ok := s.vals[hv] if !ok { return false } for _, ev := range bucket { if s.rules.Equivalent(val, ev) { return true } } return false } // Copy performs a shallow copy of the receiving set, returning a new set // with the same rules and elements. func (s Set[T]) Copy() Set[T] { ret := NewSet(s.rules) for k, v := range s.vals { ret.vals[k] = v } return ret } // Iterator returns an iterator over values in the set. If the set's rules // implement OrderedRules then the result is ordered per those rules. If // no order is provided, or if it is not a total order, then the iteration // order is undefined but consistent for a particular version of cty. Do not // rely on specific ordering between cty releases unless the rules order is a // total order. // // The pattern for using the returned iterator is: // // it := set.Iterator() // for it.Next() { // val := it.Value() // // ... // } // // Once an iterator has been created for a set, the set *must not* be mutated // until the iterator is no longer in use. func (s Set[T]) Iterator() *Iterator[T] { vals := s.Values() return &Iterator[T]{ vals: vals, idx: -1, } } // EachValue calls the given callback once for each value in the set, in an // undefined order that callers should not depend on. func (s Set[T]) EachValue(cb func(T)) { it := s.Iterator() for it.Next() { cb(it.Value()) } } // Values returns a slice of all the values in the set. If the set rules have // an order then the result is in that order. If no order is provided or if // it is not a total order then the result order is undefined, but consistent // for a particular set value within a specific release of cty. func (s Set[T]) Values() []T { var ret []T // Sort the bucketIds to ensure that we always traverse in a // consistent order. bucketIDs := make([]int, 0, len(s.vals)) for id := range s.vals { bucketIDs = append(bucketIDs, id) } sort.Ints(bucketIDs) for _, bucketID := range bucketIDs { ret = append(ret, s.vals[bucketID]...) } if orderRules, ok := s.rules.(OrderedRules[T]); ok { sort.SliceStable(ret, func(i, j int) bool { return orderRules.Less(ret[i], ret[j]) }) } return ret } // Length returns the number of values in the set. func (s Set[T]) Length() int { var count int for _, bucket := range s.vals { count = count + len(bucket) } return count } // Union returns a new set that contains all of the members of both the // receiving set and the given set. Both sets must have the same rules, or // else this function will panic. func (s1 Set[T]) Union(s2 Set[T]) Set[T] { mustHaveSameRules(s1, s2) rs := NewSet(s1.rules) s1.EachValue(func(v T) { rs.Add(v) }) s2.EachValue(func(v T) { rs.Add(v) }) return rs } // Intersection returns a new set that contains the values that both the // receiver and given sets have in common. Both sets must have the same rules, // or else this function will panic. func (s1 Set[T]) Intersection(s2 Set[T]) Set[T] { mustHaveSameRules(s1, s2) rs := NewSet(s1.rules) s1.EachValue(func(v T) { if s2.Has(v) { rs.Add(v) } }) return rs } // Subtract returns a new set that contains all of the values from the receiver // that are not also in the given set. Both sets must have the same rules, // or else this function will panic. func (s1 Set[T]) Subtract(s2 Set[T]) Set[T] { mustHaveSameRules(s1, s2) rs := NewSet(s1.rules) s1.EachValue(func(v T) { if !s2.Has(v) { rs.Add(v) } }) return rs } // SymmetricDifference returns a new set that contains all of the values from // both the receiver and given sets, except those that both sets have in // common. Both sets must have the same rules, or else this function will // panic. func (s1 Set[T]) SymmetricDifference(s2 Set[T]) Set[T] { mustHaveSameRules(s1, s2) rs := NewSet(s1.rules) s1.EachValue(func(v T) { if !s2.Has(v) { rs.Add(v) } }) s2.EachValue(func(v T) { if !s1.Has(v) { rs.Add(v) } }) return rs } go-cty-1.12.1/cty/set/ops_test.go000066400000000000000000000206231433256746400166000ustar00rootroot00000000000000package set import ( "fmt" "reflect" "sort" "testing" ) // TestBasicSetOps tests the fundamental operations, whose implementations operate // directly on the underlying data structure. The remaining operations are implemented // in terms of these. func TestBasicSetOps(t *testing.T) { s := NewSet(newTestRules()) want := map[int][]int{} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("new set has unexpected contents %#v; want %#v", s.vals, want) } s.Add(1) want[1] = []int{1} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Add(1) set has unexpected contents %#v; want %#v", s.vals, want) } if !s.Has(1) { t.Fatalf("s.Has(1) returned false; want true") } s.Add(2) want[2] = []int{2} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Add(2) set has unexpected contents %#v; want %#v", s.vals, want) } if !s.Has(2) { t.Fatalf("s.Has(2) returned false; want true") } // Our testRules cause 17 and 33 to return the same hash value as 1, so we can use this // to test the situation where multiple values are in a bucket. if s.Has(17) { t.Fatalf("s.Has(17) returned true; want false") } s.Add(17) s.Add(33) want[1] = append(want[1], 17, 33) if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Add(17) and s.Add(33) set has unexpected contents %#v; want %#v", s.vals, want) } if !s.Has(17) { t.Fatalf("s.Has(17) returned false; want true") } if !s.Has(33) { t.Fatalf("s.Has(33) returned false; want true") } vals := make([]int, 0) s.EachValue(func(v int) { vals = append(vals, v) }) sort.Ints(vals) if want := []int{1, 2, 17, 33}; !reflect.DeepEqual(vals, want) { t.Fatalf("wrong values from EachValue %#v; want %#v", vals, want) } s.Remove(2) delete(want, 2) if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(2) set has unexpected contents %#v; want %#v", s.vals, want) } s.Remove(17) want[1] = []int{1, 33} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(17) set has unexpected contents %#v; want %#v", s.vals, want) } s.Remove(1) want[1] = []int{33} if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(1) set has unexpected contents %#v; want %#v", s.vals, want) } s.Remove(33) delete(want, 1) if !reflect.DeepEqual(s.vals, want) { t.Fatalf("after s.Remove(33) set has unexpected contents %#v; want %#v", s.vals, want) } vals = make([]int, 0) s.EachValue(func(v int) { vals = append(vals, v) }) if len(vals) > 0 { t.Fatalf("s.EachValue produced values %#v; want no calls", vals) } } func TestUnion(t *testing.T) { tests := []struct { s1 Set[int] s2 Set[int] wantValues []int }{ { NewSet(newTestRules()), NewSet(newTestRules()), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSet(newTestRules()), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{2}), []int{1, 2}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{1}), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{1}), []int{1, 17, 33}, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{2, 1}), []int{1, 2, 17, 33}, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Union(test.s2) var gotValues []int got.EachValue(func(v int) { gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) if !reflect.DeepEqual(gotValues, test.wantValues) { s1Values := test.s1.Values() s2Values := test.s2.Values() t.Errorf( "wrong result %#v for %#v union %#v; want %#v", gotValues, s1Values, s2Values, test.wantValues, ) } }) } } func TestIntersection(t *testing.T) { tests := []struct { s1 Set[int] s2 Set[int] wantValues []int }{ { NewSet(newTestRules()), NewSet(newTestRules()), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSet(newTestRules()), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{2}), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{1}), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{1, 17}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{3, 2, 1}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{1, 2, 3}, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{1}), nil, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{2, 1}), nil, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Intersection(test.s2) var gotValues []int got.EachValue(func(v int) { gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) if !reflect.DeepEqual(gotValues, test.wantValues) { s1Values := test.s1.Values() s2Values := test.s2.Values() t.Errorf( "wrong result %#v for %#v intersection %#v; want %#v", gotValues, s1Values, s2Values, test.wantValues, ) } }) } } func TestSubtract(t *testing.T) { tests := []struct { s1 Set[int] s2 Set[int] wantValues []int }{ { NewSet(newTestRules()), NewSet(newTestRules()), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSet(newTestRules()), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{2}), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{1}), nil, }, { NewSetFromSlice(newTestRules(), []int{1, 17}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{17}, }, { NewSetFromSlice(newTestRules(), []int{3, 2, 1}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), nil, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{1}), []int{17, 33}, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{2, 1}), []int{17, 33}, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.Subtract(test.s2) var gotValues []int got.EachValue(func(v int) { gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) if !reflect.DeepEqual(gotValues, test.wantValues) { s1Values := test.s1.Values() s2Values := test.s2.Values() t.Errorf( "wrong result %#v for %#v subtract %#v; want %#v", gotValues, s1Values, s2Values, test.wantValues, ) } }) } } func TestSymmetricDifference(t *testing.T) { tests := []struct { s1 Set[int] s2 Set[int] wantValues []int }{ { NewSet(newTestRules()), NewSet(newTestRules()), nil, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSet(newTestRules()), []int{1}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{2}), []int{1, 2}, }, { NewSetFromSlice(newTestRules(), []int{1}), NewSetFromSlice(newTestRules(), []int{1}), nil, }, { NewSetFromSlice(newTestRules(), []int{1, 17}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), []int{2, 3, 17}, }, { NewSetFromSlice(newTestRules(), []int{3, 2, 1}), NewSetFromSlice(newTestRules(), []int{1, 2, 3}), nil, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{1}), []int{1, 17, 33}, }, { NewSetFromSlice(newTestRules(), []int{17, 33}), NewSetFromSlice(newTestRules(), []int{2, 1}), []int{1, 2, 17, 33}, }, } for i, test := range tests { t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { got := test.s1.SymmetricDifference(test.s2) var gotValues []int got.EachValue(func(v int) { gotValues = append(gotValues, v) }) sort.Ints(gotValues) sort.Ints(test.wantValues) if !reflect.DeepEqual(gotValues, test.wantValues) { s1Values := test.s1.Values() s2Values := test.s2.Values() t.Errorf( "wrong result %#v for %#v symmetric difference %#v; want %#v", gotValues, s1Values, s2Values, test.wantValues, ) } }) } } go-cty-1.12.1/cty/set/rules.go000066400000000000000000000040401433256746400160650ustar00rootroot00000000000000package set // Rules represents the operations that define membership for a Set. // // Each Set has a Rules instance, whose methods must satisfy the interface // contracts given below for any value that will be added to the set. type Rules[T any] interface { // Hash returns an int that somewhat-uniquely identifies the given value. // // A good hash function will minimize collisions for values that will be // added to the set, though collisions *are* permitted. Collisions will // simply reduce the efficiency of operations on the set. Hash(T) int // Equivalent returns true if and only if the two values are considered // equivalent for the sake of set membership. Two values that are // equivalent cannot exist in the set at the same time, and if two // equivalent values are added it is undefined which one will be // returned when enumerating all of the set members. // // Two values that are equivalent *must* result in the same hash value, // though it is *not* required that two values with the same hash value // be equivalent. Equivalent(T, T) bool // SameRules returns true if the instance is equivalent to another Rules // instance over the same element type. SameRules(Rules[T]) bool } // OrderedRules is an extension of Rules that can apply a partial order to // element values. When a set's Rules implements OrderedRules an iterator // over the set will return items in the order described by the rules. // // If the given order is not a total order (that is, some pairs of non-equivalent // elements do not have a defined order) then the resulting iteration order // is undefined but consistent for a particular version of cty. The exact // order in that case is not part of the contract and is subject to change // between versions. type OrderedRules[T any] interface { Rules[T] // Less returns true if and only if the first argument should sort before // the second argument. If the second argument should sort before the first // or if there is no defined order for the values, return false. Less(interface{}, interface{}) bool } go-cty-1.12.1/cty/set/rules_test.go000066400000000000000000000012541433256746400171300ustar00rootroot00000000000000package set // testRules is a rules implementation that is used for testing. It only // accepts ints as values, and it has a Hash function that just returns the // given value modulo 16 so that we can easily and dependably test the // situation where two non-equivalent values have the same hash value. type testRules struct{} func newTestRules() Rules[int] { return testRules{} } func (r testRules) Hash(val int) int { return val % 16 } func (r testRules) Equivalent(val1 int, val2 int) bool { return val1 == val2 } func (r testRules) SameRules(other Rules[int]) bool { // All testRules values are equal, so type-checking is enough. _, ok := other.(testRules) return ok } go-cty-1.12.1/cty/set/set.go000066400000000000000000000032211433256746400155260ustar00rootroot00000000000000package set import ( "fmt" ) // Set is an implementation of the concept of a set: a collection where all // values are conceptually either in or out of the set, but the members are // not ordered. // // This type primarily exists to be the internal type of sets in cty, but // it is considered to be at the same level of abstraction as Go's built in // slice and map collection types, and so should make no cty-specific // assumptions. // // Set operations are not thread safe. It is the caller's responsibility to // provide mutex guarantees where necessary. // // Set operations are not optimized to minimize memory pressure. Mutating // a set will generally create garbage and so should perhaps be avoided in // tight loops where memory pressure is a concern. type Set[T any] struct { vals map[int][]T rules Rules[T] } // NewSet returns an empty set with the membership rules given. func NewSet[T any](rules Rules[T]) Set[T] { return Set[T]{ vals: map[int][]T{}, rules: rules, } } func NewSetFromSlice[T any](rules Rules[T], vals []T) Set[T] { s := NewSet(rules) for _, v := range vals { s.Add(v) } return s } func sameRules[T any](s1 Set[T], s2 Set[T]) bool { return s1.rules.SameRules(s2.rules) } func mustHaveSameRules[T any](s1 Set[T], s2 Set[T]) { if !sameRules(s1, s2) { panic(fmt.Errorf("incompatible set rules: %#v, %#v", s1.rules, s2.rules)) } } // HasRules returns true if and only if the receiving set has the given rules // instance as its rules. func (s Set[T]) HasRules(rules Rules[T]) bool { return s.rules.SameRules(rules) } // Rules returns the receiving set's rules instance. func (s Set[T]) Rules() Rules[T] { return s.rules } go-cty-1.12.1/cty/set_helper.go000066400000000000000000000101311433256746400162700ustar00rootroot00000000000000package cty import ( "fmt" "github.com/zclconf/go-cty/cty/set" ) // ValueSet is to cty.Set what []cty.Value is to cty.List and // map[string]cty.Value is to cty.Map. It's provided to allow callers a // convenient interface for manipulating sets before wrapping them in cty.Set // values using cty.SetValFromValueSet. // // Unlike value slices and value maps, ValueSet instances have a single // homogenous element type because that is a requirement of the underlying // set implementation, which uses the element type to select a suitable // hashing function. // // Set mutations are not concurrency-safe. type ValueSet struct { // ValueSet is just a thin wrapper around a set.Set with our value-oriented // "rules" applied. We do this so that the caller can work in terms of // cty.Value objects even though the set internals use the raw values. s set.Set[interface{}] } // NewValueSet creates and returns a new ValueSet with the given element type. func NewValueSet(ety Type) ValueSet { return newValueSet(set.NewSet(newSetRules(ety))) } func newValueSet(s set.Set[interface{}]) ValueSet { return ValueSet{ s: s, } } // ElementType returns the element type for the receiving ValueSet. func (s ValueSet) ElementType() Type { return s.s.Rules().(setRules).Type } // Add inserts the given value into the receiving set. func (s ValueSet) Add(v Value) { s.requireElementType(v) s.s.Add(v.v) } // Remove deletes the given value from the receiving set, if indeed it was // there in the first place. If the value is not present, this is a no-op. func (s ValueSet) Remove(v Value) { s.requireElementType(v) s.s.Remove(v.v) } // Has returns true if the given value is in the receiving set, or false if // it is not. func (s ValueSet) Has(v Value) bool { s.requireElementType(v) return s.s.Has(v.v) } // Copy performs a shallow copy of the receiving set, returning a new set // with the same rules and elements. func (s ValueSet) Copy() ValueSet { return newValueSet(s.s.Copy()) } // Length returns the number of values in the set. func (s ValueSet) Length() int { return s.s.Length() } // Values returns a slice of all of the values in the set in no particular // order. func (s ValueSet) Values() []Value { l := s.s.Length() if l == 0 { return nil } ret := make([]Value, 0, l) ety := s.ElementType() for it := s.s.Iterator(); it.Next(); { ret = append(ret, Value{ ty: ety, v: it.Value(), }) } return ret } // Union returns a new set that contains all of the members of both the // receiving set and the given set. Both sets must have the same element type, // or else this function will panic. func (s ValueSet) Union(other ValueSet) ValueSet { return newValueSet(s.s.Union(other.s)) } // Intersection returns a new set that contains the values that both the // receiver and given sets have in common. Both sets must have the same element // type, or else this function will panic. func (s ValueSet) Intersection(other ValueSet) ValueSet { return newValueSet(s.s.Intersection(other.s)) } // Subtract returns a new set that contains all of the values from the receiver // that are not also in the given set. Both sets must have the same element // type, or else this function will panic. func (s ValueSet) Subtract(other ValueSet) ValueSet { return newValueSet(s.s.Subtract(other.s)) } // SymmetricDifference returns a new set that contains all of the values from // both the receiver and given sets, except those that both sets have in // common. Both sets must have the same element type, or else this function // will panic. func (s ValueSet) SymmetricDifference(other ValueSet) ValueSet { return newValueSet(s.s.SymmetricDifference(other.s)) } // requireElementType panics if the given value is not of the set's element type. // // It also panics if the given value is marked, because marked values cannot // be stored in sets. func (s ValueSet) requireElementType(v Value) { if v.IsMarked() { panic("cannot store marked value directly in a set (make the set itself unknown instead)") } if !v.Type().Equals(s.ElementType()) { panic(fmt.Errorf("attempt to use %#v value with set of %#v", v.Type(), s.ElementType())) } } go-cty-1.12.1/cty/set_internals.go000066400000000000000000000170111433256746400170140ustar00rootroot00000000000000package cty import ( "bytes" "fmt" "hash/crc32" "math/big" "sort" "github.com/zclconf/go-cty/cty/set" ) // setRules provides a Rules implementation for the ./set package that // respects the equality rules for cty values of the given type. // // This implementation expects that values added to the set will be // valid internal values for the given Type, which is to say that wrapping // the given value in a Value struct along with the ruleset's type should // produce a valid, working Value. type setRules struct { Type Type } var _ set.OrderedRules[interface{}] = setRules{} func newSetRules(ety Type) set.Rules[interface{}] { return setRules{ety} } // Hash returns a hash value for the receiver that can be used for equality // checks where some inaccuracy is tolerable. // // The hash function is value-type-specific, so it is not meaningful to compare // hash results for values of different types. // // This function is not safe to use for security-related applications, since // the hash used is not strong enough. func (val Value) Hash() int { hashBytes, marks := makeSetHashBytes(val) if len(marks) > 0 { panic("can't take hash of value that has marks or has embedded values that have marks") } return int(crc32.ChecksumIEEE(hashBytes)) } func (r setRules) Hash(v interface{}) int { return Value{ ty: r.Type, v: v, }.Hash() } func (r setRules) Equivalent(v1 interface{}, v2 interface{}) bool { v1v := Value{ ty: r.Type, v: v1, } v2v := Value{ ty: r.Type, v: v2, } eqv := v1v.Equals(v2v) // By comparing the result to true we ensure that an Unknown result, // which will result if either value is unknown, will be considered // as non-equivalent. Two unknown values are not equivalent for the // sake of set membership. return eqv.v == true } // SameRules is only true if the other Rules instance is also a setRules struct, // and the types are considered equal. func (r setRules) SameRules(other set.Rules[interface{}]) bool { rules, ok := other.(setRules) if !ok { return false } return r.Type.Equals(rules.Type) } // Less is an implementation of set.OrderedRules so that we can iterate over // set elements in a consistent order, where such an order is possible. func (r setRules) Less(v1, v2 interface{}) bool { v1v := Value{ ty: r.Type, v: v1, } v2v := Value{ ty: r.Type, v: v2, } if v1v.RawEquals(v2v) { // Easy case: if they are equal then v1 can't be less return false } // Null values always sort after non-null values if v2v.IsNull() && !v1v.IsNull() { return true } else if v1v.IsNull() { return false } // Unknown values always sort after known values if v1v.IsKnown() && !v2v.IsKnown() { return true } else if !v1v.IsKnown() { return false } switch r.Type { case String: // String values sort lexicographically return v1v.AsString() < v2v.AsString() case Bool: // Weird to have a set of bools, but if we do then false sorts before true. if v2v.True() || !v1v.True() { return true } return false case Number: v1f := v1v.AsBigFloat() v2f := v2v.AsBigFloat() return v1f.Cmp(v2f) < 0 default: // No other types have a well-defined ordering, so we just produce a // default consistent-but-undefined ordering then. This situation is // not considered a compatibility constraint; callers should rely only // on the ordering rules for primitive values. v1h, _ := makeSetHashBytes(v1v) v2h, _ := makeSetHashBytes(v2v) return bytes.Compare(v1h, v2h) < 0 } } func makeSetHashBytes(val Value) ([]byte, ValueMarks) { var buf bytes.Buffer marks := make(ValueMarks) appendSetHashBytes(val, &buf, marks) return buf.Bytes(), marks } func appendSetHashBytes(val Value, buf *bytes.Buffer, marks ValueMarks) { // Exactly what bytes we generate here don't matter as long as the following // constraints hold: // - Unknown and null values all generate distinct strings from // each other and from any normal value of the given type. // - The delimiter used to separate items in a compound structure can // never appear literally in any of its elements. // Since we don't support hetrogenous lists we don't need to worry about // collisions between values of different types, apart from // PseudoTypeDynamic. // If in practice we *do* get a collision then it's not a big deal because // the Equivalent function will still distinguish values, but set // performance will be best if we are able to produce a distinct string // for each distinct value, unknown values notwithstanding. // Marks aren't considered part of a value for equality-testing purposes, // so we'll unmark our value before we work with it but we'll remember // the marks in case the caller needs to re-apply them to a derived // value. if val.IsMarked() { unmarkedVal, valMarks := val.Unmark() for m := range valMarks { marks[m] = struct{}{} } val = unmarkedVal } if !val.IsKnown() { buf.WriteRune('?') return } if val.IsNull() { buf.WriteRune('~') return } switch val.ty { case Number: // Due to an unfortunate quirk of gob encoding for big.Float, we end up // with non-pointer values immediately after a gob round-trip, and // we end up in here before we've had a chance to run // gobDecodeFixNumberPtr on the inner values of a gob-encoded set, // and so sadly we must make a special effort to handle that situation // here just so that we can get far enough along to fix it up for // everything else in this package. if bf, ok := val.v.(big.Float); ok { buf.WriteString(bf.String()) return } buf.WriteString(val.v.(*big.Float).String()) return case Bool: if val.v.(bool) { buf.WriteRune('T') } else { buf.WriteRune('F') } return case String: buf.WriteString(fmt.Sprintf("%q", val.v.(string))) return } if val.ty.IsMapType() { buf.WriteRune('{') val.ForEachElement(func(keyVal, elementVal Value) bool { appendSetHashBytes(keyVal, buf, marks) buf.WriteRune(':') appendSetHashBytes(elementVal, buf, marks) buf.WriteRune(';') return false }) buf.WriteRune('}') return } if val.ty.IsListType() || val.ty.IsSetType() { buf.WriteRune('[') val.ForEachElement(func(keyVal, elementVal Value) bool { appendSetHashBytes(elementVal, buf, marks) buf.WriteRune(';') return false }) buf.WriteRune(']') return } if val.ty.IsObjectType() { buf.WriteRune('<') attrNames := make([]string, 0, len(val.ty.AttributeTypes())) for attrName := range val.ty.AttributeTypes() { attrNames = append(attrNames, attrName) } sort.Strings(attrNames) for _, attrName := range attrNames { appendSetHashBytes(val.GetAttr(attrName), buf, marks) buf.WriteRune(';') } buf.WriteRune('>') return } if val.ty.IsTupleType() { buf.WriteRune('<') val.ForEachElement(func(keyVal, elementVal Value) bool { appendSetHashBytes(elementVal, buf, marks) buf.WriteRune(';') return false }) buf.WriteRune('>') return } if val.ty.IsCapsuleType() { buf.WriteRune('«') ops := val.ty.CapsuleOps() if ops != nil && ops.HashKey != nil { key := ops.HashKey(val.EncapsulatedValue()) buf.WriteString(fmt.Sprintf("%q", key)) } else { // If there isn't an explicit hash implementation then we'll // just generate the same hash value for every value of this // type, which is logically fine but less efficient for // larger sets because we'll have to bucket all values // together and scan over them with Equals to determine // set membership. buf.WriteRune('?') } buf.WriteRune('»') return } // should never get down here panic(fmt.Sprintf("unsupported type %#v in set hash", val.ty)) } go-cty-1.12.1/cty/set_internals_test.go000066400000000000000000000150101433256746400200500ustar00rootroot00000000000000package cty import ( "fmt" "math/big" "reflect" "testing" "github.com/zclconf/go-cty/cty/set" ) func TestSetHashBytes(t *testing.T) { type encapsulated struct { name string } typeWithHash := CapsuleWithOps("with hash function", reflect.TypeOf(encapsulated{}), &CapsuleOps{ RawEquals: func(a, b interface{}) bool { return a.(*encapsulated).name == b.(*encapsulated).name }, HashKey: func(v interface{}) string { return v.(*encapsulated).name }, }) typeWithoutHash := CapsuleWithOps("without hash function", reflect.TypeOf(encapsulated{}), &CapsuleOps{ RawEquals: func(a, b interface{}) bool { return a.(*encapsulated).name == b.(*encapsulated).name }, }) tests := []struct { value Value want string wantMarks ValueMarks }{ { UnknownVal(Number), "?", nil, }, { UnknownVal(String), "?", nil, }, { NullVal(Number), "~", nil, }, { NullVal(String), "~", nil, }, { DynamicVal, "?", nil, }, { NumberVal(big.NewFloat(12)), "12", nil, }, { StringVal(""), `""`, nil, }, { StringVal("pizza"), `"pizza"`, nil, }, { True, "T", nil, }, { False, "F", nil, }, { ListValEmpty(Bool), "[]", nil, }, { ListValEmpty(DynamicPseudoType), "[]", nil, }, { ListVal([]Value{True, False}), "[T;F;]", nil, }, { ListVal([]Value{UnknownVal(Bool)}), "[?;]", nil, }, { ListVal([]Value{ListValEmpty(Bool)}), "[[];]", nil, }, { MapValEmpty(Bool), "{}", nil, }, { MapVal(map[string]Value{"true": True, "false": False}), `{"false":F;"true":T;}`, nil, }, { MapVal(map[string]Value{"true": True, "unknown": UnknownVal(Bool), "dynamic": DynamicVal}), `{"dynamic":?;"true":T;"unknown":?;}`, nil, }, { SetValEmpty(Bool), "[]", nil, }, { SetVal([]Value{True, True, False}), "[F;T;]", nil, }, { SetVal([]Value{UnknownVal(Bool), UnknownVal(Bool)}), "[?;?;]", // unknowns are never equal, so we can have multiple of them nil, }, { EmptyObjectVal, "<>", nil, }, { ObjectVal(map[string]Value{ "name": StringVal("ermintrude"), "age": NumberVal(big.NewFloat(54)), }), `<54;"ermintrude";>`, nil, }, { EmptyTupleVal, "<>", nil, }, { TupleVal([]Value{ StringVal("ermintrude"), NumberVal(big.NewFloat(54)), }), `<"ermintrude";54;>`, nil, }, // Marked values { StringVal("pizza").Mark(1), `"pizza"`, NewValueMarks(1), }, { ObjectVal(map[string]Value{ "name": StringVal("ermintrude").Mark(1), "age": NumberVal(big.NewFloat(54)).Mark(2), }), `<54;"ermintrude";>`, NewValueMarks(1, 2), }, // Encapsulated values { CapsuleVal(typeWithHash, &encapsulated{"boop"}), `«"boop"»`, // we use the guillemets to differentiate this from a cty.String hash nil, }, { CapsuleVal(typeWithoutHash, &encapsulated{"boop"}), `«?»`, // we use the guillemets to differentiate a known value without a hash func from an unknown value nil, }, } for _, test := range tests { t.Run(test.value.GoString(), func(t *testing.T) { gotRaw, gotMarks := makeSetHashBytes(test.value) got := string(gotRaw) if got != test.want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, test.want) } if !test.wantMarks.Equal(gotMarks) { t.Errorf("wrong result marks\ngot: %#v\nwant: %#v", gotMarks, test.wantMarks) } }) } } func TestSetOrder(t *testing.T) { tests := []struct { a, b Value want bool }{ // Strings sort lexicographically (this is a compatibility constraint) { StringVal("a"), StringVal("b"), true, }, { StringVal("b"), StringVal("a"), false, }, { UnknownVal(String), StringVal("a"), false, }, { StringVal("a"), UnknownVal(String), true, }, // Numbers sort numerically (this is a compatibility constraint) { Zero, NumberIntVal(1), true, }, { NumberIntVal(1), Zero, false, }, // Booleans sort false before true (this is a compatibility constraint) { False, True, true, }, { True, False, false, }, // Unknown and Null values push to the end of a sort (this is a compatibility constraint) { UnknownVal(String), UnknownVal(String), false, // no defined ordering }, { NullVal(String), StringVal("a"), false, }, { StringVal("a"), NullVal(String), true, }, { UnknownVal(String), NullVal(String), true, }, { NullVal(String), UnknownVal(String), false, }, // All other types just use an arbitrary fallback sort. These results // are _not_ compatibility constraints but we are testing them here // to verify that the result is consistent between runs for a // specific version of cty. { ListValEmpty(String), ListVal([]Value{StringVal("boop")}), false, }, { ListVal([]Value{StringVal("boop")}), ListValEmpty(String), true, }, { SetValEmpty(String), SetVal([]Value{StringVal("boop")}), false, }, { SetVal([]Value{StringVal("boop")}), SetValEmpty(String), true, }, { MapValEmpty(String), MapVal(map[string]Value{"blah": StringVal("boop")}), false, }, { MapVal(map[string]Value{"blah": StringVal("boop")}), MapValEmpty(String), true, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v < %#v", test.a, test.b), func(t *testing.T) { rules := setRules{test.a.Type()} // both values are assumed to have the same type got := rules.Less(test.a.v, test.b.v) if got != test.want { t.Errorf("wrong result\na: %#v\nb: %#v\ngot: %#v\nwant: %#v", test.a, test.b, got, test.want) } }) } } func TestSetRulesSameRules(t *testing.T) { tests := []struct { a set.Rules[interface{}] b set.Rules[interface{}] want bool }{ { setRules{EmptyObject}, setRules{DynamicPseudoType}, false, }, { setRules{EmptyObject}, setRules{EmptyObject}, true, }, { setRules{String}, setRules{String}, true, }, { setRules{Object(map[string]Type{"a": String})}, setRules{Object(map[string]Type{"a": String})}, true, }, { setRules{Object(map[string]Type{"a": String})}, setRules{Object(map[string]Type{"a": Bool})}, false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.SameRules(%#v)", test.a, test.b), func(t *testing.T) { got := test.a.SameRules(test.b) if got != test.want { t.Errorf("wrong result\na: %#v\nb: %#v\ngot %#v, want %#v", test.a, test.b, got, test.want) } }) } } go-cty-1.12.1/cty/set_type.go000066400000000000000000000031031433256746400157730ustar00rootroot00000000000000package cty import ( "fmt" ) type typeSet struct { typeImplSigil ElementTypeT Type } // Set creates a set type with the given element Type. // // Set types are CollectionType implementations. func Set(elem Type) Type { return Type{ typeSet{ ElementTypeT: elem, }, } } // Equals returns true if the other Type is a set whose element type is // equal to that of the receiver. func (t typeSet) Equals(other Type) bool { ot, isSet := other.typeImpl.(typeSet) if !isSet { return false } return t.ElementTypeT.Equals(ot.ElementTypeT) } func (t typeSet) FriendlyName(mode friendlyTypeNameMode) string { elemName := t.ElementTypeT.friendlyNameMode(mode) if mode == friendlyTypeConstraintName { if t.ElementTypeT == DynamicPseudoType { elemName = "any single type" } } return "set of " + elemName } func (t typeSet) ElementType() Type { return t.ElementTypeT } func (t typeSet) GoString() string { return fmt.Sprintf("cty.Set(%#v)", t.ElementTypeT) } // IsSetType returns true if the given type is a list type, regardless of its // element type. func (t Type) IsSetType() bool { _, ok := t.typeImpl.(typeSet) return ok } // SetElementType is a convenience method that checks if the given type is // a set type, returning a pointer to its element type if so and nil // otherwise. This is intended to allow convenient conditional branches, // like so: // // if et := t.SetElementType(); et != nil { // // Do something with *et // } func (t Type) SetElementType() *Type { if lt, ok := t.typeImpl.(typeSet); ok { return <.ElementTypeT } return nil } go-cty-1.12.1/cty/set_type_test.go000066400000000000000000000103641433256746400170410ustar00rootroot00000000000000package cty import ( "reflect" "sort" "testing" "github.com/google/go-cmp/cmp" ) func TestSetOperations(t *testing.T) { // This test is for the mechanisms that allow a calling application to // implement set operations using the underlying set.Set type. This is // not expected to be a common case but is useful, for example, for // implementing the set-related functions in function/stdlib . s1 := SetVal([]Value{ StringVal("a"), StringVal("b"), StringVal("c"), }) s2 := SetVal([]Value{ StringVal("c"), StringVal("d"), StringVal("e"), }) s1r := s1.AsValueSet() s2r := s2.AsValueSet() s3r := s1r.Union(s2r) s3 := SetValFromValueSet(s3r) if got, want := s3.LengthInt(), 5; got != want { t.Errorf("wrong length %d; want %d", got, want) } for _, wantStr := range []string{"a", "b", "c", "d", "e"} { if got, want := s3.HasElement(StringVal(wantStr)), True; got != want { t.Errorf("missing element %q", wantStr) } } } func TestSetOfCapsuleType(t *testing.T) { type capsuleTypeForSetTests struct { name string } encapsulatedNames := func(vals []Value) []string { if len(vals) == 0 { return nil } ret := make([]string, len(vals)) for i, v := range vals { ret[i] = v.EncapsulatedValue().(*capsuleTypeForSetTests).name } sort.Strings(ret) return ret } typeWithHash := CapsuleWithOps("with hash function", reflect.TypeOf(capsuleTypeForSetTests{}), &CapsuleOps{ RawEquals: func(a, b interface{}) bool { return a.(*capsuleTypeForSetTests).name == b.(*capsuleTypeForSetTests).name }, HashKey: func(v interface{}) string { return v.(*capsuleTypeForSetTests).name }, }) typeWithoutHash := CapsuleWithOps("without hash function", reflect.TypeOf(capsuleTypeForSetTests{}), &CapsuleOps{ RawEquals: func(a, b interface{}) bool { return a.(*capsuleTypeForSetTests).name == b.(*capsuleTypeForSetTests).name }, }) typeWithoutEquals := Capsule("without hash function", reflect.TypeOf(capsuleTypeForSetTests{})) t.Run("with hash", func(t *testing.T) { // When we provide a hashing function the set implementation can // optimize its internal storage by spreading values over multiple // smaller buckets. v := SetVal([]Value{ CapsuleVal(typeWithHash, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithHash, &capsuleTypeForSetTests{"b"}), CapsuleVal(typeWithHash, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithHash, &capsuleTypeForSetTests{"c"}), }) got := encapsulatedNames(v.AsValueSlice()) want := []string{"a", "b", "c"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("wrong element names\n%s", diff) } }) t.Run("without hash", func(t *testing.T) { // When we don't provide a hashing function the outward behavior // should still be identical but the internal storage won't be // so efficient, due to everything living in one big bucket and // so we have to scan over all values to test if a particular // element is present. v := SetVal([]Value{ CapsuleVal(typeWithoutHash, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithoutHash, &capsuleTypeForSetTests{"b"}), CapsuleVal(typeWithoutHash, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithoutHash, &capsuleTypeForSetTests{"c"}), }) got := encapsulatedNames(v.AsValueSlice()) want := []string{"a", "b", "c"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("wrong element names\n%s", diff) } }) t.Run("without equals", func(t *testing.T) { // When we don't even have an equals function we can still store // values in the set but we will use the default capsule type // behavior of comparing by pointer equality. That means that // the name field doesn't coalesce anymore, but two instances // of this same d should. d := &capsuleTypeForSetTests{"d"} v := SetVal([]Value{ CapsuleVal(typeWithoutEquals, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithoutEquals, &capsuleTypeForSetTests{"b"}), CapsuleVal(typeWithoutEquals, d), CapsuleVal(typeWithoutEquals, &capsuleTypeForSetTests{"a"}), CapsuleVal(typeWithoutEquals, &capsuleTypeForSetTests{"c"}), CapsuleVal(typeWithoutEquals, d), }) got := encapsulatedNames(v.AsValueSlice()) want := []string{"a", "a", "b", "c", "d"} if diff := cmp.Diff(want, got); diff != "" { t.Errorf("wrong element names\n%s", diff) } }) } go-cty-1.12.1/cty/tuple_type.go000066400000000000000000000064521433256746400163430ustar00rootroot00000000000000package cty import ( "fmt" ) type typeTuple struct { typeImplSigil ElemTypes []Type } // Tuple creates a tuple type with the given element types. // // After a slice is passed to this function the caller must no longer access // the underlying array, since ownership is transferred to this library. func Tuple(elemTypes []Type) Type { return Type{ typeTuple{ ElemTypes: elemTypes, }, } } func (t typeTuple) Equals(other Type) bool { if ot, ok := other.typeImpl.(typeTuple); ok { if len(t.ElemTypes) != len(ot.ElemTypes) { // Fast path: if we don't have the same number of elements // then we can't possibly be equal. return false } for i, ty := range t.ElemTypes { oty := ot.ElemTypes[i] if !ok { return false } if !oty.Equals(ty) { return false } } return true } return false } func (t typeTuple) FriendlyName(mode friendlyTypeNameMode) string { // There isn't really a friendly way to write a tuple type due to its // complexity, so we'll just do something English-ish. Callers will // probably want to make some extra effort to avoid ever printing out // a tuple type FriendlyName in its entirety. For example, could // produce an error message by diffing two object types and saying // something like "Expected attribute foo to be string, but got number". // TODO: Finish this return "tuple" } func (t typeTuple) GoString() string { if len(t.ElemTypes) == 0 { return "cty.EmptyTuple" } return fmt.Sprintf("cty.Tuple(%#v)", t.ElemTypes) } // EmptyTuple is a shorthand for Tuple([]Type{}), to more easily talk about // the empty tuple type. var EmptyTuple Type // EmptyTupleVal is the only possible non-null, non-unknown value of type // EmptyTuple. var EmptyTupleVal Value func init() { EmptyTuple = Tuple([]Type{}) EmptyTupleVal = Value{ ty: EmptyTuple, v: []interface{}{}, } } // IsTupleType returns true if the given type is an object type, regardless // of its element type. func (t Type) IsTupleType() bool { _, ok := t.typeImpl.(typeTuple) return ok } // Length returns the number of elements of the receiving tuple type. // Will panic if the reciever isn't a tuple type; use IsTupleType to determine // whether this operation will succeed. func (t Type) Length() int { if ot, ok := t.typeImpl.(typeTuple); ok { return len(ot.ElemTypes) } panic("Length on non-tuple Type") } // TupleElementType returns the type of the element with the given index. Will // panic if the receiver is not a tuple type (use IsTupleType to confirm) // or if the index is out of range (use Length to confirm). func (t Type) TupleElementType(idx int) Type { if ot, ok := t.typeImpl.(typeTuple); ok { return ot.ElemTypes[idx] } panic("TupleElementType on non-tuple Type") } // TupleElementTypes returns a slice of the recieving tuple type's element // types. Will panic if the receiver is not a tuple type (use IsTupleType // to confirm). // // The returned slice is part of the internal state of the type, and is provided // for read access only. It is forbidden for any caller to modify the // underlying array. For many purposes the element-related methods of Value // are more appropriate and more convenient to use. func (t Type) TupleElementTypes() []Type { if ot, ok := t.typeImpl.(typeTuple); ok { return ot.ElemTypes } panic("TupleElementTypes on non-tuple Type") } go-cty-1.12.1/cty/tuple_type_test.go000066400000000000000000000017701433256746400174000ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestTupleTypeEquals(t *testing.T) { tests := []struct { LHS Type // Must be typeTuple RHS Type Expected bool }{ { Tuple([]Type{}), Tuple([]Type{}), true, }, { EmptyTuple, Tuple([]Type{}), true, }, { Tuple([]Type{String}), Tuple([]Type{String}), true, }, { Tuple([]Type{Tuple([]Type{String})}), Tuple([]Type{Tuple([]Type{String})}), true, }, { Tuple([]Type{String}), EmptyTuple, false, }, { Tuple([]Type{String}), Tuple([]Type{Number}), false, }, { Tuple([]Type{String}), Tuple([]Type{String, Number}), false, }, { Tuple([]Type{String}), Tuple([]Type{Tuple([]Type{String})}), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Equals(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Equals(test.RHS) if got != test.Expected { t.Errorf("Equals returned %#v; want %#v", got, test.Expected) } }) } } go-cty-1.12.1/cty/type.go000066400000000000000000000120601433256746400151220ustar00rootroot00000000000000package cty // Type represents value types within the type system. // // This is a closed interface type, meaning that only the concrete // implementations provided within this package are considered valid. type Type struct { typeImpl } type typeImpl interface { // isTypeImpl is a do-nothing method that exists only to express // that a type is an implementation of typeImpl. isTypeImpl() typeImplSigil // Equals returns true if the other given Type exactly equals the // receiver Type. Equals(other Type) bool // FriendlyName returns a human-friendly *English* name for the given // type. FriendlyName(mode friendlyTypeNameMode) string // GoString implements the GoStringer interface from package fmt. GoString() string } // Base implementation of Type to embed into concrete implementations // to signal that they are implementations of Type. type typeImplSigil struct{} func (t typeImplSigil) isTypeImpl() typeImplSigil { return typeImplSigil{} } // Equals returns true if the other given Type exactly equals the receiver // type. func (t Type) Equals(other Type) bool { if t == NilType || other == NilType { return t == other } return t.typeImpl.Equals(other) } // FriendlyName returns a human-friendly *English* name for the given type. func (t Type) FriendlyName() string { return t.typeImpl.FriendlyName(friendlyTypeName) } // FriendlyNameForConstraint is similar to FriendlyName except that the // result is specialized for describing type _constraints_ rather than types // themselves. This is more appropriate when reporting that a particular value // does not conform to an expected type constraint. // // In particular, this function uses the term "any type" to refer to // cty.DynamicPseudoType, rather than "dynamic" as returned by FriendlyName. func (t Type) FriendlyNameForConstraint() string { return t.typeImpl.FriendlyName(friendlyTypeConstraintName) } // friendlyNameMode is an internal combination of the various FriendlyName* // variants that just directly takes a mode, for easy passthrough for // recursive name construction. func (t Type) friendlyNameMode(mode friendlyTypeNameMode) string { return t.typeImpl.FriendlyName(mode) } // GoString returns a string approximating how the receiver type would be // expressed in Go source code. func (t Type) GoString() string { if t.typeImpl == nil { return "cty.NilType" } return t.typeImpl.GoString() } // NilType is an invalid type used when a function is returning an error // and has no useful type to return. It should not be used and any methods // called on it will panic. var NilType = Type{} // HasDynamicTypes returns true either if the receiver is itself // DynamicPseudoType or if it is a compound type whose descendent elements // are DynamicPseudoType. func (t Type) HasDynamicTypes() bool { switch { case t == DynamicPseudoType: return true case t.IsPrimitiveType(): return false case t.IsCollectionType(): return t.ElementType().HasDynamicTypes() case t.IsObjectType(): attrTypes := t.AttributeTypes() for _, at := range attrTypes { if at.HasDynamicTypes() { return true } } return false case t.IsTupleType(): elemTypes := t.TupleElementTypes() for _, et := range elemTypes { if et.HasDynamicTypes() { return true } } return false case t.IsCapsuleType(): return false default: // Should never happen, since above should be exhaustive panic("HasDynamicTypes does not support the given type") } } // WithoutOptionalAttributesDeep returns a type equivalent to the receiver but // with any objects with optional attributes converted into fully concrete // object types. This operation is applied recursively. func (t Type) WithoutOptionalAttributesDeep() Type { switch { case t == DynamicPseudoType, t.IsPrimitiveType(), t.IsCapsuleType(): return t case t.IsMapType(): return Map(t.ElementType().WithoutOptionalAttributesDeep()) case t.IsListType(): return List(t.ElementType().WithoutOptionalAttributesDeep()) case t.IsSetType(): return Set(t.ElementType().WithoutOptionalAttributesDeep()) case t.IsTupleType(): originalElemTypes := t.TupleElementTypes() elemTypes := make([]Type, len(originalElemTypes)) for i, et := range originalElemTypes { elemTypes[i] = et.WithoutOptionalAttributesDeep() } return Tuple(elemTypes) case t.IsObjectType(): originalAttrTypes := t.AttributeTypes() attrTypes := make(map[string]Type, len(originalAttrTypes)) for k, t := range originalAttrTypes { attrTypes[k] = t.WithoutOptionalAttributesDeep() } // This is the subtle line which does all the work of this function: by // constructing a new Object type with these attribute types, we drop // the list of optional attributes (if present). This results in a // concrete Object type which requires all of the original attributes. return Object(attrTypes) default: // Should never happen, since above should be exhaustive panic("WithoutOptionalAttributesDeep does not support the given type") } } type friendlyTypeNameMode rune const ( friendlyTypeName friendlyTypeNameMode = 'N' friendlyTypeConstraintName friendlyTypeNameMode = 'C' ) go-cty-1.12.1/cty/type_conform.go000066400000000000000000000100111433256746400166370ustar00rootroot00000000000000package cty // TestConformance recursively walks the receiver and the given other type and // returns nil if the receiver *conforms* to the given type. // // Type conformance is similar to type equality but has one crucial difference: // PseudoTypeDynamic can be used within the given type to represent that // *any* type is allowed. // // If any non-conformities are found, the returned slice will be non-nil and // contain at least one error value. It will be nil if the type is entirely // conformant. // // Note that the special behavior of PseudoTypeDynamic is the *only* exception // to normal type equality. Calling applications may wish to apply their own // automatic conversion logic to the given data structure to create a more // liberal notion of conformance to a type. // // Returned errors are usually (but not always) PathError instances that // indicate where in the structure the error was found. If a returned error // is of that type then the error message is written for (English-speaking) // end-users working within the cty type system, not mentioning any Go-oriented // implementation details. func (t Type) TestConformance(other Type) []error { path := make(Path, 0) var errs []error testConformance(t, other, path, &errs) return errs } func testConformance(given Type, want Type, path Path, errs *[]error) { if want.Equals(DynamicPseudoType) { // anything goes! return } if given.Equals(want) { // Any equal types are always conformant return } // The remainder of this function is concerned with detecting // and reporting the specific non-conformance, since we wouldn't // have got here if the types were not divergent. // We treat compound structures as special so that we can report // specifically what is non-conforming, rather than simply returning // the entire type names and letting the user puzzle it out. if given.IsObjectType() && want.IsObjectType() { givenAttrs := given.AttributeTypes() wantAttrs := want.AttributeTypes() for k := range givenAttrs { if _, exists := wantAttrs[k]; !exists { *errs = append( *errs, errorf(path, "unsupported attribute %q", k), ) } } for k := range wantAttrs { if _, exists := givenAttrs[k]; !exists { *errs = append( *errs, errorf(path, "missing required attribute %q", k), ) } } path = append(path, nil) pathIdx := len(path) - 1 for k, wantAttrType := range wantAttrs { if givenAttrType, exists := givenAttrs[k]; exists { path[pathIdx] = GetAttrStep{Name: k} testConformance(givenAttrType, wantAttrType, path, errs) } } path = path[0:pathIdx] return } if given.IsTupleType() && want.IsTupleType() { givenElems := given.TupleElementTypes() wantElems := want.TupleElementTypes() if len(givenElems) != len(wantElems) { *errs = append( *errs, errorf(path, "%d elements are required, but got %d", len(wantElems), len(givenElems)), ) return } path = append(path, nil) pathIdx := len(path) - 1 for i, wantElemType := range wantElems { givenElemType := givenElems[i] path[pathIdx] = IndexStep{Key: NumberIntVal(int64(i))} testConformance(givenElemType, wantElemType, path, errs) } path = path[0:pathIdx] return } if given.IsListType() && want.IsListType() { path = append(path, IndexStep{Key: UnknownVal(Number)}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } if given.IsMapType() && want.IsMapType() { path = append(path, IndexStep{Key: UnknownVal(String)}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } if given.IsSetType() && want.IsSetType() { path = append(path, IndexStep{Key: UnknownVal(given.ElementType())}) pathIdx := len(path) - 1 testConformance(given.ElementType(), want.ElementType(), path, errs) path = path[0:pathIdx] return } *errs = append( *errs, errorf(path, "%s required, but received %s", want.FriendlyName(), given.FriendlyName()), ) } go-cty-1.12.1/cty/type_conform_test.go000066400000000000000000000110121433256746400177000ustar00rootroot00000000000000package cty import ( "fmt" "strings" "testing" ) func TestTypeTestConformance(t *testing.T) { tests := []struct { Receiver Type Given Type Conforms bool }{ { Receiver: Number, Given: Number, Conforms: true, }, { Receiver: Number, Given: String, Conforms: false, }, { Receiver: Number, Given: DynamicPseudoType, Conforms: true, }, { Receiver: DynamicPseudoType, Given: DynamicPseudoType, Conforms: true, }, { Receiver: DynamicPseudoType, Given: Number, Conforms: false, }, { Receiver: List(Number), Given: List(Number), Conforms: true, }, { Receiver: List(Number), Given: Map(Number), Conforms: false, }, { Receiver: List(Number), Given: List(DynamicPseudoType), Conforms: true, }, { Receiver: List(Number), Given: List(String), Conforms: false, }, { Receiver: Map(Number), Given: Map(Number), Conforms: true, }, { Receiver: Map(Number), Given: Set(Number), Conforms: false, }, { Receiver: List(Number), Given: Map(DynamicPseudoType), Conforms: false, }, { Receiver: Map(Number), Given: Map(DynamicPseudoType), Conforms: true, }, { Receiver: Map(Number), Given: Map(String), Conforms: false, }, { Receiver: Set(Number), Given: Set(Number), Conforms: true, }, { Receiver: Set(Number), Given: List(Number), Conforms: false, }, { Receiver: Set(Number), Given: List(DynamicPseudoType), Conforms: false, }, { Receiver: Set(Number), Given: Set(DynamicPseudoType), Conforms: true, }, { Receiver: Set(Number), Given: Set(String), Conforms: false, }, { Receiver: EmptyObject, Given: EmptyObject, Conforms: true, }, { Receiver: EmptyObject, Given: Object(map[string]Type{"name": String}), Conforms: false, }, { Receiver: Object(map[string]Type{"name": String}), Given: EmptyObject, Conforms: false, }, { Receiver: Object(map[string]Type{"name": String}), Given: Object(map[string]Type{"name": String}), Conforms: true, }, { Receiver: Object(map[string]Type{"name": String}), Given: Object(map[string]Type{"gnome": String}), Conforms: false, }, { Receiver: Object(map[string]Type{"name": Number}), Given: Object(map[string]Type{"name": String}), Conforms: false, }, { Receiver: Object(map[string]Type{"name": Number}), Given: Object(map[string]Type{"name": String, "number": Number}), Conforms: false, }, { Receiver: ObjectWithOptionalAttrs(map[string]Type{"name": Number}, []string{"name"}), Given: Object(map[string]Type{"name": Number}), Conforms: true, }, { Receiver: ObjectWithOptionalAttrs(map[string]Type{"name": Number}, []string{"name"}), Given: EmptyObject, Conforms: false, // "optionalness" of attributes is only considered under conversion, not for conformance }, { Receiver: EmptyTuple, Given: EmptyTuple, Conforms: true, }, { Receiver: EmptyTuple, Given: Tuple([]Type{String}), Conforms: false, }, { Given: Tuple([]Type{String}), Receiver: EmptyTuple, Conforms: false, }, { Receiver: Tuple([]Type{String}), Given: Tuple([]Type{String}), Conforms: true, }, { Receiver: Tuple([]Type{String}), Given: Tuple([]Type{Number}), Conforms: false, }, { Receiver: Tuple([]Type{String, Number}), Given: Tuple([]Type{String, Number}), Conforms: true, }, { Receiver: Tuple([]Type{String}), Given: Tuple([]Type{String, Number}), Conforms: false, }, { Receiver: Tuple([]Type{String, Number}), Given: Tuple([]Type{String}), Conforms: false, }, } for _, test := range tests { t.Run(fmt.Sprintf("(%#v).TestConformance(%#v)", test.Receiver, test.Given), func(t *testing.T) { errs := test.Receiver.TestConformance(test.Given) if test.Conforms { if errs != nil { errStrs := make([]string, 0, len(errs)) for _, err := range errs { if pathErr, ok := err.(PathError); ok { errStrs = append(errStrs, fmt.Sprintf("at %#v: %s", pathErr.Path, pathErr)) } else { errStrs = append(errStrs, err.Error()) } } t.Errorf("(%#v).TestConformance(%#v): unexpected errors\n%s", test.Receiver, test.Given, strings.Join(errStrs, "\n")) } } else { if errs == nil { t.Errorf("(%#v).TestConformance(%#v): expected errors, but got none", test.Receiver, test.Given) } } }) } } go-cty-1.12.1/cty/type_test.go000066400000000000000000000110121433256746400161550ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestHasDynamicTypes(t *testing.T) { tests := []struct { ty Type expected bool }{ { DynamicPseudoType, true, }, { List(DynamicPseudoType), true, }, { Tuple([]Type{String, DynamicPseudoType}), true, }, { Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }), true, }, { List(Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, })), true, }, { Tuple([]Type{Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, })}), true, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.HasDynamicTypes()", test.ty), func(t *testing.T) { got := test.ty.HasDynamicTypes() if got != test.expected { t.Errorf("Equals returned %#v; want %#v", got, test.expected) } }) } } func TestWithoutOptionalAttributesDeep(t *testing.T) { tests := []struct { ty Type expected Type }{ { DynamicPseudoType, DynamicPseudoType, }, { List(DynamicPseudoType), List(DynamicPseudoType), }, { Tuple([]Type{String, DynamicPseudoType}), Tuple([]Type{String, DynamicPseudoType}), }, { Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }), Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }), }, { ObjectWithOptionalAttrs(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }, []string{"a"}), Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }), }, { Map(ObjectWithOptionalAttrs(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }, []string{"a"})), Map(Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, })), }, { Set(ObjectWithOptionalAttrs(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }, []string{"a"})), Set(Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, })), }, { List(ObjectWithOptionalAttrs(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }, []string{"a"})), List(Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, })), }, { Tuple([]Type{ ObjectWithOptionalAttrs(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }, []string{"a"}), ObjectWithOptionalAttrs(map[string]Type{ "b": Number, }, []string{"b"}), }), Tuple([]Type{ Object(map[string]Type{ "a": String, "unknown": DynamicPseudoType, }), Object(map[string]Type{ "b": Number, }), }), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.HasDynamicTypes()", test.ty), func(t *testing.T) { got := test.ty.WithoutOptionalAttributesDeep() if !test.expected.Equals(got) { t.Errorf("Equals returned %#v; want %#v", got, test.expected) } }) } } func TestNilTypeEquals(t *testing.T) { var typ Type if !typ.Equals(NilType) { t.Fatal("expected NilTypes to equal") } } func TestTypeGoString(t *testing.T) { tests := []struct { Type Type Want string }{ { DynamicPseudoType, `cty.DynamicPseudoType`, }, { String, `cty.String`, }, { Tuple([]Type{String, Bool}), `cty.Tuple([]cty.Type{cty.String, cty.Bool})`, }, { Number, `cty.Number`, }, { Bool, `cty.Bool`, }, { List(String), `cty.List(cty.String)`, }, { List(List(String)), `cty.List(cty.List(cty.String))`, }, { List(Bool), `cty.List(cty.Bool)`, }, { Set(String), `cty.Set(cty.String)`, }, { Set(Map(String)), `cty.Set(cty.Map(cty.String))`, }, { Set(Bool), `cty.Set(cty.Bool)`, }, { Tuple([]Type{Bool}), `cty.Tuple([]cty.Type{cty.Bool})`, }, { Map(String), `cty.Map(cty.String)`, }, { Map(Set(String)), `cty.Map(cty.Set(cty.String))`, }, { Map(Bool), `cty.Map(cty.Bool)`, }, { Object(map[string]Type{"foo": Bool}), `cty.Object(map[string]cty.Type{"foo":cty.Bool})`, }, { ObjectWithOptionalAttrs(map[string]Type{"foo": Bool, "bar": String}, []string{"bar"}), `cty.ObjectWithOptionalAttrs(map[string]cty.Type{"bar":cty.String, "foo":cty.Bool}, []string{"bar"})`, }, } for _, test := range tests { t.Run(test.Type.GoString(), func(t *testing.T) { got := test.Type.GoString() want := test.Want if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } }) } } go-cty-1.12.1/cty/unknown.go000066400000000000000000000052631433256746400156470ustar00rootroot00000000000000package cty // unknownType is the placeholder type used for the sigil value representing // "Unknown", to make it unambigiously distinct from any other possible value. type unknownType struct { } // unknown is a special value that can be used as the internal value of a // Value to create a placeholder for a value that isn't yet known. var unknown interface{} = &unknownType{} // UnknownVal returns an Value that represents an unknown value of the given // type. Unknown values can be used to represent a value that is // not yet known. Its meaning is undefined in cty, but it could be used by // an calling application to allow partial evaluation. // // Unknown values of any type can be created of any type. All operations on // Unknown values themselves return Unknown. func UnknownVal(t Type) Value { return Value{ ty: t, v: unknown, } } func (t unknownType) GoString() string { // This is the stringification of our internal unknown marker. The // stringification of the public representation of unknowns is in // Value.GoString. return "cty.unknown" } type pseudoTypeDynamic struct { typeImplSigil } // DynamicPseudoType represents the dynamic pseudo-type. // // This type can represent situations where a type is not yet known. Its // meaning is undefined in cty, but it could be used by a calling // application to allow expression type checking with some types not yet known. // For example, the application might optimistically permit any operation on // values of this type in type checking, allowing a partial type-check result, // and then repeat the check when more information is known to get the // final, concrete type. // // It is a pseudo-type because it is used only as a sigil to the calling // application. "Unknown" is the only valid value of this pseudo-type, so // operations on values of this type will always short-circuit as per // the rules for that special value. var DynamicPseudoType Type func (t pseudoTypeDynamic) Equals(other Type) bool { _, ok := other.typeImpl.(pseudoTypeDynamic) return ok } func (t pseudoTypeDynamic) FriendlyName(mode friendlyTypeNameMode) string { switch mode { case friendlyTypeConstraintName: return "any type" default: return "dynamic" } } func (t pseudoTypeDynamic) GoString() string { return "cty.DynamicPseudoType" } // DynamicVal is the only valid value of the pseudo-type dynamic. // This value can be used as a placeholder where a value or expression's // type and value are both unknown, thus allowing partial evaluation. See // the docs for DynamicPseudoType for more information. var DynamicVal Value func init() { DynamicPseudoType = Type{ pseudoTypeDynamic{}, } DynamicVal = Value{ ty: DynamicPseudoType, v: unknown, } } go-cty-1.12.1/cty/unknown_as_null.go000066400000000000000000000030311433256746400173530ustar00rootroot00000000000000package cty // UnknownAsNull returns a value of the same type as the given value but // with any unknown values (including nested values) replaced with null // values of the same type. // // This can be useful if a result is to be serialized in a format that can't // represent unknowns, such as JSON, as long as the caller does not need to // retain the unknown value information. func UnknownAsNull(val Value) Value { ty := val.Type() switch { case val.IsNull(): return val case !val.IsKnown(): return NullVal(ty) case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): length := val.LengthInt() if length == 0 { // If there are no elements then we can't have unknowns return val } vals := make([]Value, 0, length) it := val.ElementIterator() for it.Next() { _, v := it.Element() vals = append(vals, UnknownAsNull(v)) } switch { case ty.IsListType(): return ListVal(vals) case ty.IsTupleType(): return TupleVal(vals) default: return SetVal(vals) } case ty.IsMapType() || ty.IsObjectType(): var length int switch { case ty.IsMapType(): length = val.LengthInt() default: length = len(val.Type().AttributeTypes()) } if length == 0 { // If there are no elements then we can't have unknowns return val } vals := make(map[string]Value, length) it := val.ElementIterator() for it.Next() { k, v := it.Element() vals[k.AsString()] = UnknownAsNull(v) } switch { case ty.IsMapType(): return MapVal(vals) default: return ObjectVal(vals) } } return val } go-cty-1.12.1/cty/unknown_as_null_test.go000066400000000000000000000056131433256746400204220ustar00rootroot00000000000000package cty import ( "testing" ) func TestUnknownAsNull(t *testing.T) { tests := []struct { Input Value Want Value }{ { StringVal("hello"), StringVal("hello"), }, { NullVal(String), NullVal(String), }, { UnknownVal(String), NullVal(String), }, { NullVal(DynamicPseudoType), NullVal(DynamicPseudoType), }, { NullVal(Object(map[string]Type{"test": String})), NullVal(Object(map[string]Type{"test": String})), }, { DynamicVal, NullVal(DynamicPseudoType), }, { ListValEmpty(String), ListValEmpty(String), }, { ListVal([]Value{ StringVal("hello"), }), ListVal([]Value{ StringVal("hello"), }), }, { ListVal([]Value{ NullVal(String), }), ListVal([]Value{ NullVal(String), }), }, { ListVal([]Value{ UnknownVal(String), }), ListVal([]Value{ NullVal(String), }), }, { SetValEmpty(String), SetValEmpty(String), }, { SetVal([]Value{ StringVal("hello"), }), SetVal([]Value{ StringVal("hello"), }), }, { SetVal([]Value{ NullVal(String), }), SetVal([]Value{ NullVal(String), }), }, { SetVal([]Value{ UnknownVal(String), }), SetVal([]Value{ NullVal(String), }), }, { EmptyTupleVal, EmptyTupleVal, }, { TupleVal([]Value{ StringVal("hello"), }), TupleVal([]Value{ StringVal("hello"), }), }, { TupleVal([]Value{ NullVal(String), }), TupleVal([]Value{ NullVal(String), }), }, { TupleVal([]Value{ UnknownVal(String), }), TupleVal([]Value{ NullVal(String), }), }, { MapValEmpty(String), MapValEmpty(String), }, { MapVal(map[string]Value{ "greeting": StringVal("hello"), }), MapVal(map[string]Value{ "greeting": StringVal("hello"), }), }, { MapVal(map[string]Value{ "greeting": NullVal(String), }), MapVal(map[string]Value{ "greeting": NullVal(String), }), }, { MapVal(map[string]Value{ "greeting": UnknownVal(String), }), MapVal(map[string]Value{ "greeting": NullVal(String), }), }, { EmptyObjectVal, EmptyObjectVal, }, { ObjectVal(map[string]Value{ "greeting": StringVal("hello"), }), ObjectVal(map[string]Value{ "greeting": StringVal("hello"), }), }, { ObjectVal(map[string]Value{ "greeting": NullVal(String), }), ObjectVal(map[string]Value{ "greeting": NullVal(String), }), }, { ObjectVal(map[string]Value{ "greeting": UnknownVal(String), }), ObjectVal(map[string]Value{ "greeting": NullVal(String), }), }, } for _, test := range tests { t.Run(test.Input.GoString(), func(t *testing.T) { got := UnknownAsNull(test.Input) if !got.RawEquals(test.Want) { t.Errorf( "wrong result\ninput: %#v\ngot: %#v\nwant: %#v", test.Input, got, test.Want, ) } }) } } go-cty-1.12.1/cty/value.go000066400000000000000000000111311433256746400152530ustar00rootroot00000000000000package cty // Value represents a value of a particular type, and is the interface by // which operations are executed on typed values. // // Value has two different classes of method. Operation methods stay entirely // within the type system (methods accept and return Value instances) and // are intended for use in implementing a language in terms of cty, while // integration methods either enter or leave the type system, working with // native Go values. Operation methods are guaranteed to support all of the // expected short-circuit behavior for unknown and dynamic values, while // integration methods may not. // // The philosophy for the operations API is that it's the caller's // responsibility to ensure that the given types and values satisfy the // specified invariants during a separate type check, so that the caller is // able to return errors to its user from the application's own perspective. // // Consequently the design of these methods assumes such checks have already // been done and panics if any invariants turn out not to be satisfied. These // panic errors are not intended to be handled, but rather indicate a bug in // the calling application that should be fixed with more checks prior to // executing operations. // // A related consequence of this philosophy is that no automatic type // conversions are done. If a method specifies that its argument must be // number then it's the caller's responsibility to do that conversion before // the call, thus allowing the application to have more constrained conversion // rules than are offered by the built-in converter where necessary. type Value struct { ty Type v interface{} } // Type returns the type of the value. func (val Value) Type() Type { return val.ty } // IsKnown returns true if the value is known. That is, if it is not // the result of the unknown value constructor Unknown(...), and is not // the result of an operation on another unknown value. // // Unknown values are only produced either directly or as a result of // operating on other unknown values, and so an application that never // introduces Unknown values can be guaranteed to never receive any either. func (val Value) IsKnown() bool { if val.IsMarked() { return val.unmarkForce().IsKnown() } return val.v != unknown } // IsNull returns true if the value is null. Values of any type can be // null, but any operations on a null value will panic. No operation ever // produces null, so an application that never introduces Null values can // be guaranteed to never receive any either. func (val Value) IsNull() bool { if val.IsMarked() { return val.unmarkForce().IsNull() } return val.v == nil } // NilVal is an invalid Value that can be used as a placeholder when returning // with an error from a function that returns (Value, error). // // NilVal is *not* a valid error and so no operations may be performed on it. // Any attempt to use it will result in a panic. // // This should not be confused with the idea of a Null value, as returned by // NullVal. NilVal is a nil within the *Go* type system, and is invalid in // the cty type system. Null values *do* exist in the cty type system. var NilVal = Value{ ty: Type{typeImpl: nil}, v: nil, } // IsWhollyKnown is an extension of IsKnown that also recursively checks // inside collections and structures to see if there are any nested unknown // values. func (val Value) IsWhollyKnown() bool { if val.IsMarked() { return val.unmarkForce().IsWhollyKnown() } if !val.IsKnown() { return false } if val.IsNull() { // Can't recurse into a null, so we're done return true } switch { case val.CanIterateElements(): for it := val.ElementIterator(); it.Next(); { _, ev := it.Element() if !ev.IsWhollyKnown() { return false } } return true default: return true } } // HasWhollyKnownType checks if the value is dynamic, or contains any nested // DynamicVal. This implies that both the value is not known, and the final // type may change. func (val Value) HasWhollyKnownType() bool { // a null dynamic type is known if val.IsNull() { return true } // an unknown DynamicPseudoType is a DynamicVal, but we don't want to // check that value for equality here, since this method is used within the // equality check. if !val.IsKnown() && val.ty == DynamicPseudoType { return false } if val.CanIterateElements() { // if the value is not known, then we can look directly at the internal // types if !val.IsKnown() { return !val.ty.HasDynamicTypes() } for it := val.ElementIterator(); it.Next(); { _, ev := it.Element() if !ev.HasWhollyKnownType() { return false } } } return true } go-cty-1.12.1/cty/value_init.go000066400000000000000000000245221433256746400163060ustar00rootroot00000000000000package cty import ( "fmt" "math/big" "reflect" "golang.org/x/text/unicode/norm" "github.com/zclconf/go-cty/cty/set" ) // BoolVal returns a Value of type Number whose internal value is the given // bool. func BoolVal(v bool) Value { return Value{ ty: Bool, v: v, } } // NumberVal returns a Value of type Number whose internal value is the given // big.Float. The returned value becomes the owner of the big.Float object, // and so it's forbidden for the caller to mutate the object after it's // wrapped in this way. func NumberVal(v *big.Float) Value { return Value{ ty: Number, v: v, } } // ParseNumberVal returns a Value of type number produced by parsing the given // string as a decimal real number. To ensure that two identical strings will // always produce an equal number, always use this function to derive a number // from a string; it will ensure that the precision and rounding mode for the // internal big decimal is configured in a consistent way. // // If the given string cannot be parsed as a number, the returned error has // the message "a number is required", making it suitable to return to an // end-user to signal a type conversion error. // // If the given string contains a number that becomes a recurring fraction // when expressed in binary then it will be truncated to have a 512-bit // mantissa. Note that this is a higher precision than that of a float64, // so coverting the same decimal number first to float64 and then calling // NumberFloatVal will not produce an equal result; the conversion first // to float64 will round the mantissa to fewer than 512 bits. func ParseNumberVal(s string) (Value, error) { // Base 10, precision 512, and rounding to nearest even is the standard // way to handle numbers arriving as strings. f, _, err := big.ParseFloat(s, 10, 512, big.ToNearestEven) if err != nil { return NilVal, fmt.Errorf("a number is required") } return NumberVal(f), nil } // MustParseNumberVal is like ParseNumberVal but it will panic in case of any // error. It can be used during initialization or any other situation where // the given string is a constant or otherwise known to be correct by the // caller. func MustParseNumberVal(s string) Value { ret, err := ParseNumberVal(s) if err != nil { panic(err) } return ret } // NumberIntVal returns a Value of type Number whose internal value is equal // to the given integer. func NumberIntVal(v int64) Value { return NumberVal(new(big.Float).SetInt64(v)) } // NumberUIntVal returns a Value of type Number whose internal value is equal // to the given unsigned integer. func NumberUIntVal(v uint64) Value { return NumberVal(new(big.Float).SetUint64(v)) } // NumberFloatVal returns a Value of type Number whose internal value is // equal to the given float. func NumberFloatVal(v float64) Value { return NumberVal(new(big.Float).SetFloat64(v)) } // StringVal returns a Value of type String whose internal value is the // given string. // // Strings must be UTF-8 encoded sequences of valid unicode codepoints, and // they are NFC-normalized on entry into the world of cty values. // // If the given string is not valid UTF-8 then behavior of string operations // is undefined. func StringVal(v string) Value { return Value{ ty: String, v: NormalizeString(v), } } // NormalizeString applies the same normalization that cty applies when // constructing string values. // // A return value from this function can be meaningfully compared byte-for-byte // with a Value.AsString result. func NormalizeString(s string) string { return norm.NFC.String(s) } // ObjectVal returns a Value of an object type whose structure is defined // by the key names and value types in the given map. func ObjectVal(attrs map[string]Value) Value { attrTypes := make(map[string]Type, len(attrs)) attrVals := make(map[string]interface{}, len(attrs)) for attr, val := range attrs { attr = NormalizeString(attr) attrTypes[attr] = val.ty attrVals[attr] = val.v } return Value{ ty: Object(attrTypes), v: attrVals, } } // TupleVal returns a Value of a tuple type whose element types are // defined by the value types in the given slice. func TupleVal(elems []Value) Value { elemTypes := make([]Type, len(elems)) elemVals := make([]interface{}, len(elems)) for i, val := range elems { elemTypes[i] = val.ty elemVals[i] = val.v } return Value{ ty: Tuple(elemTypes), v: elemVals, } } // ListVal returns a Value of list type whose element type is defined by // the types of the given values, which must be homogenous. // // If the types are not all consistent (aside from elements that are of the // dynamic pseudo-type) then this function will panic. It will panic also // if the given list is empty, since then the element type cannot be inferred. // (See also ListValEmpty.) func ListVal(vals []Value) Value { if len(vals) == 0 { panic("must not call ListVal with empty slice") } elementType := DynamicPseudoType rawList := make([]interface{}, len(vals)) for i, val := range vals { if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { panic(fmt.Errorf( "inconsistent list element types (%#v then %#v)", elementType, val.ty, )) } rawList[i] = val.v } return Value{ ty: List(elementType), v: rawList, } } // ListValEmpty returns an empty list of the given element type. func ListValEmpty(element Type) Value { return Value{ ty: List(element), v: []interface{}{}, } } // CanListVal returns false if the given Values can not be coalesced // into a single List due to inconsistent element types. func CanListVal(vals []Value) bool { elementType := DynamicPseudoType for _, val := range vals { if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { return false } } return true } // MapVal returns a Value of a map type whose element type is defined by // the types of the given values, which must be homogenous. // // If the types are not all consistent (aside from elements that are of the // dynamic pseudo-type) then this function will panic. It will panic also // if the given map is empty, since then the element type cannot be inferred. // (See also MapValEmpty.) func MapVal(vals map[string]Value) Value { if len(vals) == 0 { panic("must not call MapVal with empty map") } elementType := DynamicPseudoType rawMap := make(map[string]interface{}, len(vals)) for key, val := range vals { if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { panic(fmt.Errorf( "inconsistent map element types (%#v then %#v)", elementType, val.ty, )) } rawMap[NormalizeString(key)] = val.v } return Value{ ty: Map(elementType), v: rawMap, } } // MapValEmpty returns an empty map of the given element type. func MapValEmpty(element Type) Value { return Value{ ty: Map(element), v: map[string]interface{}{}, } } // CanMapVal returns false if the given Values can not be coalesced into a // single Map due to inconsistent element types. func CanMapVal(vals map[string]Value) bool { elementType := DynamicPseudoType for _, val := range vals { if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { return false } } return true } // SetVal returns a Value of set type whose element type is defined by // the types of the given values, which must be homogenous. // // If the types are not all consistent (aside from elements that are of the // dynamic pseudo-type) then this function will panic. It will panic also // if the given list is empty, since then the element type cannot be inferred. // (See also SetValEmpty.) func SetVal(vals []Value) Value { if len(vals) == 0 { panic("must not call SetVal with empty slice") } elementType := DynamicPseudoType rawList := make([]interface{}, len(vals)) var markSets []ValueMarks for i, val := range vals { if unmarkedVal, marks := val.UnmarkDeep(); len(marks) > 0 { val = unmarkedVal markSets = append(markSets, marks) } if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { panic(fmt.Errorf( "inconsistent set element types (%#v then %#v)", elementType, val.ty, )) } rawList[i] = val.v } rawVal := set.NewSetFromSlice(set.Rules[interface{}](setRules{elementType}), rawList) return Value{ ty: Set(elementType), v: rawVal, }.WithMarks(markSets...) } // CanSetVal returns false if the given Values can not be coalesced // into a single Set due to inconsistent element types. func CanSetVal(vals []Value) bool { elementType := DynamicPseudoType var markSets []ValueMarks for _, val := range vals { if unmarkedVal, marks := val.UnmarkDeep(); len(marks) > 0 { val = unmarkedVal markSets = append(markSets, marks) } if elementType == DynamicPseudoType { elementType = val.ty } else if val.ty != DynamicPseudoType && !elementType.Equals(val.ty) { return false } } return true } // SetValFromValueSet returns a Value of set type based on an already-constructed // ValueSet. // // The element type of the returned value is the element type of the given // set. func SetValFromValueSet(s ValueSet) Value { ety := s.ElementType() rawVal := s.s.Copy() // copy so caller can't mutate what we wrap return Value{ ty: Set(ety), v: rawVal, } } // SetValEmpty returns an empty set of the given element type. func SetValEmpty(element Type) Value { return Value{ ty: Set(element), v: set.NewSet(set.Rules[interface{}](setRules{element})), } } // CapsuleVal creates a value of the given capsule type using the given // wrapVal, which must be a pointer to a value of the capsule type's native // type. // // This function will panic if the given type is not a capsule type, if // the given wrapVal is not compatible with the given capsule type, or if // wrapVal is not a pointer. func CapsuleVal(ty Type, wrapVal interface{}) Value { if !ty.IsCapsuleType() { panic("not a capsule type") } wv := reflect.ValueOf(wrapVal) if wv.Kind() != reflect.Ptr { panic("wrapVal is not a pointer") } it := ty.typeImpl.(*capsuleType).GoType if !wv.Type().Elem().AssignableTo(it) { panic("wrapVal target is not compatible with the given capsule type") } return Value{ ty: ty, v: wrapVal, } } go-cty-1.12.1/cty/value_init_test.go000066400000000000000000000177161433256746400173540ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestSetVal(t *testing.T) { plain := SetVal([]Value{True}) marked := SetVal([]Value{True}).Mark(1) deepMarked := SetVal([]Value{True.Mark(2), True.Mark(3)}) if plain.RawEquals(marked) { t.Errorf("plain should be unequal to marked\nplain: %#v\nmarked: %#v", plain, marked) } if marked.RawEquals(deepMarked) { t.Errorf("marked should be unequal to deepMarked\nmarked: %#v\ndeepmarked: %#v", marked, deepMarked) } if got, want := marked.Marks(), NewValueMarks(1); !got.Equal(want) { t.Errorf("wrong marks for marked\ngot: %#v\nwant: %#v", got, want) } if got, want := deepMarked.Marks(), NewValueMarks(2, 3); !got.Equal(want) { // Both 2 and 3 marks are preserved even though both of them are // marking the same value True, and thus the resulting set contains // only one element. t.Errorf("wrong marks for deepMarked\ngot: %#v\nwant: %#v", got, want) } if got, want := deepMarked.unmarkForce(), SetVal([]Value{True}); !got.RawEquals(want) { t.Errorf("wrong unmarked value for deepMarked\ngot: %#v\nwant: %#v", got, want) } } func TestSetVal_nestedStructures(t *testing.T) { testCases := []struct { Name string Elems []Value }{ { "set", []Value{ SetVal([]Value{ NumberIntVal(5), }), }, }, { "doubly nested set", []Value{ SetVal([]Value{ SetVal([]Value{ NumberIntVal(5), }), }), }, }, { "list", []Value{ ListVal([]Value{ NumberIntVal(5), }), }, }, { "doubly nested list", []Value{ ListVal([]Value{ ListVal([]Value{ NumberIntVal(5), }), }), }, }, { "map", []Value{ MapVal(map[string]Value{ "key": NumberIntVal(5), }), }, }, { "doubly nested map", []Value{ MapVal(map[string]Value{ "key": MapVal(map[string]Value{ "child": StringVal("hello world"), }), }), }, }, { "tuple", []Value{ TupleVal([]Value{ NumberIntVal(5), }), }, }, { "doubly nested tuple", []Value{ TupleVal([]Value{ TupleVal([]Value{ NumberIntVal(5), }), }), }, }, } for i, tc := range testCases { t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { SetVal(tc.Elems) }) } } func TestCanListVal(t *testing.T) { testCases := []struct { Elems []Value Want bool }{ // Valid lists { []Value{StringVal("Hello"), StringVal("World")}, true, }, { []Value{NumberIntVal(13), NumberIntVal(31)}, true, }, { []Value{BoolVal(true), BoolVal(false)}, true, }, { []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), ListVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, { []Value{ MapVal(map[string]Value{ "a": StringVal("Hello"), }), MapVal(map[string]Value{ "c": StringVal("World"), }), }, true, }, { []Value{ SetVal([]Value{ StringVal("Hello"), StringVal("World"), }), SetVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, // invalid list elements { []Value{StringVal("hello"), NumberIntVal(13)}, false, }, { []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), MapVal(map[string]Value{ "a": StringVal("bloop"), }), }, false, }, { // List of string and List of lists []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), ListVal([]Value{ ListVal([]Value{ StringVal("a"), StringVal("b"), }), ListVal([]Value{ StringVal("c"), StringVal("d"), }), }), }, false, }, { // Inconsistent map elements []Value{ MapVal(map[string]Value{ "a": StringVal("Hello"), }), MapVal(map[string]Value{ "a": BoolVal(true), }), }, false, }, } for _, tc := range testCases { got := CanListVal(tc.Elems) if got != tc.Want { t.Errorf("wrong result for elements %#v:\ngot %v, want %v", tc.Elems, got, tc.Want) } } } func TestCanSetVal(t *testing.T) { testCases := []struct { Elems []Value Want bool }{ // Valid set elements { []Value{StringVal("Hello"), StringVal("World")}, true, }, { []Value{StringVal("Hello").Mark(1), StringVal("World").Mark(2)}, true, }, { []Value{NumberIntVal(13), NumberIntVal(31)}, true, }, { []Value{BoolVal(true), BoolVal(false)}, true, }, { []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), ListVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, { []Value{ MapVal(map[string]Value{ "a": StringVal("Hello"), }), MapVal(map[string]Value{ "c": StringVal("World"), }), }, true, }, { []Value{ SetVal([]Value{ StringVal("Hello"), StringVal("World"), }), SetVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, // invalid set elements { []Value{StringVal("hello"), NumberIntVal(13)}, false, }, { []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), MapVal(map[string]Value{ "a": StringVal("bloop"), }), }, false, }, { // List of string and List of lists []Value{ ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), ListVal([]Value{ ListVal([]Value{ StringVal("a"), StringVal("b"), }), ListVal([]Value{ StringVal("c"), StringVal("d"), }), }), }, false, }, { // Inconsistent map elements []Value{ MapVal(map[string]Value{ "a": StringVal("Hello"), }), MapVal(map[string]Value{ "a": BoolVal(true), }), }, false, }, } for _, tc := range testCases { got := CanSetVal(tc.Elems) if got != tc.Want { t.Errorf("wrong result for elements %#v:\ngot %v, want %v", tc.Elems, got, tc.Want) } } } func TestCanMapVal(t *testing.T) { testCases := []struct { Elems map[string]Value Want bool }{ // Valid lists { map[string]Value{"a": StringVal("Hello"), "b": StringVal("World")}, true, }, { map[string]Value{"one": NumberIntVal(13), "two": NumberIntVal(31)}, true, }, { map[string]Value{"one": BoolVal(true), "two": BoolVal(false)}, true, }, { map[string]Value{ "lista": ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), "listb": ListVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, { map[string]Value{ "map_a": MapVal(map[string]Value{ "a": StringVal("Hello"), }), "map_b": MapVal(map[string]Value{ "c": StringVal("World"), }), }, true, }, { map[string]Value{ "set_a": SetVal([]Value{ StringVal("Hello"), StringVal("World"), }), "set_b": SetVal([]Value{ StringVal("beep"), StringVal("boop"), StringVal("bloop"), }), }, true, }, // invalid map elements { map[string]Value{"one": StringVal("hello"), "two": NumberIntVal(13)}, false, }, { map[string]Value{ "one": ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), "two": MapVal(map[string]Value{ "a": StringVal("bloop"), }), }, false, }, { map[string]Value{ "one": ListVal([]Value{ StringVal("Hello"), StringVal("World"), }), "two": ListVal([]Value{ ListVal([]Value{ StringVal("a"), StringVal("b"), }), ListVal([]Value{ StringVal("c"), StringVal("d"), }), }), }, false, }, { // Inconsistent map elements map[string]Value{ "one": MapVal(map[string]Value{ "a": StringVal("Hello"), }), "two": MapVal(map[string]Value{ "a": BoolVal(true), }), }, false, }, } for _, tc := range testCases { got := CanMapVal(tc.Elems) if got != tc.Want { t.Errorf("wrong result for elements %#v:\ngot %v, want %v", tc.Elems, got, tc.Want) } } } go-cty-1.12.1/cty/value_ops.go000066400000000000000000001171001433256746400161370ustar00rootroot00000000000000package cty import ( "fmt" "math/big" "github.com/zclconf/go-cty/cty/set" ) // GoString is an implementation of fmt.GoStringer that produces concise // source-like representations of values suitable for use in debug messages. func (val Value) GoString() string { if val.IsMarked() { unVal, marks := val.Unmark() if len(marks) == 1 { var mark interface{} for m := range marks { mark = m } return fmt.Sprintf("%#v.Mark(%#v)", unVal, mark) } return fmt.Sprintf("%#v.WithMarks(%#v)", unVal, marks) } if val == NilVal { return "cty.NilVal" } if val.IsNull() { return fmt.Sprintf("cty.NullVal(%#v)", val.ty) } if val == DynamicVal { // is unknown, so must be before the IsKnown check below return "cty.DynamicVal" } if !val.IsKnown() { return fmt.Sprintf("cty.UnknownVal(%#v)", val.ty) } // By the time we reach here we've dealt with all of the exceptions around // unknowns and nulls, so we're guaranteed that the values are the // canonical internal representation of the given type. switch val.ty { case Bool: if val.v.(bool) { return "cty.True" } return "cty.False" case Number: if f, ok := val.v.(big.Float); ok { panic(fmt.Sprintf("number value contains big.Float value %s, rather than pointer to big.Float", f.Text('g', -1))) } fv := val.v.(*big.Float) // We'll try to use NumberIntVal or NumberFloatVal if we can, since // the fully-general initializer call is pretty ugly-looking. if fv.IsInt() { return fmt.Sprintf("cty.NumberIntVal(%#v)", fv) } if rfv, accuracy := fv.Float64(); accuracy == big.Exact { return fmt.Sprintf("cty.NumberFloatVal(%#v)", rfv) } return fmt.Sprintf("cty.MustParseNumberVal(%q)", fv.Text('f', -1)) case String: return fmt.Sprintf("cty.StringVal(%#v)", val.v) } switch { case val.ty.IsSetType(): vals := val.AsValueSlice() if len(vals) == 0 { return fmt.Sprintf("cty.SetValEmpty(%#v)", val.ty.ElementType()) } return fmt.Sprintf("cty.SetVal(%#v)", vals) case val.ty.IsListType(): vals := val.AsValueSlice() if len(vals) == 0 { return fmt.Sprintf("cty.ListValEmpty(%#v)", val.ty.ElementType()) } return fmt.Sprintf("cty.ListVal(%#v)", vals) case val.ty.IsMapType(): vals := val.AsValueMap() if len(vals) == 0 { return fmt.Sprintf("cty.MapValEmpty(%#v)", val.ty.ElementType()) } return fmt.Sprintf("cty.MapVal(%#v)", vals) case val.ty.IsTupleType(): if val.ty.Equals(EmptyTuple) { return "cty.EmptyTupleVal" } vals := val.AsValueSlice() return fmt.Sprintf("cty.TupleVal(%#v)", vals) case val.ty.IsObjectType(): if val.ty.Equals(EmptyObject) { return "cty.EmptyObjectVal" } vals := val.AsValueMap() return fmt.Sprintf("cty.ObjectVal(%#v)", vals) case val.ty.IsCapsuleType(): impl := val.ty.CapsuleOps().GoString if impl == nil { return fmt.Sprintf("cty.CapsuleVal(%#v, %#v)", val.ty, val.v) } return impl(val.EncapsulatedValue()) } // Default exposes implementation details, so should actually cover // all of the cases above for good caller UX. return fmt.Sprintf("cty.Value{ty: %#v, v: %#v}", val.ty, val.v) } // Equals returns True if the receiver and the given other value have the // same type and are exactly equal in value. // // As a special case, two null values are always equal regardless of type. // // The usual short-circuit rules apply, so the result will be unknown if // either of the given values are. // // Use RawEquals to compare if two values are equal *ignoring* the // short-circuit rules and the exception for null values. func (val Value) Equals(other Value) Value { if val.ContainsMarked() || other.ContainsMarked() { val, valMarks := val.UnmarkDeep() other, otherMarks := other.UnmarkDeep() return val.Equals(other).WithMarks(valMarks, otherMarks) } // Start by handling Unknown values before considering types. // This needs to be done since Null values are always equal regardless of // type. switch { case !val.IsKnown() && !other.IsKnown(): // both unknown return UnknownVal(Bool) case val.IsKnown() && !other.IsKnown(): switch { case val.IsNull(), other.ty.HasDynamicTypes(): // If known is Null, we need to wait for the unknown value since // nulls of any type are equal. // An unknown with a dynamic type compares as unknown, which we need // to check before the type comparison below. return UnknownVal(Bool) case !val.ty.Equals(other.ty): // There is no null comparison or dynamic types, so unequal types // will never be equal. return False default: return UnknownVal(Bool) } case other.IsKnown() && !val.IsKnown(): switch { case other.IsNull(), val.ty.HasDynamicTypes(): // If known is Null, we need to wait for the unknown value since // nulls of any type are equal. // An unknown with a dynamic type compares as unknown, which we need // to check before the type comparison below. return UnknownVal(Bool) case !other.ty.Equals(val.ty): // There's no null comparison or dynamic types, so unequal types // will never be equal. return False default: return UnknownVal(Bool) } } switch { case val.IsNull() && other.IsNull(): // Nulls are always equal, regardless of type return BoolVal(true) case val.IsNull() || other.IsNull(): // If only one is null then the result must be false return BoolVal(false) } // Check if there are any nested dynamic values making this comparison // unknown. if !val.HasWhollyKnownType() || !other.HasWhollyKnownType() { // Even if we have dynamic values, we can still determine inequality if // there is no way the types could later conform. if val.ty.TestConformance(other.ty) != nil && other.ty.TestConformance(val.ty) != nil { return BoolVal(false) } return UnknownVal(Bool) } if !val.ty.Equals(other.ty) { return BoolVal(false) } ty := val.ty result := false switch { case ty == Number: result = rawNumberEqual(val.v.(*big.Float), other.v.(*big.Float)) case ty == Bool: result = val.v.(bool) == other.v.(bool) case ty == String: // Simple equality is safe because we NFC-normalize strings as they // enter our world from StringVal, and so we can assume strings are // always in normal form. result = val.v.(string) == other.v.(string) case ty.IsObjectType(): oty := ty.typeImpl.(typeObject) result = true for attr, aty := range oty.AttrTypes { lhs := Value{ ty: aty, v: val.v.(map[string]interface{})[attr], } rhs := Value{ ty: aty, v: other.v.(map[string]interface{})[attr], } eq := lhs.Equals(rhs) if !eq.IsKnown() { return UnknownVal(Bool) } if eq.False() { result = false break } } case ty.IsTupleType(): tty := ty.typeImpl.(typeTuple) result = true for i, ety := range tty.ElemTypes { lhs := Value{ ty: ety, v: val.v.([]interface{})[i], } rhs := Value{ ty: ety, v: other.v.([]interface{})[i], } eq := lhs.Equals(rhs) if !eq.IsKnown() { return UnknownVal(Bool) } if eq.False() { result = false break } } case ty.IsListType(): ety := ty.typeImpl.(typeList).ElementTypeT if len(val.v.([]interface{})) == len(other.v.([]interface{})) { result = true for i := range val.v.([]interface{}) { lhs := Value{ ty: ety, v: val.v.([]interface{})[i], } rhs := Value{ ty: ety, v: other.v.([]interface{})[i], } eq := lhs.Equals(rhs) if !eq.IsKnown() { return UnknownVal(Bool) } if eq.False() { result = false break } } } case ty.IsSetType(): s1 := val.v.(set.Set[interface{}]) s2 := other.v.(set.Set[interface{}]) equal := true // Two sets are equal if all of their values are known and all values // in one are also in the other. for it := s1.Iterator(); it.Next(); { rv := it.Value() if rv == unknown { // "unknown" is the internal representation of unknown-ness return UnknownVal(Bool) } if !s2.Has(rv) { equal = false } } for it := s2.Iterator(); it.Next(); { rv := it.Value() if rv == unknown { // "unknown" is the internal representation of unknown-ness return UnknownVal(Bool) } if !s1.Has(rv) { equal = false } } result = equal case ty.IsMapType(): ety := ty.typeImpl.(typeMap).ElementTypeT if len(val.v.(map[string]interface{})) == len(other.v.(map[string]interface{})) { result = true for k := range val.v.(map[string]interface{}) { if _, ok := other.v.(map[string]interface{})[k]; !ok { result = false break } lhs := Value{ ty: ety, v: val.v.(map[string]interface{})[k], } rhs := Value{ ty: ety, v: other.v.(map[string]interface{})[k], } eq := lhs.Equals(rhs) if !eq.IsKnown() { return UnknownVal(Bool) } if eq.False() { result = false break } } } case ty.IsCapsuleType(): impl := val.ty.CapsuleOps().Equals if impl == nil { impl := val.ty.CapsuleOps().RawEquals if impl == nil { // A capsule type's encapsulated value is a pointer to a value of its // native type, so we can just compare these to get the identity test // we need. return BoolVal(val.v == other.v) } return BoolVal(impl(val.v, other.v)) } ret := impl(val.v, other.v) if !ret.Type().Equals(Bool) { panic(fmt.Sprintf("Equals for %#v returned %#v, not cty.Bool", ty, ret.Type())) } return ret default: // should never happen panic(fmt.Errorf("unsupported value type %#v in Equals", ty)) } return BoolVal(result) } // NotEqual is a shorthand for Equals followed by Not. func (val Value) NotEqual(other Value) Value { return val.Equals(other).Not() } // True returns true if the receiver is True, false if False, and panics if // the receiver is not of type Bool. // // This is a helper function to help write application logic that works with // values, rather than a first-class operation. It does not work with unknown // or null values. For more robust handling with unknown value // short-circuiting, use val.Equals(cty.True). func (val Value) True() bool { val.assertUnmarked() if val.ty != Bool { panic("not bool") } return val.Equals(True).v.(bool) } // False is the opposite of True. func (val Value) False() bool { return !val.True() } // RawEquals returns true if and only if the two given values have the same // type and equal value, ignoring the usual short-circuit rules about // unknowns and dynamic types. // // This method is more appropriate for testing than for real use, since it // skips over usual semantics around unknowns but as a consequence allows // testing the result of another operation that is expected to return unknown. // It returns a primitive Go bool rather than a Value to remind us that it // is not a first-class value operation. func (val Value) RawEquals(other Value) bool { if !val.ty.Equals(other.ty) { return false } if !val.HasSameMarks(other) { return false } // Since we've now checked the marks, we'll unmark for the rest of this... val = val.unmarkForce() other = other.unmarkForce() if (!val.IsKnown()) && (!other.IsKnown()) { return true } if (val.IsKnown() && !other.IsKnown()) || (other.IsKnown() && !val.IsKnown()) { return false } if val.IsNull() && other.IsNull() { return true } if (val.IsNull() && !other.IsNull()) || (other.IsNull() && !val.IsNull()) { return false } if val.ty == DynamicPseudoType && other.ty == DynamicPseudoType { return true } ty := val.ty switch { case ty == Number || ty == Bool || ty == String || ty == DynamicPseudoType: return val.Equals(other).True() case ty.IsObjectType(): oty := ty.typeImpl.(typeObject) for attr, aty := range oty.AttrTypes { lhs := Value{ ty: aty, v: val.v.(map[string]interface{})[attr], } rhs := Value{ ty: aty, v: other.v.(map[string]interface{})[attr], } eq := lhs.RawEquals(rhs) if !eq { return false } } return true case ty.IsTupleType(): tty := ty.typeImpl.(typeTuple) for i, ety := range tty.ElemTypes { lhs := Value{ ty: ety, v: val.v.([]interface{})[i], } rhs := Value{ ty: ety, v: other.v.([]interface{})[i], } eq := lhs.RawEquals(rhs) if !eq { return false } } return true case ty.IsListType(): ety := ty.typeImpl.(typeList).ElementTypeT if len(val.v.([]interface{})) == len(other.v.([]interface{})) { for i := range val.v.([]interface{}) { lhs := Value{ ty: ety, v: val.v.([]interface{})[i], } rhs := Value{ ty: ety, v: other.v.([]interface{})[i], } eq := lhs.RawEquals(rhs) if !eq { return false } } return true } return false case ty.IsSetType(): // Convert the set values into a slice so that we can compare each // value. This is safe because the underlying sets are ordered (see // setRules in set_internals.go), and so the results are guaranteed to // be in a consistent order for two equal sets setList1 := val.AsValueSlice() setList2 := other.AsValueSlice() // If both physical sets have the same length and they have all of their // _known_ values in common, we know that both sets also have the same // number of unknown values. if len(setList1) != len(setList2) { return false } for i := range setList1 { eq := setList1[i].RawEquals(setList2[i]) if !eq { return false } } // If we got here without returning false already then our sets are // equal enough for RawEquals purposes. return true case ty.IsMapType(): ety := ty.typeImpl.(typeMap).ElementTypeT if !val.HasSameMarks(other) { return false } valUn, _ := val.Unmark() otherUn, _ := other.Unmark() if len(valUn.v.(map[string]interface{})) == len(otherUn.v.(map[string]interface{})) { for k := range valUn.v.(map[string]interface{}) { if _, ok := otherUn.v.(map[string]interface{})[k]; !ok { return false } lhs := Value{ ty: ety, v: valUn.v.(map[string]interface{})[k], } rhs := Value{ ty: ety, v: otherUn.v.(map[string]interface{})[k], } eq := lhs.RawEquals(rhs) if !eq { return false } } return true } return false case ty.IsCapsuleType(): impl := val.ty.CapsuleOps().RawEquals if impl == nil { // A capsule type's encapsulated value is a pointer to a value of its // native type, so we can just compare these to get the identity test // we need. return val.v == other.v } return impl(val.v, other.v) default: // should never happen panic(fmt.Errorf("unsupported value type %#v in RawEquals", ty)) } } // Add returns the sum of the receiver and the given other value. Both values // must be numbers; this method will panic if not. func (val Value) Add(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Add(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } ret := new(big.Float) ret.Add(val.v.(*big.Float), other.v.(*big.Float)) return NumberVal(ret) } // Subtract returns receiver minus the given other value. Both values must be // numbers; this method will panic if not. func (val Value) Subtract(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Subtract(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } return val.Add(other.Negate()) } // Negate returns the numeric negative of the receiver, which must be a number. // This method will panic when given a value of any other type. func (val Value) Negate() Value { if val.IsMarked() { val, valMarks := val.Unmark() return val.Negate().WithMarks(valMarks) } if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } ret := new(big.Float).Neg(val.v.(*big.Float)) return NumberVal(ret) } // Multiply returns the product of the receiver and the given other value. // Both values must be numbers; this method will panic if not. func (val Value) Multiply(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Multiply(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } // find the larger precision of the arguments resPrec := val.v.(*big.Float).Prec() otherPrec := other.v.(*big.Float).Prec() if otherPrec > resPrec { resPrec = otherPrec } // make sure we have enough precision for the product ret := new(big.Float).SetPrec(512) ret.Mul(val.v.(*big.Float), other.v.(*big.Float)) // now reduce the precision back to the greater argument, or the minimum // required by the product. minPrec := ret.MinPrec() if minPrec > resPrec { resPrec = minPrec } ret.SetPrec(resPrec) return NumberVal(ret) } // Divide returns the quotient of the receiver and the given other value. // Both values must be numbers; this method will panic if not. // // If the "other" value is exactly zero, this operation will return either // PositiveInfinity or NegativeInfinity, depending on the sign of the // receiver value. For some use-cases the presence of infinities may be // undesirable, in which case the caller should check whether the // other value equals zero before calling and raise an error instead. // // If both values are zero or infinity, this function will panic with // an instance of big.ErrNaN. func (val Value) Divide(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Divide(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } ret := new(big.Float) ret.Quo(val.v.(*big.Float), other.v.(*big.Float)) return NumberVal(ret) } // Modulo returns the remainder of an integer division of the receiver and // the given other value. Both values must be numbers; this method will panic // if not. // // If the "other" value is exactly zero, this operation will return either // PositiveInfinity or NegativeInfinity, depending on the sign of the // receiver value. For some use-cases the presence of infinities may be // undesirable, in which case the caller should check whether the // other value equals zero before calling and raise an error instead. // // This operation is primarily here for use with nonzero natural numbers. // Modulo with "other" as a non-natural number gets somewhat philosophical, // and this function takes a position on what that should mean, but callers // may wish to disallow such things outright or implement their own modulo // if they disagree with the interpretation used here. func (val Value) Modulo(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Modulo(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Number, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } // We cheat a bit here with infinities, just abusing the Multiply operation // to get an infinite result of the correct sign. if val == PositiveInfinity || val == NegativeInfinity || other == PositiveInfinity || other == NegativeInfinity { return val.Multiply(other) } if other.RawEquals(Zero) { return val } // FIXME: This is a bit clumsy. Should come back later and see if there's a // more straightforward way to do this. rat := val.Divide(other) ratFloorInt, _ := rat.v.(*big.Float).Int(nil) // start with a copy of the original larger value so that we do not lose // precision. v := val.v.(*big.Float) work := new(big.Float).Copy(v).SetInt(ratFloorInt) work.Mul(other.v.(*big.Float), work) work.Sub(v, work) return NumberVal(work) } // Absolute returns the absolute (signless) value of the receiver, which must // be a number or this method will panic. func (val Value) Absolute() Value { if val.IsMarked() { val, valMarks := val.Unmark() return val.Absolute().WithMarks(valMarks) } if shortCircuit := mustTypeCheck(Number, Number, val); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Number) return *shortCircuit } ret := (&big.Float{}).Abs(val.v.(*big.Float)) return NumberVal(ret) } // GetAttr returns the value of the given attribute of the receiver, which // must be of an object type that has an attribute of the given name. // This method will panic if the receiver type is not compatible. // // The method will also panic if the given attribute name is not defined // for the value's type. Use the attribute-related methods on Type to // check for the validity of an attribute before trying to use it. // // This method may be called on a value whose type is DynamicPseudoType, // in which case the result will also be DynamicVal. func (val Value) GetAttr(name string) Value { if val.IsMarked() { val, valMarks := val.Unmark() return val.GetAttr(name).WithMarks(valMarks) } if val.ty == DynamicPseudoType { return DynamicVal } if !val.ty.IsObjectType() { panic("value is not an object") } name = NormalizeString(name) if !val.ty.HasAttribute(name) { panic("value has no attribute of that name") } attrType := val.ty.AttributeType(name) if !val.IsKnown() { return UnknownVal(attrType) } return Value{ ty: attrType, v: val.v.(map[string]interface{})[name], } } // Index returns the value of an element of the receiver, which must have // either a list, map or tuple type. This method will panic if the receiver // type is not compatible. // // The key value must be the correct type for the receving collection: a // number if the collection is a list or tuple, or a string if it is a map. // In the case of a list or tuple, the given number must be convertable to int // or this method will panic. The key may alternatively be of // DynamicPseudoType, in which case the result itself is an unknown of the // collection's element type. // // The result is of the receiver collection's element type, or in the case // of a tuple the type of the specific element index requested. // // This method may be called on a value whose type is DynamicPseudoType, // in which case the result will also be the DynamicValue. func (val Value) Index(key Value) Value { if val.IsMarked() || key.IsMarked() { val, valMarks := val.Unmark() key, keyMarks := key.Unmark() return val.Index(key).WithMarks(valMarks, keyMarks) } if val.ty == DynamicPseudoType { return DynamicVal } switch { case val.Type().IsListType(): elty := val.Type().ElementType() if key.Type() == DynamicPseudoType { return UnknownVal(elty) } if key.Type() != Number { panic("element key for list must be number") } if !key.IsKnown() { return UnknownVal(elty) } if !val.IsKnown() { return UnknownVal(elty) } index, accuracy := key.v.(*big.Float).Int64() if accuracy != big.Exact || index < 0 { panic("element key for list must be non-negative integer") } return Value{ ty: elty, v: val.v.([]interface{})[index], } case val.Type().IsMapType(): elty := val.Type().ElementType() if key.Type() == DynamicPseudoType { return UnknownVal(elty) } if key.Type() != String { panic("element key for map must be string") } if !key.IsKnown() { return UnknownVal(elty) } if !val.IsKnown() { return UnknownVal(elty) } keyStr := key.v.(string) return Value{ ty: elty, v: val.v.(map[string]interface{})[keyStr], } case val.Type().IsTupleType(): if key.Type() == DynamicPseudoType { return DynamicVal } if key.Type() != Number { panic("element key for tuple must be number") } if !key.IsKnown() { return DynamicVal } index, accuracy := key.v.(*big.Float).Int64() if accuracy != big.Exact || index < 0 { panic("element key for list must be non-negative integer") } eltys := val.Type().TupleElementTypes() if !val.IsKnown() { return UnknownVal(eltys[index]) } return Value{ ty: eltys[index], v: val.v.([]interface{})[index], } default: panic("not a list, map, or tuple type") } } // HasIndex returns True if the receiver (which must be supported for Index) // has an element with the given index key, or False if it does not. // // The result will be UnknownVal(Bool) if either the collection or the // key value are unknown. // // This method will panic if the receiver is not indexable, but does not // impose any panic-causing type constraints on the key. func (val Value) HasIndex(key Value) Value { if val.IsMarked() || key.IsMarked() { val, valMarks := val.Unmark() key, keyMarks := key.Unmark() return val.HasIndex(key).WithMarks(valMarks, keyMarks) } if val.ty == DynamicPseudoType { return UnknownVal(Bool) } switch { case val.Type().IsListType(): if key.Type() == DynamicPseudoType { return UnknownVal(Bool) } if key.Type() != Number { return False } if !key.IsKnown() { return UnknownVal(Bool) } if !val.IsKnown() { return UnknownVal(Bool) } index, accuracy := key.v.(*big.Float).Int64() if accuracy != big.Exact || index < 0 { return False } return BoolVal(int(index) < len(val.v.([]interface{})) && index >= 0) case val.Type().IsMapType(): if key.Type() == DynamicPseudoType { return UnknownVal(Bool) } if key.Type() != String { return False } if !key.IsKnown() { return UnknownVal(Bool) } if !val.IsKnown() { return UnknownVal(Bool) } keyStr := key.v.(string) _, exists := val.v.(map[string]interface{})[keyStr] return BoolVal(exists) case val.Type().IsTupleType(): if key.Type() == DynamicPseudoType { return UnknownVal(Bool) } if key.Type() != Number { return False } if !key.IsKnown() { return UnknownVal(Bool) } index, accuracy := key.v.(*big.Float).Int64() if accuracy != big.Exact || index < 0 { return False } length := val.Type().Length() return BoolVal(int(index) < length && index >= 0) default: panic("not a list, map, or tuple type") } } // HasElement returns True if the receiver (which must be of a set type) // has the given value as an element, or False if it does not. // // The result will be UnknownVal(Bool) if either the set or the // given value are unknown. // // This method will panic if the receiver is not a set, or if it is a null set. func (val Value) HasElement(elem Value) Value { if val.IsMarked() || elem.IsMarked() { val, valMarks := val.Unmark() elem, elemMarks := elem.Unmark() return val.HasElement(elem).WithMarks(valMarks, elemMarks) } ty := val.Type() if !ty.IsSetType() { panic("not a set type") } if !val.IsKnown() || !elem.IsKnown() { return UnknownVal(Bool) } if val.IsNull() { panic("can't call HasElement on a nil value") } if !ty.ElementType().Equals(elem.Type()) { return False } s := val.v.(set.Set[interface{}]) return BoolVal(s.Has(elem.v)) } // Length returns the length of the receiver, which must be a collection type // or tuple type, as a number value. If the receiver is not a compatible type // then this method will panic. // // If the receiver is unknown then the result is also unknown. // // If the receiver is null then this function will panic. // // Note that Length is not supported for strings. To determine the length // of a string, use the Length function in funcs/stdlib. func (val Value) Length() Value { if val.IsMarked() { val, valMarks := val.Unmark() return val.Length().WithMarks(valMarks) } if val.Type().IsTupleType() { // For tuples, we can return the length even if the value is not known. return NumberIntVal(int64(val.Type().Length())) } if !val.IsKnown() { return UnknownVal(Number) } if val.Type().IsSetType() { // The Length rules are a little different for sets because if any // unknown values are present then the values they are standing in for // may or may not be equal to other elements in the set, and thus they // may or may not coalesce with other elements and produce fewer // items in the resulting set. storeLength := int64(val.v.(set.Set[interface{}]).Length()) if storeLength == 1 || val.IsWhollyKnown() { // If our set is wholly known then we know its length. // // We also know the length if the physical store has only one // element, even if that element is unknown, because there's // nothing else in the set for it to coalesce with and a single // unknown value cannot represent more than one known value. return NumberIntVal(storeLength) } // Otherwise, we cannot predict the length. return UnknownVal(Number) } return NumberIntVal(int64(val.LengthInt())) } // LengthInt is like Length except it returns an int. It has the same behavior // as Length except that it will panic if the receiver is unknown. // // This is an integration method provided for the convenience of code bridging // into Go's type system. // // For backward compatibility with an earlier implementation error, LengthInt's // result can disagree with Length's result for any set containing unknown // values. Length can potentially indicate the set's length is unknown in that // case, whereas LengthInt will return the maximum possible length as if the // unknown values were each a placeholder for a value not equal to any other // value in the set. func (val Value) LengthInt() int { val.assertUnmarked() if val.Type().IsTupleType() { // For tuples, we can return the length even if the value is not known. return val.Type().Length() } if val.Type().IsObjectType() { // For objects, the length is the number of attributes associated with the type. return len(val.Type().AttributeTypes()) } if !val.IsKnown() { panic("value is not known") } if val.IsNull() { panic("value is null") } switch { case val.ty.IsListType(): return len(val.v.([]interface{})) case val.ty.IsSetType(): // NOTE: This is technically not correct in cases where the set // contains unknown values, because in that case we can't know how // many known values those unknown values are standing in for -- they // might coalesce with other values once known. // // However, this incorrect behavior is preserved for backward // compatibility with callers that were relying on LengthInt rather // than calling Length. Instead of panicking when a set contains an // unknown value, LengthInt returns the largest possible length. return val.v.(set.Set[interface{}]).Length() case val.ty.IsMapType(): return len(val.v.(map[string]interface{})) default: panic("value is not a collection") } } // ElementIterator returns an ElementIterator for iterating the elements // of the receiver, which must be a collection type, a tuple type, or an object // type. If called on a method of any other type, this method will panic. // // The value must be Known and non-Null, or this method will panic. // // If the receiver is of a list type, the returned keys will be of type Number // and the values will be of the list's element type. // // If the receiver is of a map type, the returned keys will be of type String // and the value will be of the map's element type. Elements are passed in // ascending lexicographical order by key. // // If the receiver is of a set type, each element is returned as both the // key and the value, since set members are their own identity. // // If the receiver is of a tuple type, the returned keys will be of type Number // and the value will be of the corresponding element's type. // // If the receiver is of an object type, the returned keys will be of type // String and the value will be of the corresponding attributes's type. // // ElementIterator is an integration method, so it cannot handle Unknown // values. This method will panic if the receiver is Unknown. func (val Value) ElementIterator() ElementIterator { val.assertUnmarked() if !val.IsKnown() { panic("can't use ElementIterator on unknown value") } if val.IsNull() { panic("can't use ElementIterator on null value") } return elementIterator(val) } // CanIterateElements returns true if the receiver can support the // ElementIterator method (and by extension, ForEachElement) without panic. func (val Value) CanIterateElements() bool { return canElementIterator(val) } // ForEachElement executes a given callback function for each element of // the receiver, which must be a collection type or tuple type, or this method // will panic. // // ForEachElement uses ElementIterator internally, and so the values passed // to the callback are as described for ElementIterator. // // Returns true if the iteration exited early due to the callback function // returning true, or false if the loop ran to completion. // // ForEachElement is an integration method, so it cannot handle Unknown // values. This method will panic if the receiver is Unknown. func (val Value) ForEachElement(cb ElementCallback) bool { val.assertUnmarked() it := val.ElementIterator() for it.Next() { key, val := it.Element() stop := cb(key, val) if stop { return true } } return false } // Not returns the logical inverse of the receiver, which must be of type // Bool or this method will panic. func (val Value) Not() Value { if val.IsMarked() { val, valMarks := val.Unmark() return val.Not().WithMarks(valMarks) } if shortCircuit := mustTypeCheck(Bool, Bool, val); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Bool) return *shortCircuit } return BoolVal(!val.v.(bool)) } // And returns the result of logical AND with the receiver and the other given // value, which must both be of type Bool or this method will panic. func (val Value) And(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.And(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Bool) return *shortCircuit } return BoolVal(val.v.(bool) && other.v.(bool)) } // Or returns the result of logical OR with the receiver and the other given // value, which must both be of type Bool or this method will panic. func (val Value) Or(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.Or(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Bool, Bool, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Bool) return *shortCircuit } return BoolVal(val.v.(bool) || other.v.(bool)) } // LessThan returns True if the receiver is less than the other given value, // which must both be numbers or this method will panic. func (val Value) LessThan(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.LessThan(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Bool) return *shortCircuit } return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) < 0) } // GreaterThan returns True if the receiver is greater than the other given // value, which must both be numbers or this method will panic. func (val Value) GreaterThan(other Value) Value { if val.IsMarked() || other.IsMarked() { val, valMarks := val.Unmark() other, otherMarks := other.Unmark() return val.GreaterThan(other).WithMarks(valMarks, otherMarks) } if shortCircuit := mustTypeCheck(Number, Bool, val, other); shortCircuit != nil { shortCircuit = forceShortCircuitType(shortCircuit, Bool) return *shortCircuit } return BoolVal(val.v.(*big.Float).Cmp(other.v.(*big.Float)) > 0) } // LessThanOrEqualTo is equivalent to LessThan and Equal combined with Or. func (val Value) LessThanOrEqualTo(other Value) Value { return val.LessThan(other).Or(val.Equals(other)) } // GreaterThanOrEqualTo is equivalent to GreaterThan and Equal combined with Or. func (val Value) GreaterThanOrEqualTo(other Value) Value { return val.GreaterThan(other).Or(val.Equals(other)) } // AsString returns the native string from a non-null, non-unknown cty.String // value, or panics if called on any other value. func (val Value) AsString() string { val.assertUnmarked() if val.ty != String { panic("not a string") } if val.IsNull() { panic("value is null") } if !val.IsKnown() { panic("value is unknown") } return val.v.(string) } // AsBigFloat returns a big.Float representation of a non-null, non-unknown // cty.Number value, or panics if called on any other value. // // For more convenient conversions to other native numeric types, use the // "gocty" package. func (val Value) AsBigFloat() *big.Float { val.assertUnmarked() if val.ty != Number { panic("not a number") } if val.IsNull() { panic("value is null") } if !val.IsKnown() { panic("value is unknown") } // Copy the float so that callers can't mutate our internal state return new(big.Float).Copy(val.v.(*big.Float)) } // AsValueSlice returns a []cty.Value representation of a non-null, non-unknown // value of any type that CanIterateElements, or panics if called on // any other value. // // For more convenient conversions to slices of more specific types, use // the "gocty" package. func (val Value) AsValueSlice() []Value { val.assertUnmarked() l := val.LengthInt() if l == 0 { return nil } ret := make([]Value, 0, l) for it := val.ElementIterator(); it.Next(); { _, v := it.Element() ret = append(ret, v) } return ret } // AsValueMap returns a map[string]cty.Value representation of a non-null, // non-unknown value of any type that CanIterateElements, or panics if called // on any other value. // // For more convenient conversions to maps of more specific types, use // the "gocty" package. func (val Value) AsValueMap() map[string]Value { val.assertUnmarked() l := val.LengthInt() if l == 0 { return nil } ret := make(map[string]Value, l) for it := val.ElementIterator(); it.Next(); { k, v := it.Element() ret[k.AsString()] = v } return ret } // AsValueSet returns a ValueSet representation of a non-null, // non-unknown value of any collection type, or panics if called // on any other value. // // Unlike AsValueSlice and AsValueMap, this method requires specifically a // collection type (list, set or map) and does not allow structural types // (tuple or object), because the ValueSet type requires homogenous // element types. // // The returned ValueSet can store only values of the receiver's element type. func (val Value) AsValueSet() ValueSet { val.assertUnmarked() if !val.Type().IsCollectionType() { panic("not a collection type") } // We don't give the caller our own set.Set (assuming we're a cty.Set value) // because then the caller could mutate our internals, which is forbidden. // Instead, we will construct a new set and append our elements into it. ret := NewValueSet(val.Type().ElementType()) for it := val.ElementIterator(); it.Next(); { _, v := it.Element() ret.Add(v) } return ret } // EncapsulatedValue returns the native value encapsulated in a non-null, // non-unknown capsule-typed value, or panics if called on any other value. // // The result is the same pointer that was passed to CapsuleVal to create // the value. Since cty considers values to be immutable, it is strongly // recommended to treat the encapsulated value itself as immutable too. func (val Value) EncapsulatedValue() interface{} { val.assertUnmarked() if !val.Type().IsCapsuleType() { panic("not a capsule-typed value") } return val.v } go-cty-1.12.1/cty/value_ops_test.go000066400000000000000000001654621433256746400172140ustar00rootroot00000000000000package cty import ( "fmt" "reflect" "testing" ) func TestValueEquals(t *testing.T) { capsuleA := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleA"}) capsuleB := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleB"}) capsuleC := CapsuleVal(capsuleTestType2, &capsuleTestType2Native{"capsuleC"}) tests := []struct { LHS Value RHS Value Expected Value }{ // Booleans { BoolVal(true), BoolVal(true), BoolVal(true), }, { BoolVal(false), BoolVal(false), BoolVal(true), }, { BoolVal(true), BoolVal(false), BoolVal(false), }, // Numbers { NumberIntVal(1), NumberIntVal(2), BoolVal(false), }, { NumberIntVal(2), NumberIntVal(2), BoolVal(true), }, { NumberIntVal(2), NumberFloatVal(2.2), BoolVal(false), }, { NumberFloatVal(2.0), NumberFloatVal(2.2), BoolVal(false), }, { MustParseNumberVal("0.0"), MustParseNumberVal("-0.0"), // a statically-generated negative zero BoolVal(true), }, { NumberFloatVal(0.0), NumberFloatVal(0.0).Multiply(NumberIntVal(-1)), // a dynamically-generated negative zero BoolVal(true), }, { MustParseNumberVal("3.14159265358979323846264338327950288419716939937510582097494459"), MustParseNumberVal("3.14159265358979323846264338327950288419716939937510582097494459"), BoolVal(true), }, { MustParseNumberVal("-3.14159265358979323846264338327950288419716939937510582097494459"), MustParseNumberVal("-3.14159265358979323846264338327950288419716939937510582097494459"), BoolVal(true), }, { MustParseNumberVal("3.14159265358979323846264338327950288419716939937510582097494459"), MustParseNumberVal("-3.14159265358979323846264338327950288419716939937510582097494459"), BoolVal(false), }, { MustParseNumberVal("1.2"), NumberFloatVal(1.2), BoolVal(true), }, { MustParseNumberVal("1.22222"), NumberFloatVal(1.22222), BoolVal(true), }, // Strings { StringVal(""), StringVal(""), BoolVal(true), }, { StringVal("hello"), StringVal("hello"), BoolVal(true), }, { StringVal("hello"), StringVal("world"), BoolVal(false), }, { StringVal("0"), StringVal(""), BoolVal(false), }, { StringVal("años"), StringVal("años"), BoolVal(true), }, { // Combining marks are normalized by StringVal StringVal("años"), // (precomposed tilde-n) StringVal("años"), // (combining tilde followed by bare n) BoolVal(true), }, { // tilde-n does not normalize with bare n StringVal("años"), StringVal("anos"), BoolVal(false), }, // Objects { ObjectVal(map[string]Value{}), ObjectVal(map[string]Value{}), BoolVal(true), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), BoolVal(true), }, { ObjectVal(map[string]Value{ "h\u00e9llo": NumberIntVal(1), // precombined é }), ObjectVal(map[string]Value{ "he\u0301llo": NumberIntVal(1), // e with combining acute accent }), BoolVal(true), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{}), BoolVal(false), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), BoolVal(true), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "num": NumberIntVal(2), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "othernum": NumberIntVal(1), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(false), }), BoolVal(false), }, // Tuples { EmptyTupleVal, EmptyTupleVal, BoolVal(true), }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(1)}), BoolVal(true), }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(2)}), BoolVal(false), }, { TupleVal([]Value{StringVal("hi")}), TupleVal([]Value{NumberIntVal(1)}), BoolVal(false), }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), BoolVal(false), }, { TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), TupleVal([]Value{NumberIntVal(1)}), BoolVal(false), }, { TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), BoolVal(true), }, { TupleVal([]Value{UnknownVal(Number)}), TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Bool), }, { TupleVal([]Value{UnknownVal(Number)}), TupleVal([]Value{UnknownVal(Number)}), UnknownVal(Bool), }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{UnknownVal(Number)}), UnknownVal(Bool), }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{DynamicVal}), UnknownVal(Bool), }, { TupleVal([]Value{DynamicVal}), TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Bool), }, { TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Tuple([]Type{Number})), UnknownVal(Bool), }, { UnknownVal(Tuple([]Type{Number})), TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Bool), }, { DynamicVal, TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Bool), }, { TupleVal([]Value{NumberIntVal(1)}), DynamicVal, UnknownVal(Bool), }, // Lists { ListValEmpty(Number), ListValEmpty(Number), BoolVal(true), }, { ListValEmpty(Number), ListValEmpty(Bool), BoolVal(false), }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(1), }), BoolVal(true), }, { ListVal([]Value{ NumberIntVal(1), }), ListValEmpty(String), BoolVal(false), }, { ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), BoolVal(true), }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(2), }), BoolVal(false), }, { ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), ListVal([]Value{ NumberIntVal(1), }), BoolVal(false), }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), BoolVal(false), }, // Maps { MapValEmpty(Number), MapValEmpty(Number), BoolVal(true), }, { MapValEmpty(Number), MapValEmpty(Bool), BoolVal(false), }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "num": NumberIntVal(1), }), BoolVal(true), }, { MapVal(map[string]Value{ "h\u00e9llo": NumberIntVal(1), // precombined é }), MapVal(map[string]Value{ "he\u0301llo": NumberIntVal(1), // e with combining acute accent }), BoolVal(true), }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapValEmpty(String), BoolVal(false), }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), BoolVal(true), }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "num": NumberIntVal(2), }), BoolVal(false), }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "othernum": NumberIntVal(1), }), BoolVal(false), }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), }), BoolVal(false), }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), BoolVal(false), }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(3), }), BoolVal(false), }, // Sets { SetValEmpty(Number), SetValEmpty(Number), BoolVal(true), }, { SetValEmpty(Number), SetValEmpty(Bool), BoolVal(false), }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(1), }), BoolVal(true), }, { SetVal([]Value{ NumberIntVal(1), }), SetValEmpty(String), BoolVal(false), }, { SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), SetVal([]Value{ NumberIntVal(2), NumberIntVal(1), }), BoolVal(true), }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(2), }), BoolVal(false), }, { SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), SetVal([]Value{ NumberIntVal(1), }), BoolVal(false), }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), BoolVal(false), }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ UnknownVal(Number), }), UnknownVal(Bool), }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(1), UnknownVal(Number), }), UnknownVal(Bool), }, { SetVal([]Value{ NumberIntVal(1), UnknownVal(Number), }), SetVal([]Value{ NumberIntVal(1), }), UnknownVal(Bool), }, // Capsules { capsuleA, capsuleA, True, }, { capsuleA, capsuleB, False, }, { capsuleA, capsuleC, False, }, { capsuleA, UnknownVal(capsuleTestType1), // same type UnknownVal(Bool), }, { capsuleA, UnknownVal(capsuleTestType2), // different type False, }, // Unknowns and Dynamics { NumberIntVal(2), UnknownVal(Number), UnknownVal(Bool), }, { NumberIntVal(1), DynamicVal, UnknownVal(Bool), }, { DynamicVal, BoolVal(true), UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { ListVal([]Value{ StringVal("hi"), DynamicVal, }), ListVal([]Value{ StringVal("hi"), DynamicVal, }), UnknownVal(Bool), }, { ListVal([]Value{ StringVal("hi"), UnknownVal(String), }), ListVal([]Value{ StringVal("hi"), UnknownVal(String), }), UnknownVal(Bool), }, { MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": DynamicVal, }), MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": DynamicVal, }), UnknownVal(Bool), }, { MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": UnknownVal(String), }), MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": UnknownVal(String), }), UnknownVal(Bool), }, { NullVal(String), NullVal(DynamicPseudoType), True, }, { NullVal(String), NullVal(String), True, }, { UnknownVal(String), UnknownVal(Number), UnknownVal(Bool), }, { StringVal(""), NullVal(DynamicPseudoType), False, }, { StringVal(""), NullVal(String), False, }, { StringVal(""), UnknownVal(String), UnknownVal(Bool), }, { NullVal(DynamicPseudoType), NullVal(DynamicPseudoType), True, }, { NullVal(String), UnknownVal(Number), UnknownVal(Bool), // because second operand might eventually be null }, { UnknownVal(String), NullVal(Number), UnknownVal(Bool), // because first operand might eventually be null }, { UnknownVal(String), UnknownVal(Number), UnknownVal(Bool), // because both operands might eventually be null }, { StringVal("hello"), UnknownVal(Number), False, // because no number value -- even null -- can be equal to a non-null string }, { UnknownVal(String), NumberIntVal(1), False, // because no string value -- even null -- can be equal to a non-null number }, { ObjectVal(map[string]Value{ "a": StringVal("a"), }), // A null value is always known ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), BoolVal(true), }, { ObjectVal(map[string]Value{ "a": StringVal("a"), "b": UnknownVal(Number), }), // While we have a dynamic type, the different object types should // still compare false ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), "c": UnknownVal(Number), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "a": StringVal("a"), "b": UnknownVal(Number), }), // While we have a dynamic type, the different object types should // still compare false ObjectVal(map[string]Value{ "a": DynamicVal, "c": UnknownVal(Number), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), ObjectVal(map[string]Value{ "a": DynamicVal, }), UnknownVal(Bool), }, { ObjectVal(map[string]Value{ "a": NullVal(List(String)), }), // While the unknown val does contain dynamic types, the overall // container types can't conform. ObjectVal(map[string]Value{ "a": UnknownVal(List(List(DynamicPseudoType))), }), BoolVal(false), }, { ObjectVal(map[string]Value{ "a": NullVal(List(List(String))), }), ObjectVal(map[string]Value{ "a": UnknownVal(List(List(DynamicPseudoType))), }), UnknownVal(Bool), }, // Marks { StringVal("a").Mark(1), StringVal("b"), False.Mark(1), }, { StringVal("a"), StringVal("b").Mark(2), False.Mark(2), }, { StringVal("a").Mark(1), StringVal("b").Mark(2), False.WithMarks(NewValueMarks(1, 2)), }, { MapVal(map[string]Value{ "a": StringVal("a").Mark("boop"), }), MapVal(map[string]Value{ "a": StringVal("a").Mark("blop"), }), True.WithMarks(NewValueMarks("boop", "blop")), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Equals(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Equals(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("wrong Equals result\ngot: %#v\nwant: %#v", got, test.Expected) } }) } } func TestValueRawEquals(t *testing.T) { capsuleA := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleA"}) capsuleB := CapsuleVal(capsuleTestType1, &capsuleTestType1Native{"capsuleB"}) capsuleC := CapsuleVal(capsuleTestType2, &capsuleTestType2Native{"capsuleC"}) tests := []struct { LHS Value RHS Value Expected bool }{ // Booleans { BoolVal(true), BoolVal(true), true, }, { BoolVal(false), BoolVal(false), true, }, { BoolVal(true), BoolVal(false), false, }, // Numbers { NumberIntVal(1), NumberIntVal(2), false, }, { NumberIntVal(2), NumberIntVal(2), true, }, // Strings { StringVal(""), StringVal(""), true, }, { StringVal("hello"), StringVal("hello"), true, }, { StringVal("hello"), StringVal("world"), false, }, { StringVal("0"), StringVal(""), false, }, { StringVal("años"), StringVal("años"), true, }, { // Combining marks are normalized by StringVal StringVal("años"), // (precomposed tilde-n) StringVal("años"), // (combining tilde followed by bare n) true, }, { // tilde-n does not normalize with bare n StringVal("años"), StringVal("anos"), false, }, // Objects { ObjectVal(map[string]Value{}), ObjectVal(map[string]Value{}), true, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), true, }, { ObjectVal(map[string]Value{ "h\u00e9llo": NumberIntVal(1), // precombined é }), ObjectVal(map[string]Value{ "he\u0301llo": NumberIntVal(1), // e with combining acute accent }), true, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{}), false, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), true, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "num": NumberIntVal(2), }), false, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), ObjectVal(map[string]Value{ "othernum": NumberIntVal(1), }), false, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), }), false, }, { ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(true), }), ObjectVal(map[string]Value{ "num": NumberIntVal(1), "flag": BoolVal(false), }), false, }, // Tuples { EmptyTupleVal, EmptyTupleVal, true, }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(1)}), true, }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(2)}), false, }, { TupleVal([]Value{StringVal("hi")}), TupleVal([]Value{NumberIntVal(1)}), false, }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), false, }, { TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), TupleVal([]Value{NumberIntVal(1)}), false, }, { TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), TupleVal([]Value{NumberIntVal(1), NumberIntVal(2)}), true, }, { TupleVal([]Value{UnknownVal(Number)}), TupleVal([]Value{NumberIntVal(1)}), false, }, { TupleVal([]Value{UnknownVal(Number)}), TupleVal([]Value{UnknownVal(Number)}), true, }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{UnknownVal(Number)}), false, }, { TupleVal([]Value{NumberIntVal(1)}), TupleVal([]Value{DynamicVal}), false, }, { TupleVal([]Value{DynamicVal}), TupleVal([]Value{NumberIntVal(1)}), false, }, { TupleVal([]Value{NumberIntVal(1)}), UnknownVal(Tuple([]Type{Number})), false, }, { UnknownVal(Tuple([]Type{Number})), TupleVal([]Value{NumberIntVal(1)}), false, }, { DynamicVal, TupleVal([]Value{NumberIntVal(1)}), false, }, { TupleVal([]Value{NumberIntVal(1)}), DynamicVal, false, }, // Lists { ListValEmpty(Number), ListValEmpty(Number), true, }, { ListValEmpty(Number), ListValEmpty(Bool), false, }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(1), }), true, }, { ListVal([]Value{ NumberIntVal(1), }), ListValEmpty(String), false, }, { ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), true, }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(2), }), false, }, { ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), ListVal([]Value{ NumberIntVal(1), }), false, }, { ListVal([]Value{ NumberIntVal(1), }), ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), false, }, // Maps { MapValEmpty(Number), MapValEmpty(Number), true, }, { MapValEmpty(Number).Mark("a"), MapValEmpty(Number).Mark("a"), true, }, { MapValEmpty(Number).Mark("a"), MapValEmpty(Number), false, }, { MapValEmpty(Number), MapValEmpty(Number).Mark("a"), false, }, { MapValEmpty(Number).Mark("a"), MapValEmpty(Number).Mark("a").Mark("b"), false, }, { MapValEmpty(Number), MapValEmpty(Bool), false, }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "num": NumberIntVal(1), }), true, }, { MapVal(map[string]Value{ "h\u00e9llo": NumberIntVal(1), // precombined é }), MapVal(map[string]Value{ "he\u0301llo": NumberIntVal(1), // e with combining acute accent }), true, }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapValEmpty(String), false, }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), true, }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "num": NumberIntVal(2), }), false, }, { MapVal(map[string]Value{ "num": NumberIntVal(1), }), MapVal(map[string]Value{ "othernum": NumberIntVal(1), }), false, }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), }), false, }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), false, }, { MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(2), }), MapVal(map[string]Value{ "num1": NumberIntVal(1), "num2": NumberIntVal(3), }), false, }, // Sets { SetValEmpty(Number), SetValEmpty(Number), true, }, { SetValEmpty(Number), SetValEmpty(Bool), false, }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(1), }), true, }, { SetVal([]Value{ NumberIntVal(1), }), SetValEmpty(String), false, }, { SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), SetVal([]Value{ NumberIntVal(2), NumberIntVal(1), }), true, }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(2), }), false, }, { SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), SetVal([]Value{ NumberIntVal(1), }), false, }, { SetVal([]Value{ NumberIntVal(1), }), SetVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), false, }, // Capsules { capsuleA, capsuleA, true, }, { capsuleA, capsuleB, false, }, { capsuleA, capsuleC, false, }, { capsuleA, UnknownVal(capsuleTestType1), // same type false, }, { capsuleA, UnknownVal(capsuleTestType2), // different type false, }, // Unknowns and Dynamics { NumberIntVal(2), UnknownVal(Number), false, }, { NumberIntVal(1), DynamicVal, false, }, { DynamicVal, BoolVal(true), false, }, { DynamicVal, DynamicVal, true, //? }, { ListVal([]Value{ StringVal("hi"), DynamicVal, }), ListVal([]Value{ StringVal("hi"), DynamicVal, }), true, }, { ListVal([]Value{ StringVal("hi"), UnknownVal(String), }), ListVal([]Value{ StringVal("hi"), UnknownVal(String), }), true, }, { MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": DynamicVal, }), MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": DynamicVal, }), true, }, { MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": UnknownVal(String), }), MapVal(map[string]Value{ "static": StringVal("hi"), "dynamic": UnknownVal(String), }), true, }, { NullVal(String), NullVal(DynamicPseudoType), false, }, { NullVal(String), NullVal(String), true, }, { UnknownVal(String), UnknownVal(Number), false, }, { StringVal(""), NullVal(DynamicPseudoType), false, }, { StringVal(""), NullVal(String), false, }, { StringVal(""), UnknownVal(String), false, }, { NullVal(DynamicPseudoType), NullVal(DynamicPseudoType), true, }, { NullVal(String), UnknownVal(Number), false, // because second operand might eventually be null }, { UnknownVal(String), NullVal(Number), false, // because first operand might eventually be null }, { UnknownVal(String), UnknownVal(Number), false, // because both operands might eventually be null }, { StringVal("hello"), UnknownVal(Number), false, // because no number value -- even null -- can be equal to a non-null string }, { UnknownVal(String), NumberIntVal(1), false, // because no string value -- even null -- can be equal to a non-null number }, { ObjectVal(map[string]Value{ "a": StringVal("a"), }), // A null value is always known ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), false, }, { ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), true, }, { ObjectVal(map[string]Value{ "a": StringVal("a"), "b": UnknownVal(Number), }), // While we have a dynamic type, the different object types should // still compare false ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), "c": UnknownVal(Number), }), false, }, { ObjectVal(map[string]Value{ "a": StringVal("a"), "b": UnknownVal(Number), }), // While we have a dynamic type, the different object types should // still compare false ObjectVal(map[string]Value{ "a": DynamicVal, "c": UnknownVal(Number), }), false, }, { ObjectVal(map[string]Value{ "a": NullVal(DynamicPseudoType), }), ObjectVal(map[string]Value{ "a": DynamicVal, }), false, }, { ObjectVal(map[string]Value{ "a": NullVal(List(String)), }), // While the unknown val does contain dynamic types, the overall // container types can't conform. ObjectVal(map[string]Value{ "a": UnknownVal(List(List(DynamicPseudoType))), }), false, }, { ObjectVal(map[string]Value{ "a": NullVal(List(List(String))), }), ObjectVal(map[string]Value{ "a": UnknownVal(List(List(DynamicPseudoType))), }), false, }, { ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "b": UnknownVal(String), }), }), }), ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "b": UnknownVal(String), }), }), }), true, }, { ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "b": UnknownVal(String), "c": StringVal("cee"), }), }), }), ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "b": UnknownVal(String), "c": StringVal("cee"), }), }), }), true, }, { ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "b": UnknownVal(String), }), }), }), ObjectVal(map[string]Value{ "a": SetVal([]Value{ ObjectVal(map[string]Value{ "c": UnknownVal(String), }), }), }), false, }, // Marks { StringVal("a").Mark(1), StringVal("b"), false, }, { StringVal("a"), StringVal("b").Mark(2), false, }, { StringVal("a").Mark(1), StringVal("b").Mark(2), false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.RawEquals(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.RawEquals(test.RHS) if !got == test.Expected { t.Fatalf("wrong Equals result\ngot: %#v\nwant: %#v", got, test.Expected) } }) } } func TestValueAdd(t *testing.T) { tests := []struct { LHS Value RHS Value Expected Value }{ { NumberIntVal(1), NumberIntVal(2), NumberIntVal(3), }, { NumberIntVal(1), NumberIntVal(-2), NumberIntVal(-1), }, { NumberIntVal(1), NumberFloatVal(0.5), NumberFloatVal(1.5), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Number), }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Number), }, { NumberIntVal(1), DynamicVal, UnknownVal(Number), }, { DynamicVal, DynamicVal, UnknownVal(Number), }, { Zero.Mark(1), Zero, Zero.Mark(1), }, { Zero, Zero.Mark(2), Zero.Mark(2), }, { Zero.Mark(1), Zero.Mark(2), Zero.WithMarks(NewValueMarks(1, 2)), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Add(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Add(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("Add returned %#v; want %#v", got, test.Expected) } }) } } func TestValueSubtract(t *testing.T) { tests := []struct { LHS Value RHS Value Expected Value }{ { NumberIntVal(1), NumberIntVal(2), NumberIntVal(-1), }, { NumberIntVal(1), NumberIntVal(-2), NumberIntVal(3), }, { NumberIntVal(1), NumberFloatVal(0.5), NumberFloatVal(0.5), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Number), }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Number), }, { NumberIntVal(1), DynamicVal, UnknownVal(Number), }, { DynamicVal, DynamicVal, UnknownVal(Number), }, { Zero.Mark(1), Zero, Zero.Mark(1), }, { Zero, Zero.Mark(2), Zero.Mark(2), }, { Zero.Mark(1), Zero.Mark(2), Zero.WithMarks(NewValueMarks(1, 2)), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Subtract(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Subtract(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("Subtract returned %#v; want %#v", got, test.Expected) } }) } } func TestValueNegate(t *testing.T) { tests := []struct { Receiver Value Expected Value }{ { NumberIntVal(1), NumberIntVal(-1), }, { NumberFloatVal(0.5), NumberFloatVal(-0.5), }, { UnknownVal(Number), UnknownVal(Number), }, { DynamicVal, UnknownVal(Number), }, { Zero.Mark(1), Zero.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Negate()", test.Receiver), func(t *testing.T) { got := test.Receiver.Negate() if !got.RawEquals(test.Expected) { t.Fatalf("Negate returned %#v; want %#v", got, test.Expected) } }) } } func TestValueMultiply(t *testing.T) { tests := []struct { LHS Value RHS Value Expected Value }{ { NumberIntVal(4), NumberIntVal(2), NumberIntVal(8), }, { NumberIntVal(1), NumberIntVal(-2), NumberIntVal(-2), }, { NumberIntVal(5), NumberFloatVal(0.5), NumberFloatVal(2.5), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Number), }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Number), }, { NumberIntVal(1), DynamicVal, UnknownVal(Number), }, { DynamicVal, DynamicVal, UnknownVal(Number), }, { Zero.Mark(1), Zero, Zero.Mark(1), }, { Zero, Zero.Mark(2), Zero.Mark(2), }, { Zero.Mark(1), Zero.Mark(2), Zero.WithMarks(NewValueMarks(1, 2)), }, { MustParseNumberVal("967323432120515089486873574508975134568969931547"), NumberFloatVal(12345), MustParseNumberVal("11941607769527758779715454277313298036253933804947715"), }, { NumberFloatVal(22337203685475.5), NumberFloatVal(22337203685475.5), MustParseNumberVal("498950668486420259929661100.2"), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Multiply(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Multiply(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("Multiply returned %#v; want %#v", got, test.Expected) } }) } } func TestValueDivide(t *testing.T) { tests := []struct { LHS Value RHS Value Expected Value }{ { NumberIntVal(10), NumberIntVal(2), NumberIntVal(5), }, { NumberIntVal(1), NumberIntVal(-2), NumberFloatVal(-0.5), }, { NumberIntVal(5), NumberFloatVal(0.5), NumberIntVal(10), }, { NumberIntVal(5), NumberIntVal(0), PositiveInfinity, }, { NumberIntVal(-5), NumberIntVal(0), NegativeInfinity, }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Number), }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Number), }, { NumberIntVal(1), DynamicVal, UnknownVal(Number), }, { DynamicVal, DynamicVal, UnknownVal(Number), }, { Zero.Mark(1), NumberIntVal(1), Zero.Mark(1), }, { Zero, NumberIntVal(1).Mark(2), Zero.Mark(2), }, { Zero.Mark(1), NumberIntVal(1).Mark(2), Zero.WithMarks(NewValueMarks(1, 2)), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Divide(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Divide(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("Divide returned %#v; want %#v", got, test.Expected) } }) } } func TestValueModulo(t *testing.T) { tests := []struct { LHS Value RHS Value Expected Value }{ { NumberIntVal(10), NumberIntVal(2), NumberIntVal(0), }, { NumberIntVal(-10), NumberIntVal(2), NumberIntVal(0), }, { NumberIntVal(11), NumberIntVal(2), NumberIntVal(1), }, { NumberIntVal(-11), NumberIntVal(2), NumberIntVal(-1), }, { NumberIntVal(1), NumberIntVal(-2), NumberFloatVal(1), }, { NumberIntVal(5), NumberFloatVal(0.5), NumberIntVal(0), }, { NumberIntVal(5), NumberFloatVal(1.5), NumberFloatVal(0.5), }, { NumberIntVal(5), NumberIntVal(0), NumberIntVal(5), }, { NumberIntVal(-5), NumberIntVal(0), NumberIntVal(-5), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Number), }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Number), }, { NumberIntVal(1), DynamicVal, UnknownVal(Number), }, { DynamicVal, DynamicVal, UnknownVal(Number), }, { NumberIntVal(10).Mark(1), NumberIntVal(10), Zero.Mark(1), }, { NumberIntVal(10), NumberIntVal(10).Mark(2), Zero.Mark(2), }, { NumberIntVal(10).Mark(1), NumberIntVal(10).Mark(2), Zero.WithMarks(NewValueMarks(1, 2)), }, { MustParseNumberVal("967323432120515089486873574508975134568969931547"), NumberIntVal(10), NumberIntVal(7), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Modulo(%#v)", test.LHS, test.RHS), func(t *testing.T) { got := test.LHS.Modulo(test.RHS) if !got.RawEquals(test.Expected) { t.Fatalf("Modulo returned %#v; want %#v", got, test.Expected) } }) } } func TestValueAbsolute(t *testing.T) { tests := []struct { Receiver Value Expected Value }{ { NumberIntVal(1), NumberIntVal(1), }, { NumberIntVal(-1), NumberIntVal(1), }, { NumberFloatVal(0.5), NumberFloatVal(0.5), }, { NumberFloatVal(-0.5), NumberFloatVal(0.5), }, { PositiveInfinity, PositiveInfinity, }, { NegativeInfinity, PositiveInfinity, }, { UnknownVal(Number), UnknownVal(Number), }, { DynamicVal, UnknownVal(Number), }, { NumberIntVal(-1).Mark(1), NumberIntVal(1).Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Absolute()", test.Receiver), func(t *testing.T) { got := test.Receiver.Absolute() if !got.RawEquals(test.Expected) { t.Fatalf("Absolute returned %#v; want %#v", got, test.Expected) } }) } } func TestValueGetAttr(t *testing.T) { tests := []struct { Object Value AttrName string Expected Value }{ { ObjectVal(map[string]Value{ "greeting": StringVal("hello"), }), "greeting", StringVal("hello"), }, { ObjectVal(map[string]Value{ "greeting": StringVal("hello"), }), "greeting", StringVal("hello"), }, { UnknownVal(Object(map[string]Type{ "gr\u00e9eting": String, // precombined é })), "gre\u0301eting", // e with combining acute accent UnknownVal(String), }, { DynamicVal, "hello", DynamicVal, }, { ObjectVal(map[string]Value{ "greeting": StringVal("hello"), }).Mark(1), "greeting", StringVal("hello").Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.GetAttr(%q)", test.Object, test.AttrName), func(t *testing.T) { got := test.Object.GetAttr(test.AttrName) if !got.RawEquals(test.Expected) { t.Fatalf("GetAttr returned %#v; want %#v", got, test.Expected) } }) } } func TestValueIndex(t *testing.T) { tests := []struct { Collection Value Key Value Expected Value }{ { ListVal([]Value{StringVal("hello")}), NumberIntVal(0), StringVal("hello"), }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(1), StringVal("world"), }, { ListVal([]Value{StringVal("hello")}), UnknownVal(Number), UnknownVal(String), }, { ListVal([]Value{StringVal("hello")}), DynamicVal, UnknownVal(String), }, { UnknownVal(List(String)), NumberIntVal(0), UnknownVal(String), }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), StringVal("greeting"), StringVal("hello"), }, { MapVal(map[string]Value{"gr\u00e9eting": StringVal("hello")}), // precombined é StringVal("gre\u0301eting"), // e with combining acute accent StringVal("hello"), }, { MapVal(map[string]Value{"greeting": True}), UnknownVal(String), UnknownVal(Bool), }, { MapVal(map[string]Value{"greeting": True}), DynamicVal, UnknownVal(Bool), }, { UnknownVal(Map(String)), StringVal("greeting"), UnknownVal(String), }, { DynamicVal, StringVal("hello"), DynamicVal, }, { DynamicVal, NumberIntVal(0), DynamicVal, }, { TupleVal([]Value{StringVal("hello")}), NumberIntVal(0), StringVal("hello"), }, { TupleVal([]Value{StringVal("hello"), NumberIntVal(5)}), NumberIntVal(0), StringVal("hello"), }, { TupleVal([]Value{StringVal("hello"), NumberIntVal(5)}), NumberIntVal(1), NumberIntVal(5), }, { TupleVal([]Value{StringVal("hello"), DynamicVal}), NumberIntVal(0), StringVal("hello"), }, { TupleVal([]Value{StringVal("hello"), DynamicVal}), NumberIntVal(1), DynamicVal, }, { TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}), NumberIntVal(0), StringVal("hello"), }, { TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}), NumberIntVal(1), UnknownVal(Number), }, { TupleVal([]Value{StringVal("hello"), UnknownVal(Number)}), UnknownVal(Number), DynamicVal, }, { UnknownVal(Tuple([]Type{String})), NumberIntVal(0), UnknownVal(String), }, { ListVal([]Value{StringVal("hello")}).Mark(1), NumberIntVal(0), StringVal("hello").Mark(1), }, { ListVal([]Value{StringVal("hello")}), NumberIntVal(0).Mark(1), StringVal("hello").Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Index(%q)", test.Collection, test.Key), func(t *testing.T) { got := test.Collection.Index(test.Key) if !got.RawEquals(test.Expected) { t.Fatalf("Index returned %#v; want %#v", got, test.Expected) } }) } } func TestValueHasIndex(t *testing.T) { tests := []struct { Collection Value Key Value Expected Value }{ { ListVal([]Value{StringVal("hello")}), NumberIntVal(0), True, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(1), True, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(2), False, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(-1), False, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), NumberFloatVal(0.5), False, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), StringVal("greeting"), False, }, { ListVal([]Value{StringVal("hello"), StringVal("world")}), True, False, }, { ListVal([]Value{StringVal("hello")}), UnknownVal(Number), UnknownVal(Bool), }, { ListVal([]Value{StringVal("hello")}), DynamicVal, UnknownVal(Bool), }, { UnknownVal(List(String)), NumberIntVal(0), UnknownVal(Bool), }, { UnknownVal(List(String)), StringVal("hello"), False, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), StringVal("greeting"), True, }, { MapVal(map[string]Value{"gre\u0301eting": StringVal("hello")}), // e with combining acute accent StringVal("gr\u00e9eting"), // precombined é True, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), StringVal("grouting"), False, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), StringVal(""), False, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), Zero, False, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), True, False, }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), UnknownVal(String), UnknownVal(Bool), }, { MapVal(map[string]Value{"greeting": StringVal("hello")}), DynamicVal, UnknownVal(Bool), }, { UnknownVal(Map(String)), StringVal("hello"), UnknownVal(Bool), }, { UnknownVal(Map(String)), NumberIntVal(0), False, }, { TupleVal([]Value{StringVal("hello")}), NumberIntVal(0), True, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(1), True, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(2), False, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), NumberIntVal(-1), False, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), NumberFloatVal(0.5), False, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), StringVal("greeting"), False, }, { TupleVal([]Value{StringVal("hello"), StringVal("world")}), True, False, }, { TupleVal([]Value{StringVal("hello")}), UnknownVal(Number), UnknownVal(Bool), }, { UnknownVal(Tuple([]Type{String})), NumberIntVal(0), True, }, { TupleVal([]Value{StringVal("hello")}), DynamicVal, UnknownVal(Bool), }, { DynamicVal, StringVal("hello"), UnknownVal(Bool), }, { DynamicVal, NumberIntVal(0), UnknownVal(Bool), }, { ListVal([]Value{StringVal("hello")}).Mark(1), NumberIntVal(0), True.Mark(1), }, { ListVal([]Value{StringVal("hello")}), NumberIntVal(0).Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.HasIndex(%q)", test.Collection, test.Key), func(t *testing.T) { got := test.Collection.HasIndex(test.Key) if !got.RawEquals(test.Expected) { t.Fatalf("HasIndex returned %#v; want %#v", got, test.Expected) } }) } } func TestValueForEachElement(t *testing.T) { type call struct { Key Value Element Value } tests := []struct { Receiver Value Expected []call Stopped bool }{ { ListValEmpty(String), []call{}, false, }, { ListVal([]Value{ NumberIntVal(1), NumberIntVal(2), }), []call{ {NumberIntVal(0), NumberIntVal(1)}, {NumberIntVal(1), NumberIntVal(2)}, }, false, }, { ListVal([]Value{ StringVal("hey"), StringVal("stop"), StringVal("hey"), }), []call{ {NumberIntVal(0), StringVal("hey")}, {NumberIntVal(1), StringVal("stop")}, }, true, }, { SetValEmpty(String), []call{}, false, }, { SetVal([]Value{ NumberIntVal(1), NumberIntVal(10), NumberIntVal(2), }), []call{ // Numbers in sets are always iterated in numerical order. {NumberIntVal(1), NumberIntVal(1)}, {NumberIntVal(2), NumberIntVal(2)}, {NumberIntVal(10), NumberIntVal(10)}, }, false, }, { SetVal([]Value{ StringVal("hi"), StringVal("stop"), StringVal("zzz"), }), []call{ // Strings in sets are always iterated in lexicographical order. {StringVal("hi"), StringVal("hi")}, {StringVal("stop"), StringVal("stop")}, }, true, }, { MapVal(map[string]Value{ "second": NumberIntVal(2), "first": NumberIntVal(1), }), []call{ {StringVal("first"), NumberIntVal(1)}, {StringVal("second"), NumberIntVal(2)}, }, false, }, { MapVal(map[string]Value{ "item2": StringVal("value2"), "item1": StringVal("stop"), "item0": StringVal("value0"), }), []call{ {StringVal("item0"), StringVal("value0")}, {StringVal("item1"), StringVal("stop")}, }, true, }, { EmptyTupleVal, []call{}, false, }, { TupleVal([]Value{ StringVal("hello"), NumberIntVal(2), }), []call{ {NumberIntVal(0), StringVal("hello")}, {NumberIntVal(1), NumberIntVal(2)}, }, false, }, { TupleVal([]Value{ NumberIntVal(5), StringVal("stop"), True, }), []call{ {NumberIntVal(0), NumberIntVal(5)}, {NumberIntVal(1), StringVal("stop")}, }, true, }, { EmptyObjectVal, []call{}, false, }, { ObjectVal(map[string]Value{ "bool": True, "string": StringVal("hello"), }), []call{ {StringVal("bool"), True}, {StringVal("string"), StringVal("hello")}, }, false, }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.ForEachElement()", test.Receiver), func(t *testing.T) { calls := make([]call, 0) stopped := test.Receiver.ForEachElement( func(key Value, elem Value) (stop bool) { calls = append(calls, call{ Key: key, Element: elem, }) if elem.v == "stop" { stop = true } return }, ) if !reflect.DeepEqual(calls, test.Expected) { t.Errorf( "wrong calls from ForEachElement\ngot: %#v\nwant: %#v", calls, test.Expected, ) } if stopped != test.Stopped { t.Errorf( "ForEachElement returned %#v; want %#v", stopped, test.Stopped, ) } }) } } func TestValueNot(t *testing.T) { tests := []struct { Receiver Value Expected Value }{ { True, False, }, { False, True, }, { UnknownVal(Bool), UnknownVal(Bool), }, { DynamicVal, UnknownVal(Bool), }, { True.Mark(1), False.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Not()", test.Receiver), func(t *testing.T) { got := test.Receiver.Not() if !got.RawEquals(test.Expected) { t.Fatalf("Not returned %#v; want %#v", got, test.Expected) } }) } } func TestValueAnd(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { False, False, False, }, { False, True, False, }, { True, False, False, }, { True, True, True, }, { UnknownVal(Bool), UnknownVal(Bool), UnknownVal(Bool), }, { True, UnknownVal(Bool), UnknownVal(Bool), }, { UnknownVal(Bool), True, UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { True, DynamicVal, UnknownVal(Bool), }, { DynamicVal, True, UnknownVal(Bool), }, { True.Mark(1), True, True.Mark(1), }, { True, True.Mark(1), True.Mark(1), }, { True.Mark(1), True.Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.And(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.And(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("And returned %#v; want %#v", got, test.Expected) } }) } } func TestValueOr(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { False, False, False, }, { False, True, True, }, { True, False, True, }, { True, True, True, }, { UnknownVal(Bool), UnknownVal(Bool), UnknownVal(Bool), }, { True, UnknownVal(Bool), UnknownVal(Bool), }, { UnknownVal(Bool), True, UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { True, DynamicVal, UnknownVal(Bool), }, { DynamicVal, True, UnknownVal(Bool), }, { True.Mark(1), False, True.Mark(1), }, { True, False.Mark(1), True.Mark(1), }, { True.Mark(1), False.Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.Or(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.Or(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("Or returned %#v; want %#v", got, test.Expected) } }) } } func TestLessThan(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { NumberIntVal(0), NumberIntVal(1), True, }, { NumberIntVal(1), NumberIntVal(0), False, }, { NumberIntVal(0), NumberIntVal(0), False, }, { NumberFloatVal(0.1), NumberFloatVal(0.2), True, }, { NumberFloatVal(0.2), NumberFloatVal(0.1), False, }, { NumberIntVal(0), NumberFloatVal(0.2), True, }, { NumberFloatVal(0.2), NumberIntVal(0), False, }, { NumberFloatVal(0.2), NumberFloatVal(0.2), False, }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Bool), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Bool), }, { UnknownVal(Number), NumberIntVal(1), UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { NumberIntVal(1), DynamicVal, UnknownVal(Bool), }, { DynamicVal, NumberIntVal(1), UnknownVal(Bool), }, { NumberIntVal(0).Mark(1), NumberIntVal(1), True.Mark(1), }, { NumberIntVal(0), NumberIntVal(1).Mark(1), True.Mark(1), }, { NumberIntVal(0).Mark(1), NumberIntVal(1).Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.LessThan(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.LessThan(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("LessThan returned %#v; want %#v", got, test.Expected) } }) } } func TestGreaterThan(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { NumberIntVal(0), NumberIntVal(1), False, }, { NumberIntVal(1), NumberIntVal(0), True, }, { NumberIntVal(0), NumberIntVal(0), False, }, { NumberFloatVal(0.1), NumberFloatVal(0.2), False, }, { NumberFloatVal(0.2), NumberFloatVal(0.1), True, }, { NumberIntVal(0), NumberFloatVal(0.2), False, }, { NumberFloatVal(0.2), NumberIntVal(0), True, }, { NumberFloatVal(0.2), NumberFloatVal(0.2), False, }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Bool), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Bool), }, { UnknownVal(Number), NumberIntVal(1), UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { NumberIntVal(1), DynamicVal, UnknownVal(Bool), }, { DynamicVal, NumberIntVal(1), UnknownVal(Bool), }, { NumberIntVal(1).Mark(1), NumberIntVal(0), True.Mark(1), }, { NumberIntVal(1), NumberIntVal(0).Mark(1), True.Mark(1), }, { NumberIntVal(1).Mark(1), NumberIntVal(0).Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.GreaterThan(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.GreaterThan(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("GreaterThan returned %#v; want %#v", got, test.Expected) } }) } } func TestLessThanOrEqualTo(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { NumberIntVal(0), NumberIntVal(1), True, }, { NumberIntVal(1), NumberIntVal(0), False, }, { NumberIntVal(0), NumberIntVal(0), True, }, { NumberFloatVal(0.1), NumberFloatVal(0.2), True, }, { NumberFloatVal(0.2), NumberFloatVal(0.1), False, }, { NumberIntVal(0), NumberFloatVal(0.2), True, }, { NumberFloatVal(0.2), NumberIntVal(0), False, }, { NumberFloatVal(0.2), NumberFloatVal(0.2), True, }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Bool), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Bool), }, { UnknownVal(Number), NumberIntVal(1), UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { NumberIntVal(1), DynamicVal, UnknownVal(Bool), }, { DynamicVal, NumberIntVal(1), UnknownVal(Bool), }, { NumberIntVal(0).Mark(1), NumberIntVal(1), True.Mark(1), }, { NumberIntVal(0), NumberIntVal(1).Mark(1), True.Mark(1), }, { NumberIntVal(0).Mark(1), NumberIntVal(1).Mark(1), True.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.LessThanOrEqualTo(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.LessThanOrEqualTo(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("LessThanOrEqualTo returned %#v; want %#v", got, test.Expected) } }) } } func TestGreaterThanOrEqualTo(t *testing.T) { tests := []struct { Receiver Value Other Value Expected Value }{ { NumberIntVal(0), NumberIntVal(1), False, }, { NumberIntVal(1), NumberIntVal(0), True, }, { NumberIntVal(0), NumberIntVal(0), True, }, { NumberFloatVal(0.1), NumberFloatVal(0.2), False, }, { NumberFloatVal(0.2), NumberFloatVal(0.1), True, }, { NumberIntVal(0), NumberFloatVal(0.2), False, }, { NumberFloatVal(0.2), NumberIntVal(0), True, }, { NumberFloatVal(0.2), NumberFloatVal(0.2), True, }, { UnknownVal(Number), UnknownVal(Number), UnknownVal(Bool), }, { NumberIntVal(1), UnknownVal(Number), UnknownVal(Bool), }, { UnknownVal(Number), NumberIntVal(1), UnknownVal(Bool), }, { DynamicVal, DynamicVal, UnknownVal(Bool), }, { NumberIntVal(1), DynamicVal, UnknownVal(Bool), }, { DynamicVal, NumberIntVal(1), UnknownVal(Bool), }, { NumberIntVal(0).Mark(1), NumberIntVal(1), False.Mark(1), }, { NumberIntVal(0), NumberIntVal(1).Mark(1), False.Mark(1), }, { NumberIntVal(0).Mark(1), NumberIntVal(1).Mark(1), False.Mark(1), }, } for _, test := range tests { t.Run(fmt.Sprintf("%#v.GreaterThanOrEqualTo(%#v)", test.Receiver, test.Other), func(t *testing.T) { got := test.Receiver.GreaterThanOrEqualTo(test.Other) if !got.RawEquals(test.Expected) { t.Fatalf("GreaterThanOrEqualTo returned %#v; want %#v", got, test.Expected) } }) } } func TestValueGoString(t *testing.T) { tests := []struct { Value Value Want string }{ { NullVal(DynamicPseudoType), `cty.NullVal(cty.DynamicPseudoType)`, }, { NullVal(String), `cty.NullVal(cty.String)`, }, { NullVal(Tuple([]Type{String, Bool})), `cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.Bool}))`, }, { UnknownVal(DynamicPseudoType), `cty.DynamicVal`, }, { UnknownVal(String), `cty.UnknownVal(cty.String)`, }, { UnknownVal(Tuple([]Type{String, Bool})), `cty.UnknownVal(cty.Tuple([]cty.Type{cty.String, cty.Bool}))`, }, { StringVal(""), `cty.StringVal("")`, }, { StringVal("hello"), `cty.StringVal("hello")`, }, { Zero, `cty.NumberIntVal(0)`, }, { NumberFloatVal(1.2), `cty.NumberFloatVal(1.2)`, }, { NumberFloatVal(1.0), `cty.NumberIntVal(1)`, // the "float-ness" of the input is lost because its value is a whole number }, { MustParseNumberVal("3.14159265358979323846264338327950288419716939937510582097494459"), `cty.MustParseNumberVal("3.14159265358979323846264338327950288419716939937510582097494459")`, }, { True, `cty.True`, }, { False, `cty.False`, }, { ListValEmpty(String), `cty.ListValEmpty(cty.String)`, }, { ListValEmpty(List(String)), `cty.ListValEmpty(cty.List(cty.String))`, }, { ListVal([]Value{True}), `cty.ListVal([]cty.Value{cty.True})`, }, { SetValEmpty(String), `cty.SetValEmpty(cty.String)`, }, { SetValEmpty(Map(String)), `cty.SetValEmpty(cty.Map(cty.String))`, }, { SetVal([]Value{True}), `cty.SetVal([]cty.Value{cty.True})`, }, { EmptyTupleVal, `cty.EmptyTupleVal`, }, { TupleVal(nil), `cty.EmptyTupleVal`, }, { TupleVal([]Value{True}), `cty.TupleVal([]cty.Value{cty.True})`, }, { MapValEmpty(String), `cty.MapValEmpty(cty.String)`, }, { MapValEmpty(Set(String)), `cty.MapValEmpty(cty.Set(cty.String))`, }, { MapVal(map[string]Value{"boop": True}), `cty.MapVal(map[string]cty.Value{"boop":cty.True})`, }, { EmptyObjectVal, `cty.EmptyObjectVal`, }, { ObjectVal(nil), `cty.EmptyObjectVal`, }, { ObjectVal(map[string]Value{"foo": True}), `cty.ObjectVal(map[string]cty.Value{"foo":cty.True})`, }, } for _, test := range tests { t.Run(test.Value.GoString(), func(t *testing.T) { got := test.Value.GoString() want := test.Want if got != want { t.Errorf("wrong result\ngot: %s\nwant: %s", got, want) } }) } } func TestHasWhollyKnownType(t *testing.T) { tests := []struct { Value Value Want bool }{ { Value: DynamicVal, Want: false, }, { Value: ObjectVal(map[string]Value{ "dyn": DynamicVal, }), Want: false, }, { Value: NullVal(Object(map[string]Type{ "dyn": DynamicPseudoType, })), Want: true, }, { Value: TupleVal([]Value{ StringVal("a"), NullVal(DynamicPseudoType), }), Want: true, }, { Value: ListVal([]Value{ ObjectVal(map[string]Value{ "null": NullVal(DynamicPseudoType), }), }), Want: true, }, { Value: ListVal([]Value{ NullVal(Object(map[string]Type{ "dyn": DynamicPseudoType, })), }), Want: true, }, { Value: ObjectVal(map[string]Value{ "tuple": TupleVal([]Value{ StringVal("a"), NullVal(DynamicPseudoType), }), }), Want: true, }, { Value: ObjectVal(map[string]Value{ "tuple": TupleVal([]Value{ ObjectVal(map[string]Value{ "dyn": DynamicVal, }), }), }), Want: false, }, } for _, test := range tests { t.Run(test.Value.GoString(), func(t *testing.T) { got := test.Value.HasWhollyKnownType() want := test.Want if got != want { t.Errorf("wrong result\ngot: %v\nwant: %v", got, want) } }) } } func TestFloatCopy(t *testing.T) { // ensure manipulating floats does not modify the cty.Value v := NumberFloatVal(1.9) vString := v.GoString() // do something that will modify the internal big.Float mantissa v.AsBigFloat().SetInt64(1) if vString != v.GoString() { t.Fatalf("original value changed from %s to %#v", vString, v) } } go-cty-1.12.1/cty/walk.go000066400000000000000000000157401433256746400151070ustar00rootroot00000000000000package cty // Walk visits all of the values in a possibly-complex structure, calling // a given function for each value. // // For example, given a list of strings the callback would first be called // with the whole list and then called once for each element of the list. // // The callback function may prevent recursive visits to child values by // returning false. The callback function my halt the walk altogether by // returning a non-nil error. If the returned error is about the element // currently being visited, it is recommended to use the provided path // value to produce a PathError describing that context. // // The path passed to the given function may not be used after that function // returns, since its backing array is re-used for other calls. func Walk(val Value, cb func(Path, Value) (bool, error)) error { var path Path return walk(path, val, cb) } func walk(path Path, val Value, cb func(Path, Value) (bool, error)) error { deeper, err := cb(path, val) if err != nil { return err } if !deeper { return nil } if val.IsNull() || !val.IsKnown() { // Can't recurse into null or unknown values, regardless of type return nil } // The callback already got a chance to see the mark in our // call above, so can safely strip it off here in order to // visit the child elements, which might still have their own marks. rawVal, _ := val.Unmark() ty := val.Type() switch { case ty.IsObjectType(): for it := rawVal.ElementIterator(); it.Next(); { nameVal, av := it.Element() path := append(path, GetAttrStep{ Name: nameVal.AsString(), }) err := walk(path, av, cb) if err != nil { return err } } case rawVal.CanIterateElements(): for it := rawVal.ElementIterator(); it.Next(); { kv, ev := it.Element() path := append(path, IndexStep{ Key: kv, }) err := walk(path, ev, cb) if err != nil { return err } } } return nil } // Transformer is the interface used to optionally transform values in a // possibly-complex structure. The Enter method is called before traversing // through a given path, and the Exit method is called when traversal of a // path is complete. // // Use Enter when you want to transform a complex value before traversal // (preorder), and Exit when you want to transform a value after traversal // (postorder). // // The path passed to the given function may not be used after that function // returns, since its backing array is re-used for other calls. type Transformer interface { Enter(Path, Value) (Value, error) Exit(Path, Value) (Value, error) } type postorderTransformer struct { callback func(Path, Value) (Value, error) } func (t *postorderTransformer) Enter(p Path, v Value) (Value, error) { return v, nil } func (t *postorderTransformer) Exit(p Path, v Value) (Value, error) { return t.callback(p, v) } // Transform visits all of the values in a possibly-complex structure, // calling a given function for each value which has an opportunity to // replace that value. // // Unlike Walk, Transform visits child nodes first, so for a list of strings // it would first visit the strings and then the _new_ list constructed // from the transformed values of the list items. // // This is useful for creating the effect of being able to make deep mutations // to a value even though values are immutable. However, it's the responsibility // of the given function to preserve expected invariants, such as homogenity of // element types in collections; this function can panic if such invariants // are violated, just as if new values were constructed directly using the // value constructor functions. An easy way to preserve invariants is to // ensure that the transform function never changes the value type. // // The callback function may halt the walk altogether by // returning a non-nil error. If the returned error is about the element // currently being visited, it is recommended to use the provided path // value to produce a PathError describing that context. // // The path passed to the given function may not be used after that function // returns, since its backing array is re-used for other calls. func Transform(val Value, cb func(Path, Value) (Value, error)) (Value, error) { var path Path return transform(path, val, &postorderTransformer{cb}) } // TransformWithTransformer allows the caller to more closely control the // traversal used for transformation. See the documentation for Transformer for // more details. func TransformWithTransformer(val Value, t Transformer) (Value, error) { var path Path return transform(path, val, t) } func transform(path Path, val Value, t Transformer) (Value, error) { val, err := t.Enter(path, val) if err != nil { return DynamicVal, err } ty := val.Type() var newVal Value // We need to peel off any marks here so that we can dig around // inside any collection values. We'll reapply these to any // new collections we construct, but the transformer's Exit // method gets the final say on what to do with those. rawVal, marks := val.Unmark() switch { case val.IsNull() || !val.IsKnown(): // Can't recurse into null or unknown values, regardless of type newVal = val case ty.IsListType() || ty.IsSetType() || ty.IsTupleType(): l := rawVal.LengthInt() switch l { case 0: // No deep transform for an empty sequence newVal = val default: elems := make([]Value, 0, l) for it := rawVal.ElementIterator(); it.Next(); { kv, ev := it.Element() path := append(path, IndexStep{ Key: kv, }) newEv, err := transform(path, ev, t) if err != nil { return DynamicVal, err } elems = append(elems, newEv) } switch { case ty.IsListType(): newVal = ListVal(elems).WithMarks(marks) case ty.IsSetType(): newVal = SetVal(elems).WithMarks(marks) case ty.IsTupleType(): newVal = TupleVal(elems).WithMarks(marks) default: panic("unknown sequence type") // should never happen because of the case we are in } } case ty.IsMapType(): l := rawVal.LengthInt() switch l { case 0: // No deep transform for an empty map newVal = val default: elems := make(map[string]Value) for it := rawVal.ElementIterator(); it.Next(); { kv, ev := it.Element() path := append(path, IndexStep{ Key: kv, }) newEv, err := transform(path, ev, t) if err != nil { return DynamicVal, err } elems[kv.AsString()] = newEv } newVal = MapVal(elems).WithMarks(marks) } case ty.IsObjectType(): switch { case ty.Equals(EmptyObject): // No deep transform for an empty object newVal = val default: atys := ty.AttributeTypes() newAVs := make(map[string]Value) for name := range atys { av := val.GetAttr(name) path := append(path, GetAttrStep{ Name: name, }) newAV, err := transform(path, av, t) if err != nil { return DynamicVal, err } newAVs[name] = newAV } newVal = ObjectVal(newAVs).WithMarks(marks) } default: newVal = val } newVal, err = t.Exit(path, newVal) if err != nil { return DynamicVal, err } return newVal, err } go-cty-1.12.1/cty/walk_test.go000066400000000000000000000242441433256746400161450ustar00rootroot00000000000000package cty import ( "fmt" "testing" ) func TestWalk(t *testing.T) { type Call struct { Path string Type string } val := ObjectVal(map[string]Value{ "string": StringVal("hello"), "number": NumberIntVal(10), "bool": True, "list": ListVal([]Value{True}), "list_empty": ListValEmpty(Bool), "set": SetVal([]Value{True}), "set_empty": ListValEmpty(Bool), "tuple": TupleVal([]Value{True}), "tuple_empty": EmptyTupleVal, "map": MapVal(map[string]Value{"true": True}), "map_empty": MapValEmpty(Bool), "object": ObjectVal(map[string]Value{"true": True}), "object_empty": EmptyObjectVal, "null": NullVal(List(String)), "unknown": UnknownVal(Map(Bool)), "marked_string": StringVal("boop").Mark("blorp"), "marked_list": ListVal([]Value{True}).Mark("blorp"), "marked_tuple": TupleVal([]Value{True}).Mark("blorp"), "marked_set": SetVal([]Value{True}).Mark("blorp"), "marked_object": ObjectVal(map[string]Value{"true": True}).Mark("blorp"), "marked_map": MapVal(map[string]Value{"true": True}), }) gotCalls := map[Call]struct{}{} wantCalls := []Call{ {`cty.Path(nil)`, "object"}, {`cty.Path{cty.GetAttrStep{Name:"string"}}`, "string"}, {`cty.Path{cty.GetAttrStep{Name:"number"}}`, "number"}, {`cty.Path{cty.GetAttrStep{Name:"bool"}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"list"}}`, "list of bool"}, {`cty.Path{cty.GetAttrStep{Name:"list"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"list_empty"}}`, "list of bool"}, {`cty.Path{cty.GetAttrStep{Name:"set"}}`, "set of bool"}, {`cty.Path{cty.GetAttrStep{Name:"set"}, cty.IndexStep{Key:cty.True}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"set_empty"}}`, "list of bool"}, {`cty.Path{cty.GetAttrStep{Name:"tuple"}}`, "tuple"}, {`cty.Path{cty.GetAttrStep{Name:"tuple"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"tuple_empty"}}`, "tuple"}, {`cty.Path{cty.GetAttrStep{Name:"map"}, cty.IndexStep{Key:cty.StringVal("true")}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"map"}}`, "map of bool"}, {`cty.Path{cty.GetAttrStep{Name:"map_empty"}}`, "map of bool"}, {`cty.Path{cty.GetAttrStep{Name:"object"}}`, "object"}, {`cty.Path{cty.GetAttrStep{Name:"object"}, cty.GetAttrStep{Name:"true"}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"object_empty"}}`, "object"}, {`cty.Path{cty.GetAttrStep{Name:"null"}}`, "list of string"}, {`cty.Path{cty.GetAttrStep{Name:"unknown"}}`, "map of bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_string"}}`, "string"}, {`cty.Path{cty.GetAttrStep{Name:"marked_list"}}`, "list of bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_list"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_set"}}`, "set of bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_set"}, cty.IndexStep{Key:cty.True}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_object"}}`, "object"}, {`cty.Path{cty.GetAttrStep{Name:"marked_object"}, cty.GetAttrStep{Name:"true"}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_tuple"}}`, "tuple"}, {`cty.Path{cty.GetAttrStep{Name:"marked_tuple"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`, "bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_map"}}`, "map of bool"}, {`cty.Path{cty.GetAttrStep{Name:"marked_map"}, cty.IndexStep{Key:cty.StringVal("true")}}`, "bool"}, } err := Walk(val, func(path Path, val Value) (bool, error) { gotCalls[Call{ Path: fmt.Sprintf("%#v", path), Type: val.Type().FriendlyName(), }] = struct{}{} return true, nil }) if err != nil { t.Fatal(err) } if len(gotCalls) != len(wantCalls) { t.Errorf("wrong number of calls %d; want %d", len(gotCalls), len(wantCalls)) } for gotCall := range gotCalls { t.Logf("got call {%#q, %q}", gotCall.Path, gotCall.Type) } for _, wantCall := range wantCalls { if _, has := gotCalls[wantCall]; !has { t.Errorf("missing call {%#q, %q}", wantCall.Path, wantCall.Type) } } } type pathTransformer struct{} func (pathTransformer) Enter(p Path, v Value) (Value, error) { return v, nil } func (pathTransformer) Exit(p Path, v Value) (Value, error) { if v.Type().IsPrimitiveType() { return StringVal(fmt.Sprintf("%#v", p)), nil } return v, nil } func TestTransformWithTransformer(t *testing.T) { val := ObjectVal(map[string]Value{ "string": StringVal("hello"), "number": NumberIntVal(10), "bool": True, "list": ListVal([]Value{True}), "list_empty": ListValEmpty(Bool), "set": SetVal([]Value{True}), "set_empty": ListValEmpty(Bool), "tuple": TupleVal([]Value{True}), "tuple_empty": EmptyTupleVal, "map": MapVal(map[string]Value{"true": True}), "map_empty": MapValEmpty(Bool), "object": ObjectVal(map[string]Value{"true": True}), "object_empty": EmptyObjectVal, "null": NullVal(String), "unknown": UnknownVal(Bool), "null_list": NullVal(List(String)), "unknown_map": UnknownVal(Map(Bool)), "marked_string": StringVal("hello").Mark("blorp"), "marked_list": ListVal([]Value{True}).Mark("blorp"), "marked_set": SetVal([]Value{True}).Mark("blorp"), "marked_tuple": TupleVal([]Value{True}).Mark("blorp"), "marked_map": MapVal(map[string]Value{"true": True}).Mark("blorp"), "marked_object": ObjectVal(map[string]Value{"true": True}).Mark("blorp"), }) gotVal, err := TransformWithTransformer(val, pathTransformer{}) if err != nil { t.Fatalf("unexpected error: %s", err) } wantVal := ObjectVal(map[string]Value{ "string": StringVal(`cty.Path{cty.GetAttrStep{Name:"string"}}`), "number": StringVal(`cty.Path{cty.GetAttrStep{Name:"number"}}`), "bool": StringVal(`cty.Path{cty.GetAttrStep{Name:"bool"}}`), "list": ListVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"list"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`)}), "list_empty": ListValEmpty(Bool), "set": SetVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"set"}, cty.IndexStep{Key:cty.True}}`)}), "set_empty": ListValEmpty(Bool), "tuple": TupleVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"tuple"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`)}), "tuple_empty": EmptyTupleVal, "map": MapVal(map[string]Value{"true": StringVal(`cty.Path{cty.GetAttrStep{Name:"map"}, cty.IndexStep{Key:cty.StringVal("true")}}`)}), "map_empty": MapValEmpty(Bool), "object": ObjectVal(map[string]Value{"true": StringVal(`cty.Path{cty.GetAttrStep{Name:"object"}, cty.GetAttrStep{Name:"true"}}`)}), "object_empty": EmptyObjectVal, "null": StringVal(`cty.Path{cty.GetAttrStep{Name:"null"}}`), "unknown": StringVal(`cty.Path{cty.GetAttrStep{Name:"unknown"}}`), "null_list": NullVal(List(String)), "unknown_map": UnknownVal(Map(Bool)), "marked_string": StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_string"}}`), "marked_list": ListVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_list"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`)}).Mark("blorp"), "marked_set": SetVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_set"}, cty.IndexStep{Key:cty.True}}`)}).Mark("blorp"), "marked_tuple": TupleVal([]Value{StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_tuple"}, cty.IndexStep{Key:cty.NumberIntVal(0)}}`)}).Mark("blorp"), "marked_map": MapVal(map[string]Value{"true": StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_map"}, cty.IndexStep{Key:cty.StringVal("true")}}`)}).Mark("blorp"), "marked_object": ObjectVal(map[string]Value{"true": StringVal(`cty.Path{cty.GetAttrStep{Name:"marked_object"}, cty.GetAttrStep{Name:"true"}}`)}).Mark("blorp"), }) if !gotVal.RawEquals(wantVal) { t.Errorf("wrong result\ngot: %#v\nwant: %#v", gotVal, wantVal) if got, want := len(gotVal.Type().AttributeTypes()), len(gotVal.Type().AttributeTypes()); got != want { t.Errorf("wrong length %d; want %d", got, want) } for it := wantVal.ElementIterator(); it.Next(); { key, wantElem := it.Element() attr := key.AsString() if !gotVal.Type().HasAttribute(attr) { t.Errorf("missing attribute %q", attr) continue } gotElem := gotVal.GetAttr(attr) if !gotElem.RawEquals(wantElem) { t.Errorf("wrong value for attribute %q\ngot: %#v\nwant: %#v", attr, gotElem, wantElem) } } } } type errorTransformer struct{} func (errorTransformer) Enter(p Path, v Value) (Value, error) { return v, nil } func (errorTransformer) Exit(p Path, v Value) (Value, error) { ty := v.Type() if ty.IsPrimitiveType() { return v, nil } return v, p.NewError(fmt.Errorf("expected primitive type, was %#v", ty)) } func TestTransformWithTransformer_error(t *testing.T) { val := ObjectVal(map[string]Value{ "string": StringVal("hello"), "number": NumberIntVal(10), "bool": True, "list": ListVal([]Value{True}), }) gotVal, err := TransformWithTransformer(val, errorTransformer{}) if gotVal != DynamicVal { t.Fatalf("expected DynamicVal, got %#v", gotVal) } if err == nil { t.Fatal("expected error, got nil") } pathError, ok := err.(PathError) if !ok { t.Fatalf("expected PathError, got %#v", err) } if got, want := pathError.Path, GetAttrPath("list"); !got.Equals(want) { t.Errorf("wrong path\n got: %#v\nwant: %#v", got, want) } } func TestTransform(t *testing.T) { val := ObjectVal(map[string]Value{ "list": ListVal([]Value{True, True, False}), "set": SetVal([]Value{True, False}), "map": MapVal(map[string]Value{"a": True, "b": False}), "object": ObjectVal(map[string]Value{ "a": True, "b": ListVal([]Value{False, False, False}), }), }) wantVal := ObjectVal(map[string]Value{ "list": ListVal([]Value{False, False, True}), "set": SetVal([]Value{True, False}), "map": MapVal(map[string]Value{"a": False, "b": True}), "object": ObjectVal(map[string]Value{ "a": False, "b": ListVal([]Value{True, True, True}), }), }) gotVal, err := Transform(val, func(p Path, v Value) (Value, error) { if v.Type().Equals(Bool) { return v.Not(), nil } return v, nil }) if err != nil { t.Fatalf("unexpected error: %s", err) } if !gotVal.RawEquals(wantVal) { t.Fatalf("wrong value\n got: %#v\nwant: %#v", gotVal, wantVal) } } go-cty-1.12.1/docs/000077500000000000000000000000001433256746400137445ustar00rootroot00000000000000go-cty-1.12.1/docs/capsule-type-operations.md000066400000000000000000000112431433256746400210630ustar00rootroot00000000000000# Capsule Type Operation Definitions As described in [the main introduction to Capsule types](./types.md#capsule-types), by default a capsule type supports no operations apart from comparison by reference identity. However, there are certain operations that calling applications reasonably expect to be able to use generically across values of any type, e.g. as part of decoding arbitrary user input and preparing it for use. To support this, calling applications can optionally implement some additional operations for their capsule types. This is limited only to the subset of operations that, as noted above, are reasonable to expect to have available on values of any type. It does not include type-specialized operations like arithmetic; to perform such operations against capsule types, implement that logic in your calling application instead where you must presumably already be making specialized decisions based on value types. The following operations are implementable: * The `GoString` result for values of the type, so that values can be included in `fmt` operations using `%#v` along with other values and give a more useful result. To stay within `cty`'s conventions, the `GoString` result should generally represent a call to a value constructor function that would produce an equivalent value. * The `GoString` result for the type itself, for similar reasons. * Equality as an operation. It's unnecessary to implement this unless your capsule type represents a container for other `cty` values that would need to be recursively compared for equality. For simple capsule types, just implement raw equality and it will be used for both situations. * Raw equality as an integration method. This is commonly used in tests in order to take into account not only the value itself but its null-ness and unknown-ness. Because capsule types are primarily intended to be simple transports for opaque application values, in simple cases this integration method can just be a wrapper around whatever normal equality operation would apply to the wrapped type. * Conversion to and from the capsule type, using the `convert` package. Some applications use conversion as part of decoding user input in order to coerce user values into an expected type, in which case implementing conversions can make an application's capsule types participate in such coersions as needed. To implement one or more of these operations on a capsule type, construct it with `cty.CapsuleWithOps` instead of just `cty.Capsule`. The operation implementations are provided as function references within a struct value, and so those function references can potentially be closures referring to arguments passed to a type constructor function in order to implement parameterized capsule types. For more information on the available operations and the contract for implementing each one, see the documentation on the fields of `cty.CapsuleOps`. ## Extension Data In addition to the operations that `cty` packages use directly as described above, `CapsuleOps` includes extra function `ExtensionData` which can be used for application-specific extensions. It creates an extensible namespace using arbitrary comparable values of named types, where an application can define a key in its own package namespace and then use it to retrieve values from cooperating capsule types. For example, if there were an application package called `happyapp` which defined an extension key `Frob`, a cooperating capsule type might implement `ExtensionData` like this: ```go ExtensionData: func (key interface{}) interface{} { switch key { case happyapp.Frob: // a value of some type belonging to "happyapp" return "frob indeed" default: return nil } } ``` Package `happyapp` should define this `Frob` symbol as being of a named type belonging to its own package, so that the type can serve as a namespace: ```go type frobType int var Frob frobType ``` `cty` itself doesn't do anything special with `ExtensionData`, but there is a convenience method `CapsuleExtensionData` on `cty.Type` that can be used with capsule types to attempt to access extension data keys without first retrieving the `CapsuleOps` and checking if it implements `ExtensionData`: ```go // (in package happyapp) if frobValue, ok := givenType.CapsuleExtensionData(Frob).(string); ok { // If we get in here then the capsule type correctly handles Frob, // returning a string. If the capsule type does not handle Frob or // if it returns the wrong type then we'll ignore it. log.Printf("This capsule type has a Frob: %s", frobValue) } ``` `CapsuleExtensionData` will panic if called on a non-capsule type. go-cty-1.12.1/docs/concepts.md000066400000000000000000000167311433256746400161140ustar00rootroot00000000000000# `cty` Concepts `cty` is built around two fundamental concepts: _types_ and _values_. A _type_ represents a particular way of storing some data in memory and a set of operations that can be performed on that data. A _value_ is, therefore, the combination of some raw data and a _type_ that describes how that data should be interpreted. The simplest types in `cty` are the _number_, _string_ and _bool_ types, collectively known as the _primitive_ types. Along with the primitive types, `cty` supports _compound_ types, which are types that are constructed by assembling together other types in a particular way. The _compound_ types are further subdivided into two categories: * **Collection Types** represent collections of values that all have the same type (the _element type_) and permit access to those values in different ways. The collection type kinds are _list_, _set_, and _map_. * **Structural Types** represent collections of values that may all have _different_ types, organized either by name or by position in a sequence. The structural type kinds are _object_ and _tuple_. For example, "list of string" is a collection type that represents a collection of string values (_elements_) that are each assigned a sequential index starting at zero, while "map of string" instead assigns each of its elements a name in the form of a string value. The details of the specific types and type kinds are covered in [the full description of the type system](./types.md); the remainder of _this_ document will discuss types and values in general, using specific types only as examples. ## Value Operations Each type defines a set of operations that are valid on its values. For example, the _number_ type permits various arithmetic operations such as addition, subtraction, and multiplication, but these are not permitted for other types such as _bool_. Since `cty` is a dynamic type system (from the perspective of the calling Go program), the validity of an operation on a given value must be checked at runtime. The documentation for each type defines what operations are valid on it and what semantics each operation has. ## Unknown Values and the Dynamic Pseudo-Type `cty` has some additional _optional_ concepts that may be useful in certain applications. An _unknown value_ is a value that carries a type but no value. It can serve as a placeholder for a value to be resolved later, which can be useful when implementing a static type checker for a language. Unknown values are special because they support the same operations as a known value of the same type but the result will itself be an unknown value. For example, the number 5 added to an unknown number yields another unknown number. The dynamic pseudo-type is a special type that serves as a placeholder for a type that isn't yet known. Whereas unknown values represent situations where the type is known and the value is not, the dynamic pseudo-type represents situations where neither is known, or where any value of any type is permitted. It is referred to as a "pseudo-type" because while it can be used in many places where types are permitted, it does not define any operations of its own. These two concepts are related in that the dynamic pseudo-type has no non-null, non-unknown values. It single non-null type is itself an unknown value. _All_ operations are supported on non-null dynamic values, but the result will always be an unknown value, possibly type-unknown itself. Dealing with unknown values and the dynamic pseudo-type can cause additional complexity for a calling application, although many details of it are handled automatically by the `cty` internals. As a consequence, the main `cty` API promises to never produce an unknown value for an operation unless one of the operands is itself unknown, and so applications can opt out of this additional complexity by never providing unknown values as operands. ## Type Equality and Type Conformance Two types are said to be equal if they are exactly equivalent. Each type kind defines its own equality rules, but the overall intent is to implement strict type comparisons. Type _conformance_ is a slightly-weaker concept that allows the dynamic pseudo-type to be used as a placeholder to represent "any type". Therefore a given type is equal only to itself but it is _conformant_ to either itself or the dynamic pseudo-type. Type conformance is not directly used by `cty`'s core, but it is used as a building block for the `function` package and for JSON serialization. ## The `cty` Go API The primary way a application works with `cty` values is via the API exposed by the `cty` go package. The full details of this package are in [its reference documentation](https://godoc.org/github.com/zclconf/go-cty/cty), so this section will just cover the basic usage patterns. The main features of the `cty` package are the Go types `cty.Type` and `cty.Value`, which each represent the concept they are named after. The package contains variables that represent the primitive types, `cty.Number`, `cty.String` and `cty.Bool`. It also contains functions that allow the construction of compound types, such as `cty.List`, `cty.Object`, etc. These functions each take different arguments depending on the kind of compound type in question. Alongside the types and type factories, the package also contains variables and functions for constructing _values_ of these types, which conventionally have names that are the corresponding type or type kind with the suffix `Val`. For example, the two boolean values are exported as `cty.True` and `cty.False`, and string values can be constructed using the function `cty.StringVal`, given a native Go string. The `cty.Type` and `cty.Value` types are similar to the types of the same name in the built-in Go `reflect` package. They expose methods that are the union of all operations supported across all types, but each method has a set of constraints associated with it, and failure to follow these will result in a run-time panic. The `cty.Value` object has two classes of methods: * **Operation Methods** stay within the `cty` type system, dealing entirely with `cty.Value` instances. These methods fully deal with concerns such as unknown values, so the caller just needs to be sure to apply only operations that are valid for the receiving value's type. * **Integration Methods** live on the boundary between `cty` and the native Go type system, and can be used by the calling application to integrate with non-`cty`-aware code. These methods often have constraints such as not supporting unknown values, which are covered in their documentation. While the integration methods alone are sufficient for a calling application to convert to and from `cty` values, the utility package [`gocty`](./gocty.html) provides a more convenient way to convert between Go native values and `cty` values. ## Marks 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. 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. For more information on marks, see [the dedicated section on marks](./marks.md). go-cty-1.12.1/docs/convert.md000066400000000000000000000172441433256746400157560ustar00rootroot00000000000000# Converting between `cty` types [The `convert` package](https://godoc.org/github.com/zclconf/go-cty/cty/convert) provides a standard set of type conversion routines for moving between types in the `cty` type system. _Conversion_ in this context means taking a given value and producing a new value of a different type that, in some sense, contains the same information. For example, the number `5` can be converted to a string as `"5"`. Specific conversion operations are represented by type `Conversion`, which is a function type that takes a single value as input and returns a value or an error. ## "Safe" and "Unsafe" conversions The `convert` package broadly organizes its supported conversions into two types. "Safe" conversions are ones where all values of the source type can be represented in the target type, and thus the conversion is guaranteed to succeed for any value of the source type. "Unsafe" conversions, on the other hand, are able to convert only a subset of values of the source type. Values outside of that subset will cause the conversion function to return an error. Converting from number to string is safe because an unambiguous string representation can be created for any number. The converse is _unsafe_, because while a string like `"2.5"` can be converted to a number, a string like `"bananas"` cannot. The calling application must choose whether to attempt unsafe conversions, depending on whether it is willing to tolerate conversions returning errors even though they ostensibly passed type checking. Operations that have both safe and unsafe modes come in couplets, with the unsafe version's name having the suffix `Unsafe`. ## Getting a Conversion To find out if a conversion is available between two types, an application can call either `GetConversion` or `GetConversionUnsafe`. These functions return a valid `Conversion` if one is available, or `nil` if not. Note that there are no conversions from a type to itself. Callers should check if two types are equal before attempting to obtain a conversion between them. As usual, `cty.DynamicPseudoType` serves as a special-case placeholder. It is used in two ways, depending on whether it appears in the source or the destination type: * When a source type is dynamic, a special unsafe conversion is available that takes any value and passes it through verbatim if it matches the destination type, or returns an error if it does not. This can be used as part of handling dynamic values during a type-checking procedure, with the generated conversion serving as a run-time type check. * When a _destination_ type is dynamic, a simple passthrough conversion is generated that does not transform the source value at all. This is supported so that a destination type can behave similarly to a type description used for a conformance check, thus allowing this package to be used to attempt to _make_ a type conformant, rather than merely check whether it already is. ## Converting a Value A value can be converted by passing it as the argument to any conversion whose source type matches the value's type. If the conversion is an unsafe one, the conversion function may return an error, in which case the returned value is invalid and must not be used. As a convenience, the `Convert` function takes a value and a target type and returns a converted value if a conversion is available. This is equivalent to testing for an unsafe conversion for the value's type and then immediately calling any discovered conversion. An error is returned if a conversion is not available. ## Type Unification A related idea to type _conversion_ is type _unification_. While conversion is concerned with going from a specific source type to a specific target type, unification is instead concerned with finding a single type that several other types can be converted to, without any specific preference as to what the final type is. A good example of this would be to take a set of values provided to initialize a list and choose a single type that all of those values can be converted to, which then decides the element type of the final list. The `Unify` and `UnifyUnsafe` functions are used for type unification. They both take as input a slice of types and then return, if possible, a single target type along with a slice of conversions corresponding to each of the input types. Since many type pairs support type conversions in both directions, the unify functions must apply a preference for which direction to follow given such a pair of types. These functions prefer safe conversions over unsafe ones (assuming that `UnifyUnsafe` was called), and prefer lossless conversions over potentially-lossy ones. Type unification is a potentially-expensive operation, depending on the complexity of the passed types and whether they are mutually conformant. ## Conversion Charts The foundation of the available conversions is the matrix of conversions between the primitive types. String is the most general type, since the other two primitive types have safe conversions to string. The full matrix for primitive types is as follows: | | string | number | boolean | |---------|:------:|:------:|:-------:| | string | n/a | unsafe | unsafe | | number | safe | n/a | none | | boolean | safe | none | n/a | The conversions for compound types are then derived from the above foundation. For example, a list of numbers can convert to a list of strings because a number can convert to a string. The compound type kinds themselves have some available conversions, though: | | tuple | object | list | map | set | |--------|:------:|:------:|:----:|:------:|:----------:| | tuple | n/a | none | safe | none | safe+lossy | | object | none | n/a | none | safe | none | | list | unsafe | none | n/a | none | safe+lossy | | map | none | unsafe | none | n/a | none | | set | unsafe | none | safe | none | n/a | Conversions between compound kinds, as shown above, are possible only if their respective elements/attributes also have conversions available. The conversions from structural types to collection types rely on type unification to identify a single element type for the final collection, and so conversion is possible only if unification is possible. ## Conversion between Object Types There are some special considerations for conversion between distinct object types that do not apply to conversion between types of other kinds. For object types, `convert` implements _structural typing_ behaviors, where the target type is considered to be a description of a set of attributes the final result should have. There are two important additional concerns that result from this design intent: * If the input type has additional attributes that are not mentioned at all in the target type, those additional attributes are silently discarded during conversion, leading to a new object value that has a subset of the attributes of the input value, and whose type therefore conforms to the target type constraint. * If any of the attributes of the target type are marked as optional using the **currently-experimental** `cty.ObjectWithOptionalAttrs` constructor, type conversion will tolerate those attributes being absent in the given type, and the resulting value will include appropriately-typed null value placeholders as the values of those omitted attributes. This behavior is subject to change even in future minor versions of the `cty` module, so that we can try it out with experimental versions of calling applications and adjust the details of the behavior if needed. Hopefully this mechanism will be stabilized in a future release, if those downstream experiments are successful. go-cty-1.12.1/docs/functions.md000066400000000000000000000167261433256746400163120ustar00rootroot00000000000000# `cty` Functions system Core `cty` is primarily concerned with types and values, with behavior delegated to the calling application. However, writing functions that operate on `cty.Value` is expected to be a common enough case for it to be worth factoring out into a shared package, so [the `function` package](https://godoc.org/github.com/zclconf/go-cty/cty/function) serves that need. The shared function abstraction is intended to help applications provide the expected behavior in handling `cty` complexities such as unknown values and dynamic types. The infrastructure in this package can do basic type checking automatically, allowing applications to focus on the logic unique to each function. ## Function Specifications Functions are defined by calling applications via `FunctionSpec` instances. These describe the parameters the function accepts, the return value it would produce given a set of parameters, and the actual implementation of the function. The return type is defined by a function, allowing the definition of generic functions whose return type depends on the given argument types or values. ### Function Parameters Functions can have both fixed parameters and variadic arguments. Each fixed parameter has its own separate specification, while the variadic arguments together share a single parameter specification, meaning that they must all be of the same type. [`Parameter`](https://godoc.org/github.com/zclconf/go-cty/cty/function#Parameter) represents the description of a parameter. The `Params` member of `FunctionSpec` is a slice of positional parameters, while `VarParam` is a pointer to the description of the variadic arguments, if supported. Parameters have the following fields: * `Name` is not used directly by this package but is intended to be useful in generating documentation based on function specifications. * `Type` is a type specification that a given argument must _conform_ to. (see the `TestConformance` method of `cty.Type` for information on what exactly that means.) * `AllowNull` can be set to `true` to permit the caller to provide null values. If not set, passing a null is treated as an immediate error and the implementation function is not called at all. * `AllowUnknown` can be set to `true` if the implementation function is prepared to handle unknown values. If not set, calls with an unknown argument will immediately return an unknown value of the function's return type, and the implementation function is not called at all. * `AllowDynamicType` can be set to `true` to allow not-yet-typed values to be passed. If not set, calls with a dynamic argument will immediately return `cty.DynamicVal`, and neither the type-checking function nor the implementation function will be called. Since dynamic values are themselves unknown, `AllowUnknown` and `AllowDynamicType` must be set together to permit `cty.DynamicVal` to be passed as an argument to the implementation function, but setting `AllowDynamicType` _without_ setting `AllowUnknown` has the special effect of allowing dynamic values to be passed into the type checking function _without_ also passing them to the implementation function, allowing a more specific return type to be specified even if the input type isn't known. ### Return Type A function returns a single value when called. The return type function, specified via the `Type` field in `FunctionSpec`, defines the type this value will have for the given arguments. The arguments are passed to the type function as _values_ rather than as types, though in many cases they will be unknown values for which the only useful operation is to call the `Type()` method on them. Unknown values can be passed to the type function regardless of how the `AllowUnknown` flag is set on the associated parameter specification. If `AllowDynamicType` is set on a parameter specification, a corresponding argument may be `cty.DynamicVal`. The return type function can then handle this how it wishes. If the parameter _itself_ is typed as `cty.DynamicPseudoType` then the corresponding argument may be a value of _any_ type. These behaviors together allow the return type function to behave as a full-fledged _type checking_ function, returning an error if the caller's supplied types do not conform to some requirements that are not simple enough to be expressed via the parameter specifications alone. Returning `cty.DynamicPseudoType` from the type checking function signals that the function is not able to determine its return type from the given information. Hopefully -- but not necessarily -- the function _implementation_ will produce a value of a known type once the argument values are themselves known. Calling applications may elect to pass _known_ values for type checking, which then allows for functions whose return type depends on argument _values_. This is a relatively-rare situation, but one key example is a hypothetical JSON decoding function, which takes a string value for the JSON structure to decode. If given `cty.Unknown(cty.String)` as an argument, this function would need to specify its return type as `cty.DynamicPseudoType`, but if given a _known_ string it could infer an appropriate return type from that string. ### Function Implementation The `Impl` field in `FunctionSpec` is used to specify the function's implementation as a Go function pointer. The implementation function takes a slice of `cty.Value` representing the call arguments and the `cty.Type` that was returned from the return type function. It must then either produce a value conforming to that given type or return an error. A function implementer can write any arbitrary Go code into the implementation of a function, but `cty` functions are intended to behave as pure functions, so side-effects should be avoided unless the function is specialized for a particular calling application that is able to accept such side-effects. If any of the given arguments are unknown and their corresponding parameter specifications _permit_ unknowns, the function implementation must handle this situation, normally by immediately returning an unknown value of the required return type. A function should _not_ return unknown values unless at least one of the arguments is unknown, since to do otherwise would violate the `cty` guarantee that a caller can avoid dealing with the complexity of unknown values by never passing any in. ## The `cty` Standard Library The set of operations provided directly on `cty.Value` is intended to cover the basic operators of a simple expression language, but there are several higher-level operations that can be implemented in terms of `cty` values, such as string manipulations, standard mathematical functions, etc. [The standard library](https://godoc.org/github.com/zclconf/go-cty/cty/function/stdlib) contains a set of `cty` functions that are intended to be generally useful. For the convenience of calling applications, each function is provided both as a first-class Go function _and_ as a `Function` instance; the former could be useful for Go code dealing directly with `cty.Value` instances, while the latter is likely more useful for exposing functions into a language interpreter. The standard library also includes some functions that are just thin wrappers around the operations on `cty.Value`. These are somewhat redundant, but exposing them as functions has the advantage that their operands can be described as function parameters and so automatic type checking and error handling is possible, whereas the `cty.Value` operations prefer to `panic` when given invalid input. go-cty-1.12.1/docs/gocty.md000066400000000000000000000166571433256746400154320ustar00rootroot00000000000000# Converting between Go and `cty` values While `cty` provides a representation of values within its own type system, a calling application will inevitably need to eventually pass values to a native Go API, using native Go types. [The `gocty` package](https://godoc.org/github.com/zclconf/go-cty/cty/gocty) aims to make conversions between `cty` values and Go values as convenient as possible, using an approach similar to that used by `encoding/json` where the `reflect` package is used to define the desired structure using Go native types. ## From Go to `cty` Converting Go values to `cty` values is the task of the `ToCtyValue` function. It takes an arbitrary Go value (as an `interface{}`) and `cty.Type` describing the shape of the desired value. The given type is used both as a conformance check and as a source of hints to resolve ambiguities in the mapping from Go types. For example, it is valid to convert a Go slice to both a `cty` set and list types, and so the given `cty` type is used to indicate which is desired. The errors generated by this function use terminology aimed at the developers of the calling application, since it's assumed that any problems encountered are bugs in the calling program and are thus "should never happen" cases. Since unknown values cannot be represented natively in Go's type system, `gocty` works only with known values. An error will be generated if a caller attempts to convert an unknown value into a Go value. ## From `cty` to Go Converting `cty` values to Go values is done via the `FromCtyValue` function. In this case, the function mutates a particular Go value in place rather than returning a new value, as is traditional from similar functions in packages like `encoding/json`. The function must be given a non-nil pointer to the value that should be populated. If the function succeeds without error then this target value has been populated with data from the given `cty` value. Any errors returned are written with the target audience being the hypothetical user that wrote whatever input was transformed into the given cty value, and thus the terminology used is `cty` type system terminology. As a concrete example, consider converting a value into a Go `int8`: ```go var val int8 err := gocty.FromCtyValue(value, &val) ``` There are a few different ways that this can fail: * If `value` is not a `cty.Number` value, the error message returned says "a number is required", assuming that this value came from user input and the user provided a value of the wrong type. * If `value` is not an integer, or it's an integer outside of the range of an `int8`, the error message says "must be a whole number between -128 and 127", again assuming that this was user input and that the target type here is an implied constraint on the value provided by the user. As a consequence, it is valid and encouraged to convert arbitrary user-supplied values into concrete Go data structures as a concise way to express certain data validation constraints in a declarative way, and then return any error message verbatim to the end-user. ## Converting to and from `struct`s As well as straightforward mappings of primitive and collection types, `gocty` can convert object and tuple values to and from values of Go `struct` types. For tuples, the target `struct` must have exactly the same number of fields as exist in the tuple, and the fields are used in the order they are defined with no regard to their names or tags. A `struct` used to decode a tuple must have all public attributes. These constraints mean that generally-speaking it will be hard to re-use existing application structs for this purpose, and instead a specialized struct must be used to represent each tuple type. For simple uses, a struct defined inline within a function can be used. For objects, the mapping is more flexible. Field tags are used to express which struct fields correspond to each object attribute, as in the following example: ```go type Example struct { Name string `cty:"name"` Age int `cty:"age"` } ``` For the mapping to be valid, there must be a one-to-one correspondence between object attributes and tagged struct fields. The presence or absence of attribute tags in the struct is used to define which attributes are valid, and so error messages will be generated for any extraneous or missing attributes. Additional fields may be present without tags, but all fields with tags must be public. ## Dynamically-typed Values If parts of the `cty` data structure have types that can't be known until runtime, it is possible to leave these portions un-decoded for later processing. To achieve this, `cty.DynamicPseudoType` is used in the type passed to the two conversion functions, and at the corresponding place in the Go data structure a `cty.Value` object is placed. When converting from `cty` to Go, the portion of the value corresponding to the dynamic pseudo-type is assigned directly to the `cty.Value` object with no conversion, so the calling program can then use the core `cty` API to interact with it. The converse is true for converting from Go to `cty`: any valid `cty.Value` object can be provided, and it will be included verbatim in the returned `cty.Value`. ```go type Thing struct { Name string `cty:"name"` ExtraData cty.Value `cty:"extra_data"` } thingType := cty.Object(map[string]cty.Type{ "name": cty.String, "extra_data": cty.DynamicPseudoType, }) thingVal := cty.ObjectVal(map[string]cty.Value{ "name": cty.StringVal("Ermintrude"), "extra_data": cty.NumberIntVal(12), }) var thing Thing err := gocty.FromCtyValue(thingVal, &thing) // (error check) fmt.Printf("extra_data is %s", thing.ExtraData.Type().FriendlyName()) // Prints: "extra_data is number" ``` ## Conversion of Capsule Types Since capsule types encapsulate native Go values, their handling in `gocty` is a simple wrapping and un-wrapping of the encapsulated value. The encapsulated type and the type of the target value must match. Since capsule values capture a pointer to the target value, it is possible to round-trip a pointer from a Go value into a capsule value and back to a Go value and recover the original pointer value, referring to the same in-memory object. ## Implied `cty` Type of a Go value In simple cases it can be desirable to just write a simple type in Go and use it immediately in conversions, without needing to separately write out a corresponding `cty.Type` expression. The `ImpliedType` function helps with this by trying to generate a reasonable `cty.Type` from a native Go value. Not all `cty` types can be represented in this way, but if the goal is a straightforward mapping to a convenient Go data structure then this function is suitable. The mapping is as follows: * Go's int, uint and float types all map to `cty.Number`. * Go's bool type maps to `cty.Bool` * Go's string type maps to `cty.String` * Go slice types map to `cty` lists with the element type mapped per these rules. * Go maps _with string keys_ map to `cty` maps with the element type mapped per these rules. * Go struct types are converted to `cty` object types using the struct tag convention described above and these mapping rules for each tagged field. * A Go value of type `cty.Value` maps to `cty.DynamicPseudoType`, allowing for values whose precise type isn't known statically. `ImpliedType` considers only the Go type of the provided value, so it's valid to pass a nil or zero value of the type. When passing `nil`, be sure to convert it to the target type, e.g. `[]int(nil)`. go-cty-1.12.1/docs/json.md000066400000000000000000000074261433256746400152500ustar00rootroot00000000000000# JSON serialization of `cty` values [The `json` package](https://godoc.org/github.com/zclconf/go-cty/cty/json) allows `cty` values to be serialized as JSON and decoded back into `cty` values. Since the `cty` type system is a superset of the JSON type system, two modes of operation are possible: The recommended approach is to define the intended `cty` data structure as a `cty.Type` -- possibly involving `cty.DynamicPseudoType` placeholders -- which then allows full recovery of the original values with correct type information, assuming that the same type description can be provided at decoding time. Alternatively, this package can decode an arbitrary JSON data structure into the corresponding `cty` types, which means that it is possible to serialize a `cty` value without type information and then decode into a value that contains the same data but possibly uses different types to represent that data. This allows direct integration with the standard library `encoding/json` package, at the expense of type-lossy deserialization. ## Type-preserving JSON Serialization The `Marshal` and `Unmarshal` functions together provide for type-preserving serialization and deserialization (respectively) of `cty` values. The pattern for using these functions is to define the intended `cty` type as a `cty.Type` instance and then pass an identical type as the second argument to both `Marshal` and `Unmarshal`. Assuming an identical type is used for both functions, it is guaranteed that values will round-trip through JSON serialization to produce a value of the same type. The `cty.Type` passed to `Unmarshal` is used as a hint to resolve ambiguities in the mapping to JSON. For example, `cty` list, set and tuple types all lower to JSON arrays, so additional type information is needed to decide which type to use when unmarshaling. The `cty.Type` passed to `Marshal` serves a more subtle purpose. Any `cty.DynamicPseudoType` placeholders in the type will cause extra type information to be saved in the JSON data structure, which is then used by `Unmarshal` to recover the original type. Type-preserving JSON serialization is able to serialize and deserialize capsule-typed values whose encapsulated Go types are JSON-serializable, except when those values are conformed to a `cty.DynamicPseudoType`. However, since capsule values compare by pointer equality, a decoded value will not be equal (as `cty` defines it) with the value that produced it. ## Type-lossy JSON Serialization If a given application does not need to exactly preserve the type of a value, the `SimpleJSONValue` type provides a simpler method for JSON serialization that works with the `encoding/json` package in Go's standard library. `SimpleJSONValue` is a wrapper struct around `cty.Value`, which can be embedded into another struct used with the standard library `Marshal` and `Unmarshal` functions: ```go type Example struct { Name string `json:"name"` Value SimpleJSONValue `json:"value"` } var example Example example.Name = "Ermintrude" example.Value = SimpleJSONValue{cty.NumberIntVal(43)} ``` Since no specific `cty` type is available when unmarshalling into `SimpleJSONValue`, a straightforward mapping is used: * JSON strings become `cty.String` values. * JSON numbers become `cty.Number` values. * JSON booleans become `cty.Bool` values. * JSON arrays become `cty.Tuple`-typed values whose element types are selected via this mapping. * JSON objects become `cty.Object`-typed values whose attribute types are selected via this mapping. * Any JSON `null` is mapped to `cty.NullVal(cty.DynamicPseudoType)`. The above mapping is unambiguous and lossless, so any valid JSON buffer can be decoded into an equally-expressive `cty` value, but the type may not exactly match that of the value used to produce the JSON buffer in the first place. go-cty-1.12.1/docs/marks.md000066400000000000000000000106261433256746400154100ustar00rootroot00000000000000# 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. ```go // Use a named type for all marks, for namespacing purposes. type fromConfigType int val fromConfig fromConfigType return val.Mark(fromConfig) ``` ```go 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: ```go 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. go-cty-1.12.1/docs/types.md000066400000000000000000000411241433256746400154340ustar00rootroot00000000000000# `cty` types This page covers in detail all of the primitive types and compound type kinds supported within `cty`. For more general background information, see [the `cty` overview](./concepts.md). ## Common Operations and Integration Methods The following methods apply to all values: * `Type` returns the type of the value, as a `cty.Type` instance. * `Equals` returns a `cty.Bool` that is `cty.True` if the receiver and the given other value are equal. This is an operation method, so its result will be unknown if either argument is unknown. * `RawEquals` is similar to `Equals` except that it doesn't implement the usual special behavior for unknowns and dynamic values, and it returns a native Go `bool` as its result. This method is intended for use in tests; `Equals` should be preferred for most uses. * `IsKnown` returns `true` if the receiver is a known value, or `false` if it is an unknown value. * `IsNull` returns `true` if the receiver is a null value, or `false` otherwise. All values except capsule-typed values can be serialized with the builtin Go package `encoding/gob`. Values can also be used with the `%#v` pattern in the `fmt` package to print out a Go-oriented serialization of the value. ## Primitive Types ### `cty.Number` The number type represents what we'll clumsily call "JSON numbers". Technically, this means the set of numbers that have a canonical decimal representation in our JSON encoding _and_ that can be represented in memory with 512 bits of binary floating point precision. Since these numbers have high precision, there is little need to worry about integer overflow/underflow or over-zealous rounding during arithmetic operations. In particular, `cty.Number` can represent the full range of `int64` with no loss. However, numbers _are_ still finite in memory and subject to approximation in binary-to-decimal and decimal-to-binary conversions, and so can't accurately represent _all_ real numbers. Eventually a calling application will probably want to convert a number to one of the Go numeric types, at which point its range will be constrained to fit within that type, generating an error if it does not fit. Because the number range is larger than all of the Go integer types, it's always possible to convert a whole number to a Go integer without any loss, as long as it value is within the required range. The following additional operations are supported on numbers: * `Absolute` converts a negative value to a positive value of the same magnitude. * `Add` computes the sum of two numbers. * `Divide` divides the receiver by another number. * `GreaterThan` returns `cty.True` if the receiver is greater than the other given number. * `GreaterThanOrEqualTo` returns `cty.True` if the receiver is greater than or equal to the other given number. * `LessThan` returns `cty.True` if the receiver is less than the other given number. * `LessThanOrEqualTo` returns `cty.True` if the receiver is less than or equal to the other given number. * `Modulo` computes the remainder from integer division of the receiver by the other given number. * `Multiply` computes the product of two numbers. * `Negate` inverts the sign of the number. * `Subtract` computes the difference between two numbers. `cty.Number` values can be constructed using several different factory functions: * `ParseNumberVal` creates a number value by parsing a decimal representation of it given as a string. This is the constructor that most properly represents the full documented range of number values; the others below care convenient for many cases, but have a more limited range. * `NumberIntVal` creates a number value from a native `int64` value. * `NumberUIntVal` creates a number value from a native `uint64` value. * `NumberFloatVal` creates a number value from a native `float64` value. * `NumberVal` creates a number value from a `*big.Float`, from the `math/big` package. This can preserve arbitrary big floats without modification, but comes at the risk of introducing precision inconsisistencies. Prefer the other constructors for most uses. The core API only allows extracting the value from a known number as a `*big.Float` using the `AsBigFloat` method. However, [the `gocty` package](./gocty.md) provides a more convenient way to convert numbers to any native Go number type, with automatic range checking to ensure that the value fits into the target type. The following numbers are provided as package variables for convenience: * `cty.Zero` is the number zero. * `cty.PositiveInfinity` represents positive infinity as a number. All other numbers are less than this value. * `cty.NegativeInfinity` represents negative infinity as a number. All other numbers are greater than this value. Note that the two infinity values are always out of range for a conversion to any Go primitive integer type. ### `cty.String` The string type represents a sequence of unicode codepoints. There are no additional operations supported for strings. `cty.String` values can be constructed using the `cty.StringVal` factory function. The native Go `string` passed in must be a valid UTF-8 sequence, and it will be normalized such that any combining diacritics are converted to precomposed forms where available. (Technically-speaking, the mapping applied is the NFC normalization as defined in the relevant Unicode specifications.) The `AsString` method can be called on a string value to obtain the native Go `string` representation of a known string, after normalization. ### `cty.Bool` The bool type represents boolean (true or false) values. The following additional operations are supported on bool values: * `And` computes the logical AND operation for two boolean values. * `Not` returns the boolean opposite of the receiver. * `Or` computes the logical OR operation for two boolean values. Calling applications may either work directly with the predefined `cty.True` and `cty.False` variables, or dynamically create a boolean value using `cty.BoolVal`. The `True` method returns a native Go `bool` representing a known boolean value. The `False` method returns the opposite of it. ## Collection Type Kinds `cty` has three different kinds of collection type. All three of them are parameterized with a single _element type_ to produce a collection type. The difference between the kinds is how the elements are internally organized and what operations are used to retrieve them. ### `cty.List` types List types are ordered sequences of values, accessed using consecutive integers starting at zero. The following operations apply to values of a list type: * `Index` can be passed an integer number less than the list's length to retrieve one of the list elements. * `HasIndex` can be used to determine if a particular call to `Index` would succeed. * `Length` returns a number representing the number of elements in the list. The highest integer that can be passed to `Index` is one less than this number. List types are created by passing an element type to the function `cty.List`. List _values_ can be created by passing a type-homogenous `[]cty.Value` to `cty.ListVal`, or by passing an element type to `cty.ListValEmpty`. The following integration methods can be used with known list-typed values: * `LengthInt` returns the length of the list as a native Go `int`. * `ElementIterator` returns an object that can be used to iterate over the list elements. * `ForEachElement` runs a given callback function for each element. ### `cty.Map` types Map types are collection values that are each assigned a unique string key. The following operations apply to values of a map type: * `Index` can be passed a string value to retrieve the corresponding element. * `HasIndex` can be used to determine if a particular call to `Index` would succeed. * `Length` returns a number representing the number of elements in the map. Map types are created by passing an element type to the function `cty.Map`. Map _values_ can be created by passing a type-homogenous `map[string]cty.Value` to `cty.MapVal`, or by passing an element type to `cty.MapValEmpty`. The following integration methods can be used with known map-typed values: * `LengthInt` returns the number of elements as a native Go `int`. * `ElementIterator` returns an object that can be used to iterate over the map elements in lexicographical order by key. * `ForEachElement` runs a given callback function for each element in the same order as the `ElementIterator`. ### `cty.Set` types Set types are collection values that model a mathematical set, where every possible value is either in or out of the set. Thus each set element value is its own identity in the set, and a given value cannot appear twice in the same set. The following operations apply to values of a set type: * `HasIndex` can be used to determine whether a particular value is in the receiving set.. * `Length` returns a number representing the number of elements in the set. * `Index` is not particularly useful for sets, but for symmetry with the other collection types it may be passed a value that is in the set and it will then return that same value. Set types are created by passing an element type to the function `cty.Set`. Set _values_ can be created by passing a type-homogenous `[]cty.Value` to `cty.SetVal`, though the result is undefined if two values in the slice are equal. Alternatively, an empty set can be constructed using `cty.SetValEmpty`. The following integration methods can be used with known set-typed values: * `LengthInt` returns the number of elements as a native Go `int`. * `ElementIterator` returns an object that can be used to iterate over the set elements in an undefined (but consistent) order. * `ForEachElement` runs a given callback function for each element in the same order as the `ElementIterator`. Set membership is determined by equality, which has an interesting consequence for unknown values. Since unknown values are never equal to one another, theoretically an infinite number of unknown values can be in a set (constrained by available memory) but can never be detected by calls to `HasIndex`. A set with at least one unknown value in it has an unknown length, because the unknown values may or may not match each other (and thus coalesce into a single value) once they become known. However, if a set contains a mixture of known and unknown values then `HasIndex` will return true for those values because they are guaranteed to remain present no matter what final known value each of the unknown values takes on. ## Structural Types `cty` has two different kinds of structural type. They have in common that they combine a number of values of arbitrary types together into a single value, but differ in how those values are internally organized and in which operations are used to retrieve them. ### `cty.Object` types Object types each have zero or more named attributes that each in turn have their own type. The following operation applies to values of an object type: * `GetAttr` returns the value of an attribute given its name. The set of valid attributes for an object type can be inspected using the following methods on the type itself: * `AttributeTypes` returns a `map[string]Type` describing the types of all of the attributes. * `AttributeType` returns the type of a single attribute given its name. * `HasAttribute` returns `true` if the type has an attribute with the given name. Object types are constructed by passing a `map[string]Type` to `cty.Object`. Object _values_ can be created by passing a `map[string]Value` to `cty.ObjectVal`, in which the keys and value types define the object type that is implicitly created for that value. The variable `cty.EmptyObject` contains the object type with no attributes, and `cty.EmptyObjectVal` is the only non-null, known value of that type. There is **currently-experimental** support for creating object types where one or more attributes is annotated as being "optional", using the alternative constructor `cty.ObjectWithOptionalAttrs`. The behavior of that function or of any other function working with its result is subject to change even in future minor versions of `cty`. The optional-attribute annotations are considered only during type conversion, so for more information refer to the guide [Converting between `cty` types](convert.md). ### `cty.Tuple` types Tuple types each have zero or more elements, each with its own type, arranged in a sequence and accessed by integer numbers starting at zero. A tuple type is therefore somewhat similar to a list type, but rather than representing an arbitrary number of values of a single type it represents a fixed number of values that may have _different_ types. The following operations apply to values of a tuple type: * `Index` can be passed an integer number less than the tuple's length to retrieve one of the tuple elements. * `HasIndex` can be used to determine if a particular call to `Index` would succeed. * `Length` returns a number representing the number of elements in the tuple. The highest integer that can be passed to `Index` is one less than this number. Tuple types are created by passing a `[]cty.Type` to the function `cty.Tuple`. Tuple _values_ can be created by passing a `[]cty.Value` to `cty.TupleVal`, in which the value types define the tuple type that is implicitly created for that value. The variable `cty.EmptyTuple` contains the tuple type with no elements, and `cty.EmptyTupleVal` is the only non-null, known value of that type. The following integration methods can be used with known tuple-typed values: * `LengthInt` returns the length of the tuple as a native Go `int`. * `ElementIterator` returns an object that can be used to iterate over the tuple elements. * `ForEachElement` runs a given callback function for each element. ## The Dynamic Pseudo-Type The dynamic pseudo-type is not a real type but is rather a _placeholder_ for a type that isn't known. One consequence of this being a "pseudo-type" is that there is no known, non-null value of this type, but `cty.DynamicVal` is the unknown value of this type, and a null value without a known type can be represented by `cty.NullVal(cty.DynamicPseudoType)`. This pseudo-type serves two similar purposes as a placeholder type: * When `cty.DynamicVal` is used in an operation with another value, the result is either itself `cty.DynamicVal` or it is an unknown value of some suitable type. This allows the dynamic pseudo-type to be used as a placeholder during type checking, optimistically assuming that the eventually-determined type will be compatible and failing at that later point if not. * `cty.DynamicPseudoType` can be used with the type `TestConformance` method to declare that any type is permitted in the type being tested for conformance. `cty` doesn't have _sum types_ (i.e. union types), so `cty.DynamicPseudoType` can be used also to represent situations where two or more specific types are allowed, under the assumption that more specific type checking will be done within the calling application's own logic even though it cannot be expressed directly within the `cty` type system. ## Capsule Types Capsule types are a special kind of type that allows a calling application to "smuggle" otherwise-unsupported Go values through the `cty` type system. Such types and their associated values have no defined meaning in `cty`. The interpreter for a language building on `cty` might use capsule types for passing language-specific objects between functions provided in that language. A capsule type is created using the function `cty.Capsule`, which takes a "friendly name" for the type along with a `reflect.Type` that defines what type of Go value will be encapsulated in values of this type. A capsule-typed value can then be created by passing the capsule type and a pointer to a native value of the encapsulated type to `cty.CapsuleVal`. The integration method `EncapsulatedValue` allows the encapsulated data to then later be retrieved. Capsule types compare by reference, so each call to `cty.Capsule` produces a distinct type. Capsule _values_ compare by pointer equality, so two capsule values are equal if they have the same capsule type and they encapsulate a pointer to the same object. Due to the strange nature of capsule types, they are not totally supported by all of the other packages that build on the core `cty` API. They should be used with care and the documentation for other packages should be consulted for information about caveats and constraints relating to their use. It's possible for a calling application to write additional logic to make capsule types support a subset of operations that are generally expected to work for values of any type. For more information, see [capsule type operation definitions](./capsule-type-operations.md). go-cty-1.12.1/go.mod000066400000000000000000000006611433256746400141250ustar00rootroot00000000000000module github.com/zclconf/go-cty require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 github.com/google/go-cmp v0.3.1 github.com/vmihailenco/msgpack/v4 v4.3.12 golang.org/x/text v0.3.7 ) require ( github.com/golang/protobuf v1.3.4 // indirect github.com/vmihailenco/tagparser v0.1.1 // indirect golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect google.golang.org/appengine v1.6.5 // indirect ) go 1.18 go-cty-1.12.1/go.sum000066400000000000000000000054001433256746400141460ustar00rootroot00000000000000github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=