{"record":{"id":"07a216843eb85633","repo":"floci-io/floci","slug":"validationexception-07a216","errorCode":"ValidationException","errorMessage":"1 validation error detected: Value at 'Metrics' failed to satisfy constraint: Member must contain at least 1 element.","messagePattern":"1 validation error detected: Value at 'Metrics' failed to satisfy constraint: Member must contain at least 1 element\\.","errorType":"error_code","errorClass":"AwsException","httpStatus":400,"severity":"error","filePath":"src/main/java/io/github/hectorvent/floci/services/ce/CostExplorerService.java","lineNumber":87,"sourceCode":"    /**\n     * Returns the same shape as {@link #getCostAndUsage} for now. Floci's\n     * resource-level data is already surfaced through the {@code RESOURCE_ID}\n     * dimension, so a caller that wants resource breakdown can issue\n     * {@code GetCostAndUsage} with {@code GroupBy=[{Type:DIMENSION,Key:RESOURCE_ID}]}.\n     * A separate emit path that returns inline resource attributions can land\n     * later if a consumer needs it.\n     */\n    public ObjectNode getCostAndUsageWithResources(JsonNode request, String defaultRegion) {\n        return runCostAndUsage(request, defaultRegion);\n    }\n\n    private ObjectNode runCostAndUsage(JsonNode request, String defaultRegion) {\n        TimeWindow window = parseTimeWindow(request);\n        TimeBucketing.Granularity granularity = TimeBucketing.parseGranularity(\n                request.path(\"Granularity\").asText(null));\n        Set<String> metrics = GroupAggregator.parseMetrics(request.path(\"Metrics\"));\n        if (metrics.isEmpty()) {\n            throw new AwsException(\"ValidationException\",\n                    \"1 validation error detected: Value at 'Metrics' failed to satisfy constraint: Member must contain at least 1 element.\", 400);\n        }\n        List<GroupAggregator.GroupBy> groupBys = GroupAggregator.parseGroupBy(request.path(\"GroupBy\"));\n        JsonNode filter = request.has(\"Filter\") ? request.get(\"Filter\") : null;\n\n        List<UsageLine> all = collectLines(window.start(), window.end(), defaultRegion);\n        // Apply filter once across the full window so the same set is reused\n        // per bucket (lines are emitted per request scope, no cross-bucket leakage).\n        List<UsageLine> filtered = new ArrayList<>();\n        for (UsageLine line : all) {\n            if (FilterExpressionEvaluator.matches(filter, line)) {\n                filtered.add(line);\n            }\n        }\n\n        CostSynthesizer synthesizer = new CostSynthesizer(rateLookup);\n        if (monthlyCreditUsd > 0) {\n            CreditLineEmitter creditEmitter = new CreditLineEmitter(monthlyCreditUsd);","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/floci-io/floci/blob/62ff490619e7bd3554597c28c704081b4c15add5/src/main/java/io/github/hectorvent/floci/services/ce/CostExplorerService.java#L69-L105","documentation":"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.","triggerScenarios":"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.","commonSituations":"Building the request conditionally so the metrics list ends up empty; porting scripts that relied on a server-side default metric.","solutions":["Add at least one metric: Metrics: [\"UnblendedCost\"] (and typically also \"UsageQuantity\")","Default the metrics list client-side when user selection is empty","Verify with the AWS CLI first: aws ce get-cost-and-usage --metrics UnblendedCost ..."],"exampleFix":"// before\nGetCostAndUsageRequest.builder().timePeriod(tp).granularity(DAILY).build();\n// after\nGetCostAndUsageRequest.builder().timePeriod(tp).granularity(DAILY)\n    .metrics(Metric.UNBLENDED_COST, Metric.USAGE_QUANTITY).build();","handlingStrategy":"validation","validationCode":"if (metrics == null || metrics.isEmpty()) metrics = List.of(Metric.UNBLENDED_COST);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Default to UnblendedCost client-side when the caller passes no metrics","Copy the request shape from aws ce get-cost-and-usage examples"],"tags":["cost-explorer","validation","aws-compat","floci"],"backgroundTag":null,"analyzedSha":"62ff490619e7bd3554597c28c704081b4c15add5","analyzedAt":"2026-08-14T14:25:23.764Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}