// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

syntax = "proto3";

option go_package = "hclspec";

/* Spec allows exposing the specification for an HCL body, allowing for parsing and
validation.

Certain expressions within a specification may use the following functions.
The documentation for each spec type above specifies where functions may
be used.

* `abs(number)` returns the absolute (positive) value of the given number.
* `coalesce(vals...)` returns the first non-null value given.
* `concat(lists...)` concatenates together all of the given lists to produce a new list.
* `hasindex(val, idx)` returns true if the expression `val[idx]` could succeed.
* `int(number)` returns the integer portion of the given number, rounding towards zero.
* `jsondecode(str)` interprets the given string as JSON and returns the resulting data structure.
* `jsonencode(val)` returns a JSON-serialized version of the given value.
* `length(collection)` returns the number of elements in the given collection (list, set, map, object, or tuple).
* `lower(string)` returns the given string with all uppercase letters converted to lowercase.
* `max(numbers...)` returns the greatest of the given numbers.
* `min(numbers...)` returns the smallest of the given numbers.
* `reverse(string)` returns the given string with all of the characters in reverse order.
* `strlen(string)` returns the number of characters in the given string.
* `substr(string, offset, length)` returns the requested substring of the given string.
* `upper(string)` returns the given string with all lowercase letters converted to uppercase.

## Type Expressions

Type expressions are used to describe the expected type of an attribute, as
an additional validation constraint.

A type expression uses primitive type names and compound type constructors.
A type constructor builds a new type based on one or more type expression
arguments.

The following type names and type constructors are supported:

* `any` is a wildcard that accepts a value of any type. (In HCL terms, this
  is the _dynamic pseudo-type_.)
* `string` is a Unicode string.
* `number` is an arbitrary-precision floating point number.
* `bool` is a boolean value (`true` or `false`)
* `list(element_type)` constructs a list type with the given element type
* `set(element_type)` constructs a set type with the given element type
* `map(element_type)` constructs a map type with the given element type
* `object({name1 = element_type, name2 = element_type, ...})` constructs
  an object type with the given attribute types.
* `tuple([element_type, element_type, ...])` constructs a tuple type with
  the given element types. This can be used, for example, to require an
  array with a particular number of elements, or with elements of different
  types.

`null` is a valid value of any type, and not a type itself.
*/
package hashicorp.nomad.plugins.shared.hclspec;

// Spec defines the available specification types.
message Spec {
  oneof block {
    Object object = 1;
    Array array = 2;
    // buf:lint:ignore FIELD_LOWER_SNAKE_CASE
    Attr Attr = 3;
    Block block_value = 4;
    BlockAttrs block_attrs = 5;
    BlockList block_list = 6;
    BlockSet block_set = 7;
    BlockMap block_map = 8;
    Default default = 9;
    Literal literal = 10;
  }
}

/* Attr spec type reads the value of an attribute in the current body
and returns that value as its result. It also creates validation constraints
for the given attribute name and its value.

```hcl
Attr {
  name     = "document_root"
  type     = string
  required = true
}
```

`Attr` spec blocks accept the following arguments:

* `name` (required) - The attribute name to expect within the HCL input file.
  This may be omitted when a default name selector is created by a parent
  `Object` spec, if the input attribute name should match the output JSON
  object property name.

* `type` (optional) - A [type expression](#type-expressions) that the given
  attribute value must conform to. If this argument is set, `hcldec` will
  automatically convert the given input value to this type or produce an
  error if that is not possible.

* `required` (optional) - If set to `true`, `hcldec` will produce an error
  if a value is not provided for the source attribute.

`Attr` is a leaf spec type, so no nested spec blocks are permitted.
*/
message Attr {
 string name = 1;
 string type = 2;
 bool required = 3;
}

/* Block spec type applies one nested spec block to the contents of a
block within the current body and returns the result of that spec. It also
creates validation constraints for the given block type name.

```hcl
Block {
  name = "logging"

  Object {
    Attr "level" {
      type = string
    }
    Attr "file" {
      type = string
    }
  }
}
```

`Block` spec blocks accept the following arguments:

* `name` (required) - The block type name to expect within the HCL
  input file. This may be omitted when a default name selector is created
  by a parent `Object` spec, if the input block type name should match the
  output JSON object property name.

* `required` (optional) - If set to `true`, `hcldec` will produce an error
  if a block of the specified type is not present in the current body.

`Block` creates a validation constraint that there must be zero or one blocks
of the given type name, or exactly one if `required` is set.

`Block` expects a single nested spec block, which is applied to the body of
the block of the given type when it is present.

*/
message Block {
 string name = 1;
 bool required = 2;
 Spec nested = 3;
}

/*
 The BlockAttrs spec type is similar to an Attr spec block of a map type,
 but it produces a map from the attributes of a block rather than from an
 attribute's expression.

 ```hcl
 BlockAttrs {
   name     = "variables"
   type     = string
   required = false
 }
 ```

 This allows a map with user-defined keys to be produced within block syntax,
 but due to the constraints of that syntax it also means that the user will
 be unable to dynamically-generate either individual key names using key
 expressions or the entire map value using a `for` expression.

 `BlockAttrs` spec blocks accept the following arguments:

 * `name` (required) - The block type name to expect within the HCL
   input file. This may be omitted when a default name selector is created
   by a parent `object` spec, if the input block type name should match the
   output JSON object property name.

 * `type` (required) - The value type to require for each of the
   attributes within a matched block. The resulting value will be a JSON
   object whose property values are of this type.

 * `required` (optional) - If `true`, an error will be produced if a block
   of the given type is not present. If `false` -- the default -- an absent
   block will be indicated by producing `null`.
*/
message BlockAttrs {
 string name = 1;
 string type = 2;
 bool required = 3;
}

/* BlockList spec type is similar to `Block`, but it accepts zero or
more blocks of a specified type rather than requiring zero or one. The
result is a JSON array with one entry per block of the given type.

```hcl
BlockList {
  name = "log_file"

  Object {
    Attr "level" {
      type = string
    }
    Attr "filename" {
      type     = string
      required = true
    }
  }
}
```

`BlockList` spec blocks accept the following arguments:

* `name` (required) - The block type name to expect within the HCL
  input file. This may be omitted when a default name selector is created
  by a parent `Object` spec, if the input block type name should match the
  output JSON object property name.

* `min_items` (optional) - If set to a number greater than zero, `hcldec` will
  produce an error if fewer than the given number of blocks are present.

* `max_items` (optional) - If set to a number greater than zero, `hcldec` will
  produce an error if more than the given number of blocks are present. This
  attribute must be greater than or equal to `min_items` if both are set.

`Block` creates a validation constraint on the number of blocks of the given
type that must be present.

`Block` expects a single nested spec block, which is applied to the body of
each matching block to produce the resulting list items.

*/
message BlockList {
 string name = 1;
 uint64 min_items = 2;
 uint64 max_items = 3;
 Spec nested = 4;
}

/* BlockSet spec type behaves the same as BlockList except that
the result is in no specific order and any duplicate items are removed.

```hcl
BlockSet {
  name = "log_file"

  Object {
    Attr "level" {
      type = string
    }
    Attr "filename" {
      type     = string
      required = true
    }
  }
}
```

The contents of `BlockSet` are the same as for `BlockList`.

*/
message BlockSet {
 string name = 1;
 uint64 min_items = 2;
 uint64 max_items = 3;
 Spec nested = 4;
}

/* BlockMap spec type is similar to `Block`, but it accepts zero or
more blocks of a specified type rather than requiring zero or one. The
result is a JSON object, or possibly multiple nested JSON objects, whose
properties are derived from the labels set on each matching block.

```hcl
BlockMap {
  name = "log_file"
  labels = ["filename"]

  Object {
    Attr "level" {
      type     = string
      required = true
    }
  }
}
```

`BlockMap` spec blocks accept the following arguments:

* `name` (required) - The block type name to expect within the HCL
  input file. This may be omitted when a default name selector is created
  by a parent `Object` spec, if the input block type name should match the
  output JSON object property name.

* `labels` (required) - A list of user-oriented block label names. Each entry
  in this list creates one level of object within the output value, and
  requires one additional block header label on any child block of this type.
  Block header labels are the quoted strings that appear after the block type
  name but before the opening `{`.

`Block` creates a validation constraint on the number of labels that blocks
of the given type must have.

`Block` expects a single nested spec block, which is applied to the body of
each matching block to produce the resulting map items.

*/
message BlockMap {
 string name = 1;
 repeated string labels = 2;
 Spec nested = 3;
}

/* Literal spec type returns a given literal value, and creates no
validation constraints. It is most commonly used with the `Default` spec
type to create a fallback value, but can also be used e.g. to fill out
required properties in an `Object` spec that do not correspond to any
construct in the input configuration.

```hcl
Literal {
  value = "hello world"
}
```

`Literal` spec blocks accept the following argument:

* `value` (required) - The value to return. This attribute may be an expression
  that uses [functions](#spec-definition-functions).

`Literal` is a leaf spec type, so no nested spec blocks are permitted.
*/
message Literal {
 string value = 1;
}

/* Default spec type evaluates a sequence of nested specs in turn and
returns the result of the first one that produces a non-null value.
It creates no validation constraints of its own, but passes on the validation
constraints from its first nested block.

```hcl
Default {
  Attr {
    name = "private"
    type = bool
  }
  Literal {
    value = false
  }
}
```

A `Default` spec block must have at least one nested spec block, and should
generally have at least two since otherwise the `Default` wrapper is a no-op.

The second and any subsequent spec blocks are _fallback_ specs. These exhibit
their usual behavior but are not able to impose validation constraints on the
current body since they are not evaluated unless all prior specs produce
`null` as their result.

*/
message Default {
 Spec primary = 1;
 Spec default = 2;
}

/* Object spec type is the most commonly used at the root of a spec file.
Its result is a JSON object whose properties are set based on any nested
spec blocks:

```hcl
Object {
  Attr "name" {
    type = "string"
  }
  Block "address" {
    Object {
      Attr "street" {
        type = "string"
      }
      # ...
    }
  }
}
```

Nested spec blocks inside `Object` must always have an extra block label
`"name"`, `"address"` and `"street"` in the above example) that specifies
the name of the property that should be created in the JSON object result.
This label also acts as a default name selector for the nested spec, allowing
the `Attr` blocks in the above example to omit the usually-required `name`
argument in cases where the HCL input name and JSON output name are the same.

An `Object` spec block creates no validation constraints, but it passes on
any validation constraints created by the nested specs.
*/
message Object {
 map<string, Spec> attributes = 1;
}

/* Array spec type produces a JSON array whose elements are set based on
any nested spec blocks:

```hcl
Array {
  Attr {
    name = "first_element"
    type = "string"
  }
  Attr {
    name = "second_element"
    type = "string"
  }
}
```

An `Array` spec block creates no validation constraints, but it passes on
any validation constraints created by the nested specs.
*/
message Array {
    repeated Spec values = 1;
}
