Пример #1
0
func validateGetAttAttributeID(builtin parse.IntrinsicFunction, resourceID, attributeID interface{}, ctx PropertyContext) reporting.Reports {
	resource := ctx.Template().Resources[resourceID.(string)]
	definition := ctx.Definitions().Lookup(resource.Type)

	switch t := attributeID.(type) {
	case string:
		if attribute, ok := definition.Attributes[t]; ok {
			targetType := attribute.Type
			switch targetType.CoercibleTo(ctx.Property().Type) {
			case CoercionNever:
				return reporting.Reports{reporting.NewFailure(ctx, "GetAtt value of %s.%s is %s but is being assigned to a %s property", resourceID, t, targetType.Describe(), ctx.Property().Type.Describe())}
			case CoercionBegrudgingly:
				return reporting.Reports{reporting.NewWarning(ctx, "GetAtt value of %s.%s is %s but is being dangerously coerced to a %s property", resourceID, t, targetType.Describe(), ctx.Property().Type.Describe())}
			}

			return nil
		}
	case parse.IntrinsicFunction:
		getAttAttributeNameType := Schema{Type: ValueString}
		_, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, getAttAttributeNameType), SupportedFunctions{
			parse.FnRef: true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "%s is not an attribute of %s", attributeID, resource.Type)}
}
Пример #2
0
func validateSelectArray(builtin parse.IntrinsicFunction, array interface{}, ctx PropertyContext) reporting.Reports {
	if array == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Array cannot be null")}
	}

	switch t := array.(type) {
	case []interface{}:
		reports := make(reporting.Reports, 0, 10)
		for i, item := range t {
			if item == nil {
				reports = append(reports, reporting.NewFailure(PropertyContextAdd(ctx, strconv.Itoa(i)), "Array item cannot be null"))
			}
		}
		return reporting.Safe(reports)
	case parse.IntrinsicFunction:
		arrayType := Schema{Type: Multiple(ValueString)}
		_, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, arrayType), SupportedFunctions{
			parse.FnRef:       true,
			parse.FnFindInMap: true,
			parse.FnGetAtt:    true,
			parse.FnGetAZs:    true,
			parse.FnIf:        true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid value for array %s", array)}
}
Пример #3
0
func validateGetAtt(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnGetAtt, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnGetAtt)]
	args, ok := value.([]interface{})
	if !ok || args == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::GetAtt\" key: %T", value)}
	}

	if len(args) != 2 {
		return reporting.Reports{reporting.NewFailure(ctx, "GetAtt has incorrect number of arguments (expected: 2, actual: %d)", len(args))}
	}

	reports := make(reporting.Reports, 0, 10)

	resourceID := args[0]
	attributeID := args[1]

	if errs := validateGetAttResourceID(builtin, resourceID, ctx); errs != nil {
		reports = append(reports, errs...)
	} else if errs := validateGetAttAttributeID(builtin, resourceID, attributeID, ctx); errs != nil {
		reports = append(reports, errs...)
	}

	return reporting.Safe(reports)
}
Пример #4
0
func validateEquals(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnEquals, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnEquals)]
	args, ok := value.([]interface{})
	if !ok || args == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::Equals\" key: %T", value)}
	}

	if len(args) != 2 {
		return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of arguments (expected: 2, actual: %d)", len(args))}
	}

	reports := make(reporting.Reports, 0, 10)

	left := args[0]
	if errs := validateEqualsItem(left, PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "Value-1")); errs != nil {
		reports = append(reports, errs...)
	}

	right := args[1]
	if errs := validateEqualsItem(right, PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "Value-2")); errs != nil {
		reports = append(reports, errs...)
	}

	return reporting.Safe(reports)
}
Пример #5
0
func validateAndOr(key parse.IntrinsicFunctionSignature, builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	value, found := builtin.UnderlyingMap[string(key)]
	if !found || value == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Missing \"%s\" key", key)}
	}

	args, ok := value.([]interface{})
	if !ok || args == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"%s\" key: %T", key, value)}
	}

	if len(builtin.UnderlyingMap) > 1 {
		return reporting.Reports{reporting.NewFailure(ctx, "Unexpected extra keys: %s", keysExcept(builtin.UnderlyingMap, string(key)))}
	}

	if len(args) < 2 || len(args) > 10 {
		return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of conditions (expected between 2 and 10, actual: %d)", len(args))}
	}

	reports := make(reporting.Reports, 0, 10)

	for i, condition := range args {
		if errs := validateAndOrItem(condition, PropertyContextAdd(ctx, strconv.Itoa(i))); errs != nil {
			reports = append(reports, errs...)
		}
	}

	return reporting.Safe(reports)
}
Пример #6
0
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-conditions.html#d0e97711
func validateIf(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnIf, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnIf)]
	args, ok := value.([]interface{})
	if !ok || args == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::If\" key: %T", value)}
	}

	if len(args) != 3 {
		return reporting.Reports{reporting.NewFailure(ctx, "Incorrect number of arguments [condition_name, true_value, false_value]", len(args))}
	}

	reports := make(reporting.Reports, 0, 10)

	conditionName := args[0]
	trueValue := args[1]
	falseValue := args[2]

	if errs := validateIfConditionName(conditionName, PropertyContextAdd(ctx, "ConditionName")); errs != nil {
		reports = append(reports, errs...)
	}

	if errs := validateIfValue(trueValue, PropertyContextAdd(ctx, "TrueValue")); errs != nil {
		reports = append(reports, errs...)
	}

	if errs := validateIfValue(falseValue, PropertyContextAdd(ctx, "FalseValue")); errs != nil {
		reports = append(reports, errs...)
	}

	return reporting.Safe(reports)
}
Пример #7
0
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html
func validateJoin(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnJoin, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnJoin)]

	items, ok := value.([]interface{})
	if !ok || items == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid \"Fn::Join\" key: %s", items)}
	}

	if len(items) != 2 {
		return reporting.Reports{reporting.NewFailure(ctx, "Join has incorrect number of arguments (expected: 2, actual: %d)", len(items))}
	}

	reports := make(reporting.Reports, 0, 10)
	delimiter := items[0]
	values := items[1]

	if errs := validateJoinDelimiter(builtin, delimiter, ctx); errs != nil {
		reports = append(reports, errs...)
	}

	if errs := validateJoinList(builtin, values, ctx); errs != nil {
		reports = append(reports, errs...)
	}

	return reporting.Safe(reports)
}
Пример #8
0
func validateIndexNumericalValue(index float64, array interface{}, ctx PropertyContext) reporting.Reports {
	if index < 0 {
		return reporting.Reports{reporting.NewFailure(ctx, "Index cannot less than zero")}
	} else if arr, ok := array.([]interface{}); ok && int(index) >= len(arr) {
		return reporting.Reports{reporting.NewFailure(ctx, "Index cannot greater than array length")}
	}

	return nil
}
Пример #9
0
func validateIfConditionName(value interface{}, ctx PropertyContext) reporting.Reports {
	if name, ok := value.(string); ok {
		if _, found := ctx.Template().Conditions[name]; found {
			return nil
		}

		return reporting.Reports{reporting.NewFailure(ctx, "%s is not defined in the Conditions of the template", name)}
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid condition %s", value)}
}
Пример #10
0
// validateIntrinsicFunctionBasicCriteria checks the essentials of an intrinsic
// function call:
//
//   1. Does it have a key matching the IF's name
//   2. Does that key have a value
//   3. No unexpected properties alongside the IF key
//
// Validation will fail if any of those criteria don't pass.
func validateIntrinsicFunctionBasicCriteria(key parse.IntrinsicFunctionSignature, builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	value, found := builtin.UnderlyingMap[string(key)]
	if !found || value == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Missing \"%s\" key", key)}
	}

	if len(builtin.UnderlyingMap) > 1 {
		return reporting.Reports{reporting.NewFailure(ctx, "Unexpected extra keys: %s", keysExcept(builtin.UnderlyingMap, string(key)))}
	}

	return nil
}
Пример #11
0
func validateGetAttResourceID(builtin parse.IntrinsicFunction, resourceID interface{}, ctx PropertyContext) reporting.Reports {
	switch t := resourceID.(type) {
	case string:
		if _, found := ctx.Template().Resources[t]; found {
			return nil
		}

		return reporting.Reports{reporting.NewFailure(ctx, "GetAtt %s is not a resource", t)}
	}

	return reporting.Reports{reporting.NewFailure(ctx, "GetAtt %s is not a valid resource name", resourceID)}
}
Пример #12
0
func NewUnrecognisedResource(awsType string) Resource {
	return Resource{
		ValidateFunc: func(ctx ResourceContext) (reporting.ValidateResult, reporting.Reports) {
			return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Unrecognised resource %s", awsType)}
		},
	}
}
Пример #13
0
func validateJoinDelimiter(builtin parse.IntrinsicFunction, delimiter interface{}, ctx PropertyContext) reporting.Reports {
	if _, ok := delimiter.(string); !ok {
		return reporting.Reports{reporting.NewFailure(ctx, "\"%s\" is not a valid delimiter", delimiter)}
	}

	return nil
}
Пример #14
0
func validateJoinList(builtin parse.IntrinsicFunction, values interface{}, ctx PropertyContext) reporting.Reports {
	valuesCtx := PropertyContextAdd(ctx, "Values")

	switch parts := values.(type) {
	case []interface{}:
		reports := make(reporting.Reports, 0, 10)
		valueType := Schema{Type: ValueString}
		for i, part := range parts {
			if errs := validateJoinListValue(part, PropertyContextAdd(NewPropertyContext(valuesCtx, valueType), strconv.Itoa(i))); errs != nil {
				reports = append(reports, errs...)
			}
		}

		return reporting.Safe(reports)
	case parse.IntrinsicFunction:
		listType := Schema{Type: Multiple(ValueString)}
		_, errs := ValidateIntrinsicFunctions(parts, NewPropertyContext(valuesCtx, listType), SupportedFunctions{
			parse.FnBase64:    true,
			parse.FnFindInMap: true,
			parse.FnGetAtt:    true,
			parse.FnGetAZs:    true,
			parse.FnIf:        true,
			parse.FnJoin:      true,
			parse.FnSelect:    true,
			parse.FnRef:       true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Join items are not valid: %s", values)}
}
Пример #15
0
func (p Properties) Validate(ctx ResourceContext) reporting.Reports {
	failures := make(reporting.Reports, 0, len(p)*2)
	visited := make(map[string]bool)

	self := ctx.CurrentResource()

	for key, schema := range p {
		visited[key] = true
		value, _ := self.PropertyValue(key)
		keyCtx := ResourceContextAdd(ctx, key)

		// Validate conflicting properties
		if value != nil && schema.Conflicts != nil && schema.Conflicts.Pass(self) {
			failures = append(failures, reporting.NewFailure(keyCtx, "Conflict: %s", schema.Conflicts.Describe(self)))
		}

		// Validate Required
		if value == nil && schema.Required != nil && schema.Required.Pass(self) {
			failures = append(failures, reporting.NewFailure(keyCtx, "%s is required because %s", key, schema.Required.Describe(self)))
		}

		// Validate Deprecated
		if value != nil && schema.Deprecated != nil {
			failures = append(failures, reporting.NewWarning(keyCtx, schema.Deprecated.Describe()))
			continue
		}

		// assuming the above either failed and logged some failures, or passed and
		// we can safely skip over a nil property
		if value == nil {
			continue
		}

		if _, errs := schema.Validate(value, keyCtx); errs != nil {
			failures = append(failures, errs...)
		}
	}

	// Reject any properties we weren't expecting
	for _, key := range self.Properties() {
		if !visited[key] {
			failures = append(failures, reporting.NewFailure(ResourceContextAdd(ctx, key), "%s is not a property of %s", key, self.AwsType()))
		}
	}

	return reporting.Safe(failures)
}
Пример #16
0
// validateCondition validates the inline { "Condition": "X" } structure. It
// isn't technically a condition itself, acting more like a Ref.
func validateCondition(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnCondition, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnCondition)]
	condition, ok := value.(string)
	if !ok || condition == "" {
		return reporting.Reports{reporting.NewFailure(ctx, `Invalid type for "Condition" key: %T`, value)}
	}

	if _, found := ctx.Template().Conditions[condition]; !found {
		return reporting.Reports{reporting.NewFailure(ctx, "%s is not defined in the Conditions of the template", condition)}
	}

	return nil
}
Пример #17
0
func SingleValueValidate(expected interface{}) ValidateFunc {
	return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
		if value != expected {
			return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Value must be %d but is %d", expected, value)}
		}

		return reporting.ValidateOK, nil
	}
}
Пример #18
0
func automaticFailoverEnabledValidation(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
	if version, found := ctx.CurrentResource().PropertyValueOrDefault("EngineVersion"); found {
		if versionNumber, err := strconv.ParseFloat(version.(string), 64); err == nil {
			if versionNumber < 2.8 {
				return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "EngineVersion must be 2.8 or higher for Automatic Failover")}
			}
		}
	}

	if nodeType, found := ctx.CurrentResource().PropertyValueOrDefault("CacheNodeType"); found {
		split := strings.Split(nodeType.(string), ".")
		if split[1] == "t1" || split[1] == "t2" {
			return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "CacheNodeType must not be T1 or T2 Automatic Failover")}
		}
	}

	return reporting.ValidateOK, nil
}
Пример #19
0
func validateFindInMapMapName(builtin parse.IntrinsicFunction, mapName interface{}, ctx PropertyContext) reporting.Reports {
	if mapName == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Cannot be null")}
	}

	switch t := mapName.(type) {
	case string:
		// TODO: validate actual map exists
		return nil
	case parse.IntrinsicFunction:
		_, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{
			parse.FnFindInMap: true,
			parse.FnRef:       true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid MapName: %v", mapName)}
}
Пример #20
0
func NumberOptions(numbers ...float64) ValidateFunc {
	return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
		for _, n := range numbers {
			if n == value.(float64) {
				return reporting.ValidateOK, nil
			}
		}
		return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Number must be one of %v", numbers)}
	}
}
Пример #21
0
func validateFindInMapSecondLevelKey(builtin parse.IntrinsicFunction, mapName, topLevelKey, secondLevelKey interface{}, ctx PropertyContext) reporting.Reports {
	if secondLevelKey == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Cannot be null")}
	}

	switch t := secondLevelKey.(type) {
	case string:
		// TODO: validate actual map second level key exists, if possible
		return nil
	case parse.IntrinsicFunction:
		_, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{
			parse.FnFindInMap: true,
			parse.FnRef:       true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid SecondLevelKey: %v", secondLevelKey)}
}
Пример #22
0
func validateAndOrItem(value interface{}, ctx PropertyContext) reporting.Reports {
	if value == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Value is null")}
	}

	switch t := value.(type) {
	case parse.IntrinsicFunction:
		_, errs := ValidateIntrinsicFunctions(t, ctx, SupportedFunctions{
			parse.FnAnd:       true,
			parse.FnCondition: true,
			parse.FnEquals:    true,
			parse.FnIf:        true,
			parse.FnNot:       true,
			parse.FnOr:        true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid condition: %#v", value)}
}
Пример #23
0
func azModeValidate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
	if str, ok := value.(string); ok {
		if availabilityZones, ok := ctx.CurrentResource().PropertyValueOrDefault("PreferredAvailabilityZones"); ok {
			if str == "cross-az" && len(availabilityZones.([]interface{})) < 2 {
				return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Cross-AZ clusters must have multiple preferred availability zones")}
			}
		}
	}

	return reporting.ValidateOK, nil
}
Пример #24
0
func validateSelectIndex(builtin parse.IntrinsicFunction, index interface{}, array interface{}, ctx PropertyContext) reporting.Reports {
	if index == nil {
		return reporting.Reports{reporting.NewFailure(ctx, "Index cannot be null")}
	}

	switch t := index.(type) {
	case string:
		return reporting.Reports{reporting.NewWarning(ctx, "Wrong type for index %T (can't ensure validity of index)", index)}
	case float64:
		return validateIndexNumericalValue(t, array, ctx)
	case parse.IntrinsicFunction:
		indexType := Schema{Type: ValueNumber}
		_, errs := ValidateIntrinsicFunctions(t, NewPropertyContext(ctx, indexType), SupportedFunctions{
			parse.FnRef:       true,
			parse.FnFindInMap: true,
		})
		return errs
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid value for index %#v", index)}
}
Пример #25
0
// TODO: fixme
func FixedArrayValidate(options ...[]string) ValidateFunc {
	return func(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
		for _, option := range options {
			if match(option, value.([]interface{})) {
				return reporting.ValidateOK, nil
			}
		}

		// TODO: this should be []TypeString but we can't specify that with this method
		return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Invalid list value: %s, expected one of [%s]", value, options)}
	}
}
Пример #26
0
// TODO: This is all a bit hairy. We shouldn't need to be creating the
// 			 TemplateNestedResource here, ideally `self` should already refer to
//			 one and value should already be a map[string]inteface{}
func (res NestedResource) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
	if values, ok := value.(map[string]interface{}); ok {
		property := ctx.Property()
		tnr := parse.NewTemplateResource(property.Type.Describe(), values)

		nestedResourceContext := NewResourceContext(ctx, ResourceWithDefinition{tnr, property.Type})
		failures := res.Properties.Validate(nestedResourceContext)

		return reporting.ValidateOK, reporting.Safe(failures)
	}

	return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Invalid type %T for nested resource %s", value, res.Description)}
}
Пример #27
0
func (id resourceID) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) {
	if result, errs := ValueString.Validate(value, ctx); result == reporting.ValidateAbort || errs != nil {
		return reporting.ValidateOK, errs
	}

	if !id.regex.MatchString(value.(string)) {
		return reporting.ValidateOK, reporting.Reports{
			reporting.NewFailure(ctx, "Value '%s' is not a valid %s, format: %s", value, id.description, id.example),
		}
	}

	return reporting.ValidateOK, nil
}
Пример #28
0
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-select.html
func validateSelect(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnSelect, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnSelect)]
	switch t := value.(type) {
	case []interface{}:
		return validateSelectParameters(builtin, t, ctx)
	}

	return reporting.Reports{reporting.NewFailure(ctx, "Invalid \"Fn::Select\" key: %s", value)}
}
Пример #29
0
func outputValidate(o parse.Output, ctx Context) (reporting.ValidateResult, reporting.Reports) {
	if o.Description != nil {
		if _, ok := o.Description.(string); !ok {
			return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ContextAdd(ctx, "Description"), "Expected a string")}
		}
	}

	outputContext := NewResourceContext(ctx, emptyCurrentResource{})
	if _, errs := outputSchema.Validate(o.Value, ResourceContextAdd(outputContext, "Value")); errs != nil {
		return reporting.ValidateOK, errs
	}

	return reporting.ValidateOK, nil
}
Пример #30
0
// see: http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-findinmap.html
func validateFindInMap(builtin parse.IntrinsicFunction, ctx PropertyContext) reporting.Reports {
	if errs := validateIntrinsicFunctionBasicCriteria(parse.FnFindInMap, builtin, ctx); errs != nil {
		return errs
	}

	value := builtin.UnderlyingMap[string(parse.FnFindInMap)]
	args, ok := value.([]interface{})
	if !ok {
		return reporting.Reports{reporting.NewFailure(ctx, "Invalid type for \"Fn::FindInMap\" key: %T", value)}
	}

	if len(args) != 3 {
		return reporting.Reports{reporting.NewFailure(ctx, "Options has wrong number of items, expected: 3, actual: %d", len(args))}
	}

	reports := make(reporting.Reports, 0, 10)

	mapName := args[0]
	mapNameCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "MapName")
	if errs := validateFindInMapMapName(builtin, mapName, mapNameCtx); errs != nil {
		reports = append(reports, errs...)
	}

	topLevelKey := args[1]
	topLevelKeyCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "TopLevelKey")
	if errs := validateFindInMapTopLevelKey(builtin, mapName, topLevelKey, topLevelKeyCtx); errs != nil {
		reports = append(reports, errs...)
	}

	secondLevelKey := args[2]
	secondLevelKeyCtx := PropertyContextAdd(NewPropertyContext(ctx, Schema{Type: ValueString}), "SecondLevelKey")
	if errs := validateFindInMapSecondLevelKey(builtin, mapName, topLevelKey, secondLevelKey, secondLevelKeyCtx); errs != nil {
		reports = append(reports, errs...)
	}

	return reporting.Safe(reports)
}