Repository navigation
add v3 feature flags API. Fix #1388 #1389
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Lokowandtg
wants to merge
8
commits into
cloudfoundry:5.x.x
Choose a base branch
from
Lokowandtg:5.x.x_featureFlags
base: 5.x.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+1,240
−2
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
0718d10
add v3 feature flags API. Fix #1388
Lokowandtg 285d449
fix build with java 8
Lokowandtg f2c93fe
fix spotless findings.
Lokowandtg 67fb15a
fix unknown attribute 'links'
Lokowandtg 14154d2
Merge remote-tracking branch 'upstream/5.x.x' into 5.x.x_featureFlags
Lokowandtg 88ba312
Merge remote-tracking branch 'upstream/5.x.x' into 5.x.x_featureFlags
Lokowandtg 38e4e77
fix review findings
Lokowandtg 9ab9165
fix spotless fingings
Lokowandtg File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
.../src/main/java/org/cloudfoundry/reactor/client/v3/featureflags/ReactorFeatureFlagsV3.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * Copyright 2026 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.cloudfoundry.reactor.client.v3.featureflags; | ||
|
|
||
| import java.util.Map; | ||
| import org.cloudfoundry.client.v3.featureflags.FeatureFlagsV3; | ||
| import org.cloudfoundry.client.v3.featureflags.GetFeatureFlagRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.GetFeatureFlagResponse; | ||
| import org.cloudfoundry.client.v3.featureflags.ListFeatureFlagsRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.ListFeatureFlagsResponse; | ||
| import org.cloudfoundry.client.v3.featureflags.UpdateFeatureFlagRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.UpdateFeatureFlagResponse; | ||
| import org.cloudfoundry.reactor.ConnectionContext; | ||
| import org.cloudfoundry.reactor.TokenProvider; | ||
| import org.cloudfoundry.reactor.client.v3.AbstractClientV3Operations; | ||
| import reactor.core.publisher.Mono; | ||
|
|
||
| /** | ||
| * The Reactor-based implementation of {@link FeatureFlagsV3} | ||
| */ | ||
| public final class ReactorFeatureFlagsV3 extends AbstractClientV3Operations | ||
| implements FeatureFlagsV3 { | ||
|
|
||
| /** | ||
| * Creates an instance | ||
| * | ||
| * @param connectionContext the {@link ConnectionContext} to use when communicating with the server | ||
| * @param root the root URI of the server. Typically something like {@code https://api.run.pivotal.io}. | ||
| * @param tokenProvider the {@link TokenProvider} to use when communicating with the server | ||
| * @param requestTags map with custom http headers which will be added to web request | ||
| */ | ||
| public ReactorFeatureFlagsV3( | ||
| ConnectionContext connectionContext, | ||
| Mono<String> root, | ||
| TokenProvider tokenProvider, | ||
| Map<String, String> requestTags) { | ||
| super(connectionContext, root, tokenProvider, requestTags); | ||
| } | ||
|
|
||
| @Override | ||
| public Mono<GetFeatureFlagResponse> get(GetFeatureFlagRequest request) { | ||
| return get( | ||
| request, | ||
| GetFeatureFlagResponse.class, | ||
| builder -> builder.pathSegment("feature_flags", request.getName())) | ||
| .checkpoint(); | ||
| } | ||
|
|
||
| @Override | ||
| public Mono<ListFeatureFlagsResponse> list(ListFeatureFlagsRequest request) { | ||
| return get( | ||
| request, | ||
| ListFeatureFlagsResponse.class, | ||
| builder -> builder.pathSegment("feature_flags")) | ||
| .checkpoint(); | ||
| } | ||
|
|
||
| @Override | ||
| public Mono<UpdateFeatureFlagResponse> update(UpdateFeatureFlagRequest request) { | ||
| return patch( | ||
| request, | ||
| UpdateFeatureFlagResponse.class, | ||
| builder -> builder.pathSegment("feature_flags", request.getName())) | ||
| .checkpoint(); | ||
| } | ||
| } |
228 changes: 228 additions & 0 deletions
228
.../test/java/org/cloudfoundry/reactor/client/v3/featureflags/ReactorFeatureFlagsV3Test.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| /* | ||
| * Copyright 2026 the original author or authors. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
|
|
||
| package org.cloudfoundry.reactor.client.v3.featureflags; | ||
|
|
||
| import static io.netty.handler.codec.http.HttpMethod.GET; | ||
| import static io.netty.handler.codec.http.HttpMethod.PATCH; | ||
| import static io.netty.handler.codec.http.HttpResponseStatus.OK; | ||
|
|
||
| import java.time.Duration; | ||
| import java.util.Collections; | ||
| import org.cloudfoundry.client.v3.Link; | ||
| import org.cloudfoundry.client.v3.Pagination; | ||
| import org.cloudfoundry.client.v3.featureflags.GetFeatureFlagRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.GetFeatureFlagResponse; | ||
| import org.cloudfoundry.client.v3.featureflags.ListFeatureFlagsRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.ListFeatureFlagsResponse; | ||
| import org.cloudfoundry.client.v3.featureflags.UpdateFeatureFlagRequest; | ||
| import org.cloudfoundry.client.v3.featureflags.UpdateFeatureFlagResponse; | ||
| import org.cloudfoundry.reactor.InteractionContext; | ||
| import org.cloudfoundry.reactor.TestRequest; | ||
| import org.cloudfoundry.reactor.TestResponse; | ||
| import org.cloudfoundry.reactor.client.AbstractClientApiTest; | ||
| import org.junit.jupiter.api.Test; | ||
| import reactor.test.StepVerifier; | ||
|
|
||
| final class ReactorFeatureFlagsV3Test extends AbstractClientApiTest { | ||
|
|
||
| private static final String BASE_URL = "https://api.example.org/v3/feature_flags/"; | ||
| private static final String UPDATED_AT = "2020-03-10T15:49:32Z"; | ||
|
|
||
| private final ReactorFeatureFlagsV3 featureFlags = | ||
| new ReactorFeatureFlagsV3( | ||
| CONNECTION_CONTEXT, this.root, TOKEN_PROVIDER, Collections.emptyMap()); | ||
|
|
||
| @Test | ||
| void get() { | ||
| mockRequest( | ||
| InteractionContext.builder() | ||
| .request( | ||
| TestRequest.builder() | ||
| .method(GET) | ||
| .path("/feature_flags/app_scaling") | ||
| .build()) | ||
| .response( | ||
| TestResponse.builder() | ||
| .status(OK) | ||
| .payload( | ||
| "fixtures/client/v3/featureflags/GET_{feature_flag_name}_response.json") | ||
| .build()) | ||
| .build()); | ||
|
|
||
| this.featureFlags | ||
| .get(GetFeatureFlagRequest.builder().name("app_scaling").build()) | ||
| .as(StepVerifier::create) | ||
| .expectNext( | ||
| GetFeatureFlagResponse.builder() | ||
| .name("app_scaling") | ||
| .enabled(true) | ||
| .updatedAt(UPDATED_AT) | ||
| .link("self", selfLink("app_scaling")) | ||
| .build()) | ||
| .expectComplete() | ||
| .verify(Duration.ofSeconds(5)); | ||
| } | ||
|
|
||
| @Test | ||
| void list() { | ||
| mockRequest( | ||
| InteractionContext.builder() | ||
| .request(TestRequest.builder().method(GET).path("/feature_flags").build()) | ||
| .response( | ||
| TestResponse.builder() | ||
| .status(OK) | ||
| .payload( | ||
| "fixtures/client/v3/featureflags/GET_response.json") | ||
| .build()) | ||
| .build()); | ||
|
|
||
| this.featureFlags | ||
| .list(ListFeatureFlagsRequest.builder().build()) | ||
| .as(StepVerifier::create) | ||
| .expectNext( | ||
| ListFeatureFlagsResponse.builder() | ||
| .pagination( | ||
| Pagination.builder() | ||
| .totalResults(2) | ||
| .first( | ||
| Link.builder() | ||
| .href( | ||
| "https://api.example.org/v3/feature_flags?page=1&per_page=50") | ||
| .build()) | ||
| .last( | ||
| Link.builder() | ||
| .href( | ||
| "https://api.example.org/v3/feature_flags?page=1&per_page=50") | ||
| .build()) | ||
| .build()) | ||
| .resource( | ||
| GetFeatureFlagResponse.builder() | ||
| .name("app_scaling") | ||
| .enabled(true) | ||
| .updatedAt(UPDATED_AT) | ||
| .link("self", selfLink("app_scaling")) | ||
| .build()) | ||
| .resource( | ||
| GetFeatureFlagResponse.builder() | ||
| .name("user_org_creation") | ||
| .enabled(false) | ||
| .customErrorMessage("This feature is disabled") | ||
| .updatedAt(UPDATED_AT) | ||
| .link("self", selfLink("user_org_creation")) | ||
| .build()) | ||
| .build()) | ||
| .expectComplete() | ||
| .verify(Duration.ofSeconds(5)); | ||
| } | ||
|
|
||
| @Test | ||
| void listFiltered() { | ||
| mockRequest( | ||
| InteractionContext.builder() | ||
| .request( | ||
| TestRequest.builder() | ||
| .method(GET) | ||
| .path("/feature_flags?order_by=name&page=1&per_page=1") | ||
| .build()) | ||
| .response( | ||
| TestResponse.builder() | ||
| .status(OK) | ||
| .payload( | ||
| "fixtures/client/v3/featureflags/GET_response_filtered.json") | ||
| .build()) | ||
| .build()); | ||
|
|
||
| this.featureFlags | ||
| .list(ListFeatureFlagsRequest.builder().orderBy("name").page(1).perPage(1).build()) | ||
| .as(StepVerifier::create) | ||
| .expectNext( | ||
| ListFeatureFlagsResponse.builder() | ||
| .pagination( | ||
| Pagination.builder() | ||
| .totalResults(2) | ||
| .totalPages(2) | ||
| .first( | ||
| Link.builder() | ||
| .href( | ||
| "https://api.example.org/v3/feature_flags?order_by=name&page=1&per_page=1") | ||
| .build()) | ||
| .last( | ||
| Link.builder() | ||
| .href( | ||
| "https://api.example.org/v3/feature_flags?order_by=name&page=2&per_page=1") | ||
| .build()) | ||
| .next( | ||
| Link.builder() | ||
| .href( | ||
| "https://api.example.org/v3/feature_flags?order_by=name&page=2&per_page=1") | ||
| .build()) | ||
| .build()) | ||
| .resource( | ||
| GetFeatureFlagResponse.builder() | ||
| .name("app_scaling") | ||
| .enabled(true) | ||
| .updatedAt(UPDATED_AT) | ||
| .link("self", selfLink("app_scaling")) | ||
| .build()) | ||
| .build()) | ||
| .expectComplete() | ||
| .verify(Duration.ofSeconds(5)); | ||
| } | ||
|
|
||
| @Test | ||
| void update() { | ||
| mockRequest( | ||
| InteractionContext.builder() | ||
| .request( | ||
| TestRequest.builder() | ||
| .method(PATCH) | ||
| .path("/feature_flags/app_scaling") | ||
| .payload( | ||
| "fixtures/client/v3/featureflags/PATCH_{feature_flag_name}_request.json") | ||
| .build()) | ||
| .response( | ||
| TestResponse.builder() | ||
| .status(OK) | ||
| .payload( | ||
| "fixtures/client/v3/featureflags/PATCH_{feature_flag_name}_response.json") | ||
| .build()) | ||
| .build()); | ||
|
|
||
| this.featureFlags | ||
| .update( | ||
| UpdateFeatureFlagRequest.builder() | ||
| .name("app_scaling") | ||
| .enabled(false) | ||
| .customErrorMessage("This feature is disabled") | ||
| .build()) | ||
| .as(StepVerifier::create) | ||
| .expectNext( | ||
| UpdateFeatureFlagResponse.builder() | ||
| .name("app_scaling") | ||
| .enabled(false) | ||
| .customErrorMessage("This feature is disabled") | ||
| .updatedAt(UPDATED_AT) | ||
| .link("self", selfLink("app_scaling")) | ||
| .build()) | ||
| .expectComplete() | ||
| .verify(Duration.ofSeconds(5)); | ||
| } | ||
|
|
||
| private static Link selfLink(String flagName) { | ||
| return Link.builder().href(BASE_URL + flagName).build(); | ||
| } | ||
| } | ||
37 changes: 37 additions & 0 deletions
37
...undry-client-reactor/src/test/resources/fixtures/client/v3/featureflags/GET_response.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "pagination": { | ||
| "total_results": 2, | ||
| "first": { | ||
| "href": "https://api.example.org/v3/feature_flags?page=1&per_page=50" | ||
| }, | ||
| "last": { | ||
| "href": "https://api.example.org/v3/feature_flags?page=1&per_page=50" | ||
| }, | ||
| "next": null, | ||
| "previous": null | ||
| }, | ||
| "resources": [ | ||
| { | ||
| "name": "app_scaling", | ||
| "enabled": true, | ||
| "custom_error_message": null, | ||
| "updated_at": "2020-03-10T15:49:32Z", | ||
| "links": { | ||
| "self": { | ||
| "href": "https://api.example.org/v3/feature_flags/app_scaling" | ||
| } | ||
| } | ||
| }, | ||
| { | ||
| "name": "user_org_creation", | ||
| "enabled": false, | ||
| "custom_error_message": "This feature is disabled", | ||
| "updated_at": "2020-03-10T15:49:32Z", | ||
| "links": { | ||
| "self": { | ||
| "href": "https://api.example.org/v3/feature_flags/user_org_creation" | ||
| } | ||
| } | ||
| } | ||
| ] | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unit tests only cover the happy path. It would be good to have:
listtest withpage/perPage/orderByso the query string is checked,get/update(404 for an unknown flag, 422 for a message that is too long). There are fixtures for that in other v3 reactor tests that can be copied.The integration test also never sets
customErrorMessage, so that mapping is only checked against the canned JSON. Setting a message in the update test and checking it comes back would cover it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Integration-test for customErrorMessage is added. Unittest for list with more query parameters is added. Error cases are not covered in most of the client.v3 tests, so I omited them.