Skip to content

Commit bad1347

Browse files
feat: persist validation constraints for subscription forms
- Add storage and mapping for form_validation_constraints (JSON ↔ domain) - Introduce upgrader to backfill/migrate existing subscription forms
1 parent 32965c0 commit bad1347

File tree

18 files changed

+364
-14
lines changed

18 files changed

+364
-14
lines changed

gravitee-apim-repository/gravitee-apim-repository-api/src/main/java/io/gravitee/repository/management/model/SubscriptionForm.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,10 @@ public class SubscriptionForm {
5252
* Whether the form is enabled and visible to API consumers.
5353
*/
5454
private boolean enabled;
55+
56+
/**
57+
* JSON string of validation constraints per field key, derived from the GMD content.
58+
* {@code null} when nothing is stored (empty rule sets are typically not persisted).
59+
*/
60+
private String validationConstraints;
5561
}

gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/java/io/gravitee/repository/jdbc/management/JdbcSubscriptionFormRepository.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ protected JdbcObjectMapper<SubscriptionForm> buildOrm() {
5050
.addColumn("environment_id", Types.NVARCHAR, String.class)
5151
.addColumn("gmd_content", Types.LONGNVARCHAR, String.class)
5252
.addColumn("enabled", Types.BIT, boolean.class)
53+
.addColumn("validation_constraints", Types.CLOB, String.class)
5354
.build();
5455
}
5556

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
databaseChangeLog:
2+
- changeSet:
3+
id: 4.11.0_11_add_validation_constraints_to_subscription_forms
4+
author: GraviteeSource Team
5+
changes:
6+
- addColumn:
7+
tableName: ${gravitee_prefix}subscription_forms
8+
columns:
9+
- column:
10+
name: validation_constraints
11+
type: nclob
12+
constraints:
13+
nullable: true

gravitee-apim-repository/gravitee-apim-repository-jdbc/src/main/resources/liquibase/master.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,4 +319,6 @@ databaseChangeLog:
319319
- include:
320320
- file: liquibase/changelogs/v4_11_0/10_make_root_id_not_null_portal_navigation_items.yml
321321
- include:
322-
- file: liquibase/changelogs/v4_12_0/00_add_tags_key_column.yml
322+
- file: liquibase/changelogs/v4_11_0/11_add_validation_constraints_to_subscription_forms.yml
323+
- include:
324+
- file: liquibase/changelogs/v4_12_0/00_add_tags_key_column.yml

gravitee-apim-repository/gravitee-apim-repository-mongodb/src/main/java/io/gravitee/repository/mongodb/management/internal/model/SubscriptionFormMongo.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ public class SubscriptionFormMongo {
3636
private String environmentId;
3737
private String gmdContent;
3838
private boolean enabled;
39+
private String validationConstraints;
3940
}

gravitee-apim-repository/gravitee-apim-repository-test/src/test/java/io/gravitee/repository/management/SubscriptionFormRepositoryTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public void shouldFindById() throws Exception {
4242
assertThat(form.getEnvironmentId()).isEqualTo("env-1");
4343
assertThat(form.getGmdContent()).contains("gmd-grid");
4444
assertThat(form.isEnabled()).isTrue();
45+
assertThat(form.getValidationConstraints()).isEqualTo("{\"email\":[{\"type\":\"required\"}]}");
4546
}
4647

4748
@Test
@@ -94,8 +95,9 @@ public void shouldCreate() throws Exception {
9495
SubscriptionForm form = SubscriptionForm.builder()
9596
.id("sub-form-new")
9697
.environmentId("env-new")
97-
.gmdContent("<gmd-card><gmd-input name=\"field\" label=\"Field\"/></gmd-card>")
98+
.gmdContent("<gmd-card><gmd-input name=\"field\" label=\"Field\" fieldKey=\"field\"/></gmd-card>")
9899
.enabled(false)
100+
.validationConstraints("{\"field\":[]}")
99101
.build();
100102

101103
Set<SubscriptionForm> allBefore = subscriptionFormRepository.findAll();
@@ -111,6 +113,7 @@ public void shouldCreate() throws Exception {
111113
assertThat(saved.getEnvironmentId()).isEqualTo("env-new");
112114
assertThat(saved.getGmdContent()).contains("gmd-input");
113115
assertThat(saved.isEnabled()).isFalse();
116+
assertThat(saved.getValidationConstraints()).isEqualTo("{\"field\":[]}");
114117
}
115118

116119
@Test
@@ -123,19 +126,22 @@ public void shouldUpdate() throws Exception {
123126

124127
SubscriptionForm updated = existing
125128
.toBuilder()
126-
.gmdContent("<gmd-card><gmd-input name=\"updated\" label=\"Updated\"/></gmd-card>")
129+
.gmdContent("<gmd-card><gmd-input name=\"updated\" label=\"Updated\" fieldKey=\"updated\"/></gmd-card>")
127130
.enabled(true)
131+
.validationConstraints("{\"updated\":[]}")
128132
.build();
129133

130134
SubscriptionForm result = subscriptionFormRepository.update(updated);
131135

132136
assertThat(result.getGmdContent()).contains("updated");
133137
assertThat(result.isEnabled()).isTrue();
138+
assertThat(result.getValidationConstraints()).contains("updated");
134139

135140
Optional<SubscriptionForm> reloaded = subscriptionFormRepository.findById("sub-form-update");
136141
assertThat(reloaded).isPresent();
137142
assertThat(reloaded.get().getGmdContent()).contains("updated");
138143
assertThat(reloaded.get().isEnabled()).isTrue();
144+
assertThat(reloaded.get().getValidationConstraints()).contains("updated");
139145
}
140146

141147
@Test

gravitee-apim-repository/gravitee-apim-repository-test/src/test/resources/data/subscriptionform-tests/subscriptionForms.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
{
33
"id": "sub-form-find-by-id",
44
"environmentId": "env-1",
5-
"gmdContent": "<gmd-grid columns=\"2\"><gmd-card><gmd-card-title>Info</gmd-card-title><gmd-input name=\"email\" label=\"Email\" required=\"true\"/></gmd-card></gmd-grid>",
6-
"enabled": true
5+
"gmdContent": "<gmd-grid columns=\"2\"><gmd-card><gmd-card-title>Info</gmd-card-title><gmd-input name=\"email\" label=\"Email\" fieldKey=\"email\" required=\"true\"/></gmd-card></gmd-grid>",
6+
"enabled": true,
7+
"validationConstraints": "{\"email\":[{\"type\":\"required\"}]}"
78
},
89
{
910
"id": "sub-form-update",

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/subscription_form/model/Constraint.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package io.gravitee.apim.core.subscription_form.model;
1717

18+
import com.fasterxml.jackson.annotation.JsonSubTypes;
19+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
1820
import java.util.Arrays;
1921
import java.util.HashSet;
2022
import java.util.List;
@@ -25,8 +27,8 @@
2527
/**
2628
* A single validation rule for a subscription form field.
2729
*
28-
* <p>Constraints are extracted from a {@link SubscriptionFormSchema} at save time and persisted
29-
* alongside the schema. At submission time the validator iterates the constraint map and calls
30+
* <p>Constraints are derived from a {@link SubscriptionFormSchema} at save time and persisted as JSON.
31+
* At submission time the validator iterates the constraint map and calls
3032
* {@link #validate(String, String)} — no schema knowledge required.</p>
3133
*
3234
* <p>Implementations split rule logic ({@link #check(String)}) from the human-readable message
@@ -36,6 +38,20 @@
3638
*
3739
* @author Gravitee.io Team
3840
*/
41+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
42+
@JsonSubTypes(
43+
{
44+
@JsonSubTypes.Type(value = Constraint.Required.class, name = "required"),
45+
@JsonSubTypes.Type(value = Constraint.MustBeTrue.class, name = "mustBeTrue"),
46+
@JsonSubTypes.Type(value = Constraint.NonEmptySelection.class, name = "nonEmptySelection"),
47+
@JsonSubTypes.Type(value = Constraint.ReadOnly.class, name = "readOnly"),
48+
@JsonSubTypes.Type(value = Constraint.MinLength.class, name = "minLength"),
49+
@JsonSubTypes.Type(value = Constraint.MaxLength.class, name = "maxLength"),
50+
@JsonSubTypes.Type(value = Constraint.MatchesPattern.class, name = "matchesPattern"),
51+
@JsonSubTypes.Type(value = Constraint.OneOf.class, name = "oneOf"),
52+
@JsonSubTypes.Type(value = Constraint.EachOf.class, name = "eachOf"),
53+
}
54+
)
3955
public sealed interface Constraint
4056
permits
4157
Constraint.Required,

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/subscription_form/model/SubscriptionForm.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,21 @@ public class SubscriptionForm {
4343

4444
private GraviteeMarkdown gmdContent;
4545
private boolean enabled;
46+
/**
47+
* Validation rules per field key, derived from GMD and persisted as JSON at the repository boundary.
48+
* {@code null} when nothing is stored for this form (including when there are no rules to persist).
49+
*/
50+
private SubscriptionFormFieldConstraints validationConstraints;
4651

4752
/**
48-
* Updates this form with new GMD content (mutates in place).
53+
* Updates this form (mutates in place). When {@code constraints} is {@code null},
54+
* existing validation constraints are left unchanged — use case can defer populating this until the resource layer.
4955
*/
50-
public void update(GraviteeMarkdown gmdContent) {
56+
public void update(GraviteeMarkdown gmdContent, SubscriptionFormFieldConstraints constraints) {
5157
this.gmdContent = gmdContent;
58+
if (constraints != null) {
59+
this.validationConstraints = constraints;
60+
}
5261
}
5362

5463
/**

gravitee-apim-rest-api/gravitee-apim-rest-api-service/src/main/java/io/gravitee/apim/core/subscription_form/use_case/UpdateSubscriptionFormUseCase.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public Output execute(Input input) {
5454
)
5555
);
5656

57-
existingForm.update(GraviteeMarkdown.of(input.gmdContent()));
57+
existingForm.update(GraviteeMarkdown.of(input.gmdContent()), null);
5858
var savedForm = subscriptionFormCrudService.update(existingForm);
5959

6060
log.info("Updated subscription form [{}] for environment [{}]", input.subscriptionFormId(), input.environmentId());

0 commit comments

Comments
 (0)