floci-io/floci · error · AwsException

ValidationException

ValidationException

Error message

Dimensions filter requires a Key.

What it means

A Cost Explorer Filter of type Dimensions must carry a Key naming which dimension to match (SERVICE, REGION, ...). Floci evaluates filters while scanning usage lines and throws as soon as it hits a Dimensions node without a Key.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/ce/FilterExpressionEvaluator.java:71

        if (dimensions.isObject() && !dimensions.isEmpty()) {
            return matchDimensions(dimensions, line);
        }
        JsonNode tags = expression.path("Tags");
        if (tags.isObject() && !tags.isEmpty()) {
            return matchTags(tags, line);
        }
        JsonNode costCategories = expression.path("CostCategories");
        if (costCategories.isObject() && !costCategories.isEmpty()) {
            // No cost categories defined — never matches.
            return false;
        }
        return true;
    }

    private static boolean matchDimensions(JsonNode node, UsageLine line) {
        String key = node.path("Key").asText(null);
        if (key == null || key.isEmpty()) {
            throw new AwsException("ValidationException",
                    "Dimensions filter requires a Key.", 400);
        }
        Set<String> values = readValues(node.path("Values"));
        Set<String> matchOptions = readValues(node.path("MatchOptions"));
        boolean caseInsensitive = matchOptions.contains("CASE_INSENSITIVE");
        String actual = dimensionValue(key, line);
        return valueMatches(actual, values, caseInsensitive);
    }

    private static boolean matchTags(JsonNode node, UsageLine line) {
        String key = node.path("Key").asText(null);
        if (key == null || key.isEmpty()) {
            throw new AwsException("ValidationException",
                    "Tags filter requires a Key.", 400);
        }
        Set<String> values = readValues(node.path("Values"));
        Set<String> matchOptions = readValues(node.path("MatchOptions"));
        boolean caseInsensitive = matchOptions.contains("CASE_INSENSITIVE");

View on GitHub (pinned to 62ff490619)

Solutions

  1. Add Key to the Dimensions filter: {"Dimensions": {"Key": "SERVICE", "Values": ["Amazon EC2"]}}
  2. Use the SDK's builder (DimensionValues.builder().key(Dimension.SERVICE)) which makes the required shape obvious
  3. Validate filter JSON against the CE model before sending

Example fix

// before
Filter.builder().dimensions(d -> d.values("Amazon EC2")).build();
// after
Filter.builder().dimensions(d -> d.key(Dimension.SERVICE).values("Amazon EC2")).build();
Defensive patterns

Strategy: type-guard

Validate before calling

if (filter.dimensions() != null && (filter.dimensions().key() == null || filter.dimensions().key().isBlank())) {
    throw new IllegalArgumentException("Dimensions filter requires Key");
}

Type guard

function isDimensionFilter(f: unknown): f is { Dimensions: { Key: string; Values: string[] } } {
  const d = (f as any)?.Dimensions;
  return !!d && typeof d.Key === "string" && d.Key.length > 0 && Array.isArray(d.Values);
}

Prevention

When it happens

Trigger: GetCostAndUsage / GetDimensionValues / GetCostAndUsageWithResources with Filter: {Dimensions: {Values: ["Amazon EC2"]}} — Values present but Key missing or empty.

Common situations: Hand-built filter JSON copied from an example that omitted Key; dynamically assembled filters where the key field is optional in the UI model but required by the API.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/0321cf02f2d1bbc8. Report an issue: GitHub.