floci-io/floci · error · AwsException

ValidationException

ValidationException

Error message

1 validation error detected: Value at 'Metrics' failed to satisfy constraint: Member must contain at least 1 element.

What it means

Cost Explorer GetCostAndUsage/GetCostAndUsageWithResources requires at least one metric (e.g. UnblendedCost, UsageQuantity). Floci replicates AWS's exact validation message: 'Metrics' must contain at least 1 element.

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/ce/CostExplorerService.java:87

    /**
     * Returns the same shape as {@link #getCostAndUsage} for now. Floci's
     * resource-level data is already surfaced through the {@code RESOURCE_ID}
     * dimension, so a caller that wants resource breakdown can issue
     * {@code GetCostAndUsage} with {@code GroupBy=[{Type:DIMENSION,Key:RESOURCE_ID}]}.
     * A separate emit path that returns inline resource attributions can land
     * later if a consumer needs it.
     */
    public ObjectNode getCostAndUsageWithResources(JsonNode request, String defaultRegion) {
        return runCostAndUsage(request, defaultRegion);
    }

    private ObjectNode runCostAndUsage(JsonNode request, String defaultRegion) {
        TimeWindow window = parseTimeWindow(request);
        TimeBucketing.Granularity granularity = TimeBucketing.parseGranularity(
                request.path("Granularity").asText(null));
        Set<String> metrics = GroupAggregator.parseMetrics(request.path("Metrics"));
        if (metrics.isEmpty()) {
            throw new AwsException("ValidationException",
                    "1 validation error detected: Value at 'Metrics' failed to satisfy constraint: Member must contain at least 1 element.", 400);
        }
        List<GroupAggregator.GroupBy> groupBys = GroupAggregator.parseGroupBy(request.path("GroupBy"));
        JsonNode filter = request.has("Filter") ? request.get("Filter") : null;

        List<UsageLine> all = collectLines(window.start(), window.end(), defaultRegion);
        // Apply filter once across the full window so the same set is reused
        // per bucket (lines are emitted per request scope, no cross-bucket leakage).
        List<UsageLine> filtered = new ArrayList<>();
        for (UsageLine line : all) {
            if (FilterExpressionEvaluator.matches(filter, line)) {
                filtered.add(line);
            }
        }

        CostSynthesizer synthesizer = new CostSynthesizer(rateLookup);
        if (monthlyCreditUsd > 0) {
            CreditLineEmitter creditEmitter = new CreditLineEmitter(monthlyCreditUsd);

View on GitHub (pinned to 62ff490619)

Solutions

  1. Add at least one metric: Metrics: ["UnblendedCost"] (and typically also "UsageQuantity")
  2. Default the metrics list client-side when user selection is empty
  3. Verify with the AWS CLI first: aws ce get-cost-and-usage --metrics UnblendedCost ...

Example fix

// before
GetCostAndUsageRequest.builder().timePeriod(tp).granularity(DAILY).build();
// after
GetCostAndUsageRequest.builder().timePeriod(tp).granularity(DAILY)
    .metrics(Metric.UNBLENDED_COST, Metric.USAGE_QUANTITY).build();
Defensive patterns

Strategy: validation

Validate before calling

if (metrics == null || metrics.isEmpty()) metrics = List.of(Metric.UNBLENDED_COST);

Prevention

When it happens

Trigger: Calling getCostAndUsage with Metrics absent, an empty array, or a non-array (null/empty string) — GroupAggregator.parseMetrics returns an empty set and the guard fires.

Common situations: Building the request conditionally so the metrics list ends up empty; porting scripts that relied on a server-side default metric.

Related errors


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