Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import com.openframe.sdk.fleetmdm.exception.FleetMdmApiException;
import com.openframe.sdk.fleetmdm.exception.FleetMdmException;
import com.openframe.sdk.fleetmdm.model.Host;
Expand Down Expand Up @@ -410,9 +411,8 @@ public List<Policy> listPolicies() {
try {
HttpResponse<String> response = sendRequest(POLICIES_URL, "GET", null);
checkResponse(response, "list Fleet policies");
return MAPPER.convertValue(
requireNode(response.body(), "policies"),
MAPPER.getTypeFactory().constructCollectionType(List.class, Policy.class));
String body = response.body();
return parseList(body, "policies", Policy.class);
} catch (FleetMdmApiException e) {
throw e;
} catch (Exception e) {
Expand Down Expand Up @@ -472,9 +472,8 @@ public List<Query> listScheduledQueries() {
try {
HttpResponse<String> response = sendRequest(QUERIES_URL, "GET", null);
checkResponse(response, "list Fleet scheduled queries");
return MAPPER.convertValue(
requireNode(response.body(), "queries"),
MAPPER.getTypeFactory().constructCollectionType(List.class, Query.class));
String body = response.body();
return parseList(body, "queries", Query.class);
} catch (FleetMdmApiException e) {
throw e;
} catch (Exception e) {
Expand Down Expand Up @@ -537,9 +536,8 @@ public CompletableFuture<List<Policy>> listPoliciesAsync() {
.thenApply(response -> {
checkResponse(response, "list Fleet policies");
try {
return MAPPER.convertValue(
requireNode(response.body(), "policies"),
MAPPER.getTypeFactory().constructCollectionType(List.class, Policy.class));
String body = response.body();
return parseList(body, "policies", Policy.class);
} catch (Exception e) {
throw new FleetMdmException("Failed to parse list policies response", e);
}
Expand Down Expand Up @@ -603,9 +601,8 @@ public CompletableFuture<List<Query>> listScheduledQueriesAsync() {
.thenApply(response -> {
checkResponse(response, "list Fleet scheduled queries");
try {
return MAPPER.convertValue(
requireNode(response.body(), "queries"),
MAPPER.getTypeFactory().constructCollectionType(List.class, Query.class));
String body = response.body();
return parseList(body, "queries", Query.class);
} catch (Exception e) {
throw new FleetMdmException("Failed to parse list scheduled queries response", e);
}
Expand Down Expand Up @@ -813,6 +810,16 @@ private static void checkResponse(HttpResponse<String> response, String action)
+ (body.isEmpty() ? "" : ": " + body), response.statusCode(), body);
}

private static <T> List<T> parseList(String responseBody, String fieldName, Class<T> itemType) throws Exception {
JsonNode root = MAPPER.readTree(responseBody);
JsonNode node = root.get(fieldName);
if (node == null || node.isNull()) {
return List.of();
}
CollectionType listType = MAPPER.getTypeFactory().constructCollectionType(List.class, itemType);
return MAPPER.convertValue(node, listType);
}

Comment on lines +813 to +822

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Fleet MDM API global policies list endpoint: does it omit or null the "policies" field for any reason other than an empty list?

💡 Result:

The Fleet MDM API endpoint for listing global policies (GET /api/v1/fleet/global/policies) typically returns a JSON object containing a policies field, which is an array of policy objects [1][2]. The policies field is generally present even when no policies are configured, in which case it is represented as an empty list ([]) rather than being omitted or null. While the API structure is consistent, there are specific circumstances in Fleet where policy-related fields are omitted or restricted based on licensing or endpoint context, though these differ from a simple null vs. empty list distinction: 1. Premium Licensing Restrictions: In some cases, Fleet limits access to policy information for non-premium users. For instance, in certain device-specific endpoints, policy information is omitted entirely from the response if the instance is not licensed for Fleet Premium, rather than being returned as empty or null [3]. 2. JSON Unmarshalling and Zero Values: Fleet's internal handling of JSON, particularly in PATCH requests, has historically dealt with nuances between null and omitted fields [4][5]. While this primarily affects how the server interprets client-provided payloads (e.g., whether a null value unsets an automation [4]), it highlights that the server's serialization logic for structs often defaults to specific patterns (like using custom unmarshalers for optional fields) that may influence how fields are emitted or expected [4][5]. In summary, for the global policies list endpoint, you should expect an empty array ([]) when no policies exist, rather than the field being omitted or null [1][2]. If you are encountering missing fields in other contexts, it is likely related to premium feature gating or specific structural definitions in the API's Go-based backend implementation [5][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the client code around parseList/checkResponse/usages.
if [ -f sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java ]; then
  wc -l sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java
  sed -n '740,900p' sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java | cat -n -v
  echo '--- parseList usages ---'
  rg -n "parseList|policies|queries|checkResponse" sdk/fleetmdm/src/main/java/com.openframe.sdk.fleetmdm.FleetMdmClient.java || true
else
  fd -a 'FleetMdmClient.java' .
fi

Repository: flamingo-stack/openframe-oss-lib

Length of output: 6382


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- package/files ---'
git ls-files | rg 'FleetMdmClient|fleetmdm|fleet' || true

echo '--- target file (if present) ---'
if [ -f sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java ]; then
  wc -l sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java
  echo '--- parseList and nearby API calls ---'
  sed -n '70,180p;800,900p' sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java | cat -n
  echo '--- usages/fields in FleetMdmClient.java ---'
  rg -n "parseList|policies|queries|GET|GETs|global/policies|/queries" sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java || true
fi

Repository: flamingo-stack/openframe-oss-lib

Length of output: 18689


🌐 Web query:

Fleet API list global policies JSON response policies field empty array

💡 Result:

If you receive a JSON response with an empty policies array (e.g., "policies": []) when querying the Fleet API, it indicates that the endpoint successfully returned a response, but no policies were found based on the parameters or the current state of your Fleet instance [1][2]. In the context of the Fleet Device Management API, this commonly occurs for the following reasons: 1. Absence of Data: The most straightforward reason is that there are no policies created in the scope you are querying (either global or within a specific team) [1]. 2. Filtering and Pagination: If you are using parameters such as query, page, or per_page, the filters may be excluding all available policies from the result set [2]. 3. Incorrect Endpoint: Ensure you are hitting the correct API route. Fleet has transitioned away from /global/ prefixes in its API paths [3]. For example, instead of /api/v1/fleet/global/policies, you should use /api/v1/fleet/policies [1][3]. If you are instead working with Elastic's Fleet (often used in the Elastic Stack), an empty array or missing fields may occur if the API request requires specific parameters—such as?full=true—to return populated data [4][5]. To troubleshoot: Verify your authentication and permissions to ensure your API token has sufficient access to view policies. Check the API documentation for your specific version of Fleet, as deprecated routes or parameter changes can sometimes cause unexpected empty responses [3][2]. If you are using the Fleet (FleetDM) CLI or API client, ensure you are not accidentally filtering the results [1].

Citations:


Do not treat a missing Fleet list field as an empty result.

parseList returns List.of() when policies or queries is absent or null. Fleet policy/query responses should emit an empty array when no items exist. If Fleet returns a broken or partial 2xx response, mapping it to an empty list can make the client report “no policies” instead of surfacing the API response failure. Require a non-null array field for these list endpoints, or make the missing-field case return an explicit API error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@sdk/fleetmdm/src/main/java/com/openframe/sdk/fleetmdm/FleetMdmClient.java`
around lines 813 - 822, The parseList method must not silently convert a missing
or null Fleet list field into an empty result. Require the named field to be a
non-null array before conversion, and throw or propagate an explicit API/parsing
error when it is absent, null, or invalid; preserve empty-list behavior only
when the response contains an actual empty array.

private static JsonNode requireNode(String responseBody, String fieldName) throws Exception {
JsonNode root = MAPPER.readTree(responseBody);
JsonNode node = root.get(fieldName);
Expand Down
Loading