Add support for ordering of input prompts (#662)

## Changes

JSON schema properties are a map and thus unordered.

This PR introduces a JSON schema extension field called `order` to allow
template authors to define the order in which template variables should
be resolved/prompted.

## Tests

Unit tests.

---------

Co-authored-by: Pieter Noordhuis <pieter.noordhuis@databricks.com>
This commit is contained in:
shreyas-goenka 2023-09-05 13:08:25 +02:00 committed by GitHub
parent 2f2386ef5a
commit bbbeabf98c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 138 additions and 1 deletions

View File

@ -0,0 +1,14 @@
package jsonschema
// Extension defines our custom JSON schema extensions.
//
// JSON schema supports custom extensions through vocabularies:
// https://json-schema.org/understanding-json-schema/reference/schema.html#vocabularies.
// We don't (yet?) define a meta-schema for the extensions below.
// It's not a big issue because the reach/scope of these extensions is limited.
type Extension struct {
// Order defines the order of a field with respect to other fields.
// If not defined, the field is ordered alphabetically after all fields
// that do have an order defined.
Order *int `json:"order,omitempty"`
}

View File

@ -40,6 +40,9 @@ type Schema struct {
// Default value for the property / object
Default any `json:"default,omitempty"`
// Extension embeds our custom JSON schema extensions.
Extension
}
type Type string

View File

@ -0,0 +1,57 @@
package jsonschema
import (
"slices"
"strings"
)
// Property defines a single property of a struct schema.
// This type is not used in the schema itself but rather to
// return the pair of a property name and its schema.
type Property struct {
Name string
Schema *Schema
}
// OrderedProperties returns the properties of the schema ordered according
// to the value of their `order` extension. If this extension is not set, the
// properties are ordered alphabetically.
func (s *Schema) OrderedProperties() []Property {
order := make(map[string]*int)
out := make([]Property, 0, len(s.Properties))
for key, property := range s.Properties {
order[key] = property.Order
out = append(out, Property{
Name: key,
Schema: property,
})
}
// Sort the properties by order and then by name.
slices.SortFunc(out, func(a, b Property) int {
oa := order[a.Name]
ob := order[b.Name]
cmp := 0
switch {
case oa != nil && ob != nil:
// Compare the order values if both are set.
cmp = *oa - *ob
case oa == nil && ob != nil:
// If only one is set, the one that is set comes first.
cmp = 1
case oa != nil && ob == nil:
// If only one is set, the one that is set comes first.
cmp = -1
}
// If we have a non-zero comparison, return it.
if cmp != 0 {
return cmp
}
// If the order is the same, compare by name.
return strings.Compare(a.Name, b.Name)
})
return out
}

View File

@ -0,0 +1,60 @@
package jsonschema
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestOrderedProperties(t *testing.T) {
newInt := func(i int) *int {
return &i
}
s := Schema{
Properties: map[string]*Schema{
"bbb": {
Type: StringType,
},
"ccc": {
Type: StringType,
},
"ddd": {
Type: StringType,
},
"zzz1": {
Type: StringType,
Extension: Extension{
Order: newInt(-1),
},
},
"zzz2": {
Type: StringType,
Extension: Extension{
Order: newInt(-2),
},
},
"aaa1": {
Type: StringType,
Extension: Extension{
Order: newInt(1),
},
},
"aaa2": {
Type: StringType,
Extension: Extension{
Order: newInt(2),
},
},
},
}
// Test that the properties are ordered by order and then by name.
properties := s.OrderedProperties()
names := make([]string, len(properties))
for i, property := range properties {
names[i] = property.Name
}
assert.Equal(t, []string{"zzz2", "zzz1", "aaa1", "aaa2", "bbb", "ccc", "ddd"}, names)
}

View File

@ -117,7 +117,10 @@ func (c *config) assignDefaultValues() error {
// Prompts user for values for properties that do not have a value set yet
func (c *config) promptForValues() error {
for name, property := range c.schema.Properties {
for _, p := range c.schema.OrderedProperties() {
name := p.Name
property := p.Schema
// Config already has a value assigned
if _, ok := c.values[name]; ok {
continue