task: using a beanparam for client listing options (#49074)

* task: using a beanparam for client listing options

closes: #48650

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* just adding fluent methods

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins
2026-05-22 13:54:20 -04:00
committed by GitHub
parent 2ffb8b676e
commit 26ef6d1b08
5 changed files with 39 additions and 12 deletions
+1 -2
View File
@@ -181,8 +181,7 @@ paths:
- Clients (v2)
parameters:
- description: "Set of fields to include in the response. Must be top-level\
\ fields on one of the client types. If omitted or empty, all fields will\
\ be populated."
\ fields. If omitted or empty, all fields will be populated."
name: fields
in: query
schema:
@@ -0,0 +1,28 @@
package org.keycloak.admin.api;
import java.util.Set;
import jakarta.ws.rs.QueryParam;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
public class ListOptions {
@Parameter(description = "Set of fields to include in the response. Must be top-level fields. If omitted or empty, all fields will be populated.")
@QueryParam("fields")
protected Set<String> fields;
public ListOptions fields(Set<String> fields) {
this.setFields(fields);
return this;
}
public Set<String> getFields() {
return fields;
}
public void setFields(Set<String> fields) {
this.fields = fields;
}
}
@@ -1,19 +1,19 @@
package org.keycloak.admin.api.client;
import java.util.Set;
import java.util.stream.Stream;
import jakarta.validation.Valid;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.api.ListOptions;
import org.keycloak.common.constants.KeycloakOpenAPI;
import org.keycloak.representations.admin.v2.BaseClientRepresentation;
@@ -22,7 +22,6 @@ import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.parameters.Parameter;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;
@@ -38,10 +37,10 @@ public interface ClientsApi {
@APIResponses(value = {
@APIResponse(responseCode = "200", content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = BaseClientRepresentation.class)))
})
Stream<BaseClientRepresentation> getClients(@Parameter(description = "Set of fields to include in the response. Must be top-level fields on one of the client types. If omitted or empty, all fields will be populated.") @QueryParam("fields") Set<String> fields);
Stream<BaseClientRepresentation> getClients(@BeanParam ListOptions params);
default Stream<BaseClientRepresentation> getClients() {
return getClients(Set.of());
return getClients(new ListOptions());
}
@POST
@@ -1,6 +1,5 @@
package org.keycloak.rest.admin.api.client;
import java.util.Set;
import java.util.stream.Stream;
import jakarta.annotation.Nonnull;
@@ -11,6 +10,7 @@ import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Response;
import org.keycloak.admin.api.ListOptions;
import org.keycloak.admin.api.client.ClientApi;
import org.keycloak.admin.api.client.ClientsApi;
import org.keycloak.models.KeycloakSession;
@@ -38,8 +38,8 @@ public class DefaultClientsApi implements ClientsApi {
}
@Override
public Stream<BaseClientRepresentation> getClients(Set<String> fields) {
return clientService.getClients(realm, new ClientProjectionOptions(fields), null, null);
public Stream<BaseClientRepresentation> getClients(ListOptions params) {
return clientService.getClients(realm, new ClientProjectionOptions(params.getFields()), null, null);
}
@POST
@@ -30,6 +30,7 @@ import jakarta.ws.rs.NotFoundException;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.core.MediaType;
import org.keycloak.admin.api.ListOptions;
import org.keycloak.admin.api.PatchTypeNames;
import org.keycloak.admin.client.Keycloak;
import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator;
@@ -347,7 +348,7 @@ public class ClientApiV2Test extends AbstractClientApiV2Test{
assertThat(samlClient.getFrontChannelLogout(), is(false));
// test projecting only id and protocol
try (Stream<BaseClientRepresentation> baseClientRepresentationStream = getClientsApi().getClients(Set.of("clientId", "protocol"))) {
try (Stream<BaseClientRepresentation> baseClientRepresentationStream = getClientsApi().getClients(new ListOptions().fields(Set.of("clientId", "protocol")))) {
List<BaseClientRepresentation> clients = baseClientRepresentationStream.toList();
for (BaseClientRepresentation client : clients) {
BaseClientRepresentation toCompare = null;
@@ -364,7 +365,7 @@ public class ClientApiV2Test extends AbstractClientApiV2Test{
@Test
public void invalidFieldProjection() {
BadRequestException e = assertThrows(BadRequestException.class, () -> getClientsApi().getClients(Set.of("unknown!")));
BadRequestException e = assertThrows(BadRequestException.class, () -> getClientsApi().getClients(new ListOptions().fields(Set.of("unknown!"))));
assertEquals("{\"error\":\"unknown! is an unknown field\"}", e.getResponse().readEntity(String.class));
}