Skip to content

Commit 0cc3ef8

Browse files
authored
feat(policy): implement data usage constraints for end date and duration (eclipse-tractusx#2668)
* feat(policy): implement data usage constraints for end date and duration * fix(tests): exclude cx-policy dependency from configurations * feat(policy): improve null handling and validation on data usage constraints * fix(policy): indentation in error handling for data usage constraints * feat(policy): implement data provisioning end date and duration constraints * fix(policy): evaluate true if current instant is expiry date * fix(policy): check for null operator instead of operand * fix(policy): remove redundant timestamp parsing * feat(policy): create abstract classes for data end date and data end duration days * feat(policy): add unit tests for base constraint classes * chore: adjust year of contribution * feat(policy): replace inForceDateUsagePolicy with dataProvisioningEndDate for transfer e2e tests * feat(policy): remove cx-policy dependency from e2e-fixtures * chore(policy): remove unused imports * feat(tests): create common method for context.now manipulation * feat(policy): use context.now() for date evaluations
1 parent 2ab6041 commit 0cc3ef8

19 files changed

Lines changed: 675 additions & 311 deletions

edc-extensions/cx-policy/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ dependencies {
2828
implementation(project(":spi:core-spi"))
2929
implementation(project(":core:core-utils"))
3030
implementation(project(":spi:bdrs-client-spi"))
31+
implementation(libs.edc.core.policy.monitor)
3132
implementation(libs.edc.spi.catalog)
3233
implementation(libs.edc.spi.contract)
3334
implementation(libs.edc.spi.decentralized.claims)

edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/CxPolicyExtension.java

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext;
2424
import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext;
2525
import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext;
26+
import org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext;
2627
import org.eclipse.edc.policy.engine.spi.PolicyEngine;
2728
import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry;
2829
import org.eclipse.edc.policy.model.Duty;
@@ -71,6 +72,7 @@
7172
import java.util.stream.Stream;
7273

7374
import static org.eclipse.edc.connector.controlplane.policy.spi.PolicyDefinition.EDC_POLICY_DEFINITION_TYPE;
75+
import static org.eclipse.edc.connector.policy.monitor.spi.PolicyMonitorContext.POLICY_MONITOR_SCOPE;
7476
import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA;
7577
import static org.eclipse.tractusx.edc.edr.spi.CoreConstants.CX_POLICY_2025_09_NS;
7678
import static org.eclipse.tractusx.edc.policy.cx.affiliates.AffiliatesBpnlProhibitionConstraintFunction.AFFILIATES_BPNL;
@@ -154,11 +156,11 @@ public void registerFunctions(PolicyEngine engine) {
154156
withCxPolicyNsPrefix(USAGE_RESTRICTION), new UsageRestrictionConstraintFunction<>());
155157

156158
// Usage Duty Validators
157-
engine.registerFunction(ContractNegotiationPolicyContext.class, Duty.class,
159+
engine.registerFunction(PolicyMonitorContext.class, Duty.class,
158160
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DURATION_DAYS), new DataProvisioningEndDurationDaysConstraintFunction<>());
159161
engine.registerFunction(TransferProcessPolicyContext.class, Duty.class,
160162
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DURATION_DAYS), new DataProvisioningEndDurationDaysConstraintFunction<>());
161-
engine.registerFunction(ContractNegotiationPolicyContext.class, Duty.class,
163+
engine.registerFunction(PolicyMonitorContext.class, Duty.class,
162164
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DATE), new DataProvisioningEndDateConstraintFunction<>());
163165
engine.registerFunction(TransferProcessPolicyContext.class, Duty.class,
164166
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DATE), new DataProvisioningEndDateConstraintFunction<>());
@@ -202,17 +204,17 @@ public void registerFunctions(PolicyEngine engine) {
202204
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class,
203205
withCxPolicyNsPrefix(USAGE_PURPOSE), new UsagePurposeConstraintFunction<>());
204206

205-
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_FREQUENCY, new DataFrequencyConstraintFunction<>());
206-
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_FREQUENCY, new DataFrequencyConstraintFunction<>());
207+
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_FREQUENCY), new DataFrequencyConstraintFunction<>());
208+
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_FREQUENCY), new DataFrequencyConstraintFunction<>());
207209

208-
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DATE, new DataUsageEndDateConstraintFunction<>());
209-
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DATE, new DataUsageEndDateConstraintFunction<>());
210+
engine.registerFunction(PolicyMonitorContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DATE), new DataUsageEndDateConstraintFunction<>());
211+
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DATE), new DataUsageEndDateConstraintFunction<>());
210212

211-
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DEFINITION, new DataUsageEndDefinitionConstraintFunction<>());
212-
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DEFINITION, new DataUsageEndDefinitionConstraintFunction<>());
213+
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DEFINITION), new DataUsageEndDefinitionConstraintFunction<>());
214+
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DEFINITION), new DataUsageEndDefinitionConstraintFunction<>());
213215

214-
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DURATION_DAYS, new DataUsageEndDurationDaysConstraintFunction<>());
215-
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, CX_POLICY_2025_09_NS + DATA_USAGE_END_DURATION_DAYS, new DataUsageEndDurationDaysConstraintFunction<>());
216+
engine.registerFunction(PolicyMonitorContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DURATION_DAYS), new DataUsageEndDurationDaysConstraintFunction<>());
217+
engine.registerFunction(TransferProcessPolicyContext.class, Permission.class, withCxPolicyNsPrefix(DATA_USAGE_END_DURATION_DAYS), new DataUsageEndDurationDaysConstraintFunction<>());
216218

217219
engine.registerFunction(ContractNegotiationPolicyContext.class, Permission.class,
218220
withCxPolicyNsPrefix(JURISDICTION_LOCATION), new JurisdictionLocationConstraintFunction<>());
@@ -288,13 +290,13 @@ public static void registerBindings(RuleBindingRegistry registry) {
288290
registry.bind(ODRL_SCHEMA + "use", NEGOTIATION_SCOPE);
289291
registry.bind(ODRL_SCHEMA + "use", TRANSFER_PROCESS_SCOPE);
290292

291-
registry.bind(CX_POLICY_2025_09_NS + "access", CATALOG_SCOPE);
292-
registry.bind(CX_POLICY_2025_09_NS + "access", NEGOTIATION_SCOPE);
293-
registry.bind(CX_POLICY_2025_09_NS + "access", TRANSFER_PROCESS_SCOPE);
293+
registry.bind(withCxPolicyNsPrefix("access"), CATALOG_SCOPE);
294+
registry.bind(withCxPolicyNsPrefix("access"), NEGOTIATION_SCOPE);
295+
registry.bind(withCxPolicyNsPrefix("access"), TRANSFER_PROCESS_SCOPE);
294296

295-
registry.bind(CX_POLICY_2025_09_NS + USAGE_PURPOSE, CATALOG_SCOPE);
296-
registry.bind(CX_POLICY_2025_09_NS + USAGE_PURPOSE, NEGOTIATION_SCOPE);
297-
registry.bind(CX_POLICY_2025_09_NS + USAGE_PURPOSE, TRANSFER_PROCESS_SCOPE);
297+
registry.bind(withCxPolicyNsPrefix(USAGE_PURPOSE), CATALOG_SCOPE);
298+
registry.bind(withCxPolicyNsPrefix(USAGE_PURPOSE), NEGOTIATION_SCOPE);
299+
registry.bind(withCxPolicyNsPrefix(USAGE_PURPOSE), TRANSFER_PROCESS_SCOPE);
298300

299301
var namesInCatalogScope = Set.of(
300302
withCxPolicyNsPrefix(USAGE_PURPOSE),
@@ -310,11 +312,6 @@ public static void registerBindings(RuleBindingRegistry registry) {
310312
withCxPolicyNsPrefix(AFFILIATES_BPNL),
311313
withCxPolicyNsPrefix(AFFILIATES_REGION),
312314
withCxPolicyNsPrefix(DATA_FREQUENCY),
313-
withCxPolicyNsPrefix(DATA_USAGE_END_DATE),
314-
withCxPolicyNsPrefix(DATA_USAGE_END_DEFINITION),
315-
withCxPolicyNsPrefix(DATA_USAGE_END_DURATION_DAYS),
316-
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DURATION_DAYS),
317-
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DATE),
318315
withCxPolicyNsPrefix(JURISDICTION_LOCATION),
319316
withCxPolicyNsPrefix(JURISDICTION_LOCATION_REFERENCE),
320317
withCxPolicyNsPrefix(LIABILITY),
@@ -354,6 +351,15 @@ public static void registerBindings(RuleBindingRegistry registry) {
354351
withCxPolicyNsPrefix(USAGE_RESTRICTION)
355352
);
356353
registerBindingSet(registry, namesInTransferProcessScope, TRANSFER_PROCESS_SCOPE);
354+
355+
var namesInPolicyMonitorScope = Set.of(
356+
withCxPolicyNsPrefix(DATA_USAGE_END_DATE),
357+
withCxPolicyNsPrefix(DATA_USAGE_END_DEFINITION),
358+
withCxPolicyNsPrefix(DATA_USAGE_END_DURATION_DAYS),
359+
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DURATION_DAYS),
360+
withCxPolicyNsPrefix(DATA_PROVISIONING_END_DATE)
361+
);
362+
registerBindingSet(registry, namesInPolicyMonitorScope, POLICY_MONITOR_SCOPE);
357363
}
358364

359365
private static void registerBindingSet(RuleBindingRegistry registry, Set<String> names, String scope) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License, Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* SPDX-License-Identifier: Apache-2.0
18+
*/
19+
20+
package org.eclipse.tractusx.edc.policy.cx.common;
21+
22+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext;
23+
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
24+
import org.eclipse.edc.policy.model.Operator;
25+
import org.eclipse.edc.policy.model.Rule;
26+
import org.eclipse.edc.spi.result.Result;
27+
28+
import java.time.Instant;
29+
import java.time.format.DateTimeParseException;
30+
import java.time.temporal.ChronoUnit;
31+
import java.util.Set;
32+
import java.util.regex.Pattern;
33+
34+
35+
/**
36+
* This is an abstract constraint function for DataEndDate constraints. It evaluates to true if the current date is
37+
* not after the specified expiry date. The expiry date is expected to be in ISO-8061 UTC date-time format, truncated
38+
* to seconds (e.g. "2024-12-31T23:59:59Z").
39+
*
40+
* @param <R> the type of the rule (e.g. Permission, Duty, etc.)
41+
* @param <C> the type of the agreement policy context
42+
*/
43+
public abstract class AbstractDataEndDateConstraintFunction<R extends Rule, C extends AgreementPolicyContext> implements AtomicConstraintRuleFunction<R, C> {
44+
private static final Set<Operator> ALLOWED_OPERATORS = Set.of(
45+
Operator.EQ
46+
);
47+
48+
private static final String DATE_PATTERN = "^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))$";
49+
50+
@Override
51+
public boolean evaluate(Operator operator, Object rightOperand, R rule, C context) {
52+
try {
53+
var expiryDate = Instant.parse(rightOperand.toString());
54+
return !context.now().truncatedTo(ChronoUnit.SECONDS).isAfter(expiryDate);
55+
} catch (DateTimeParseException e) {
56+
context.reportProblem("Invalid right-operand: right operand must match pattern '%s'".formatted(DATE_PATTERN));
57+
return false;
58+
}
59+
}
60+
61+
@Override
62+
public Result<Void> validate(Operator operator, Object rightOperand, R rule) {
63+
if (operator == null) {
64+
return Result.failure("Invalid operator: this constraint only allows the following operators: %s, but received null.".formatted(ALLOWED_OPERATORS));
65+
}
66+
67+
if (!ALLOWED_OPERATORS.contains(operator)) {
68+
return Result.failure("Invalid operator: this constraint only allows the following operators: %s, but received '%s'.".formatted(ALLOWED_OPERATORS, operator));
69+
}
70+
71+
var compiledPattern = Pattern.compile(DATE_PATTERN);
72+
if (!(rightOperand instanceof String rightValue && compiledPattern.matcher(rightValue).matches())) {
73+
return Result.failure("Invalid right-operand: right operand must match pattern '%s'".formatted(DATE_PATTERN));
74+
}
75+
76+
return Result.success();
77+
}
78+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* This program and the accompanying materials are made available under the
8+
* terms of the Apache License, Version 2.0 which is available at
9+
* https://www.apache.org/licenses/LICENSE-2.0.
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
* License for the specific language governing permissions and limitations
15+
* under the License.
16+
*
17+
* SPDX-License-Identifier: Apache-2.0
18+
*/
19+
20+
package org.eclipse.tractusx.edc.policy.cx.common;
21+
22+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext;
23+
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
24+
import org.eclipse.edc.policy.model.Operator;
25+
import org.eclipse.edc.policy.model.Rule;
26+
import org.eclipse.edc.spi.result.Result;
27+
28+
import java.time.Instant;
29+
import java.time.temporal.ChronoUnit;
30+
import java.util.Set;
31+
32+
/**
33+
* This is an abstract constraint function for DataEndDurationDays constraints. It evaluates to true if the current
34+
* date is before the expiry date, which is calculated by adding the specified number of days to the contract signing
35+
* date. The contract signing date is retrieved from the agreement context.
36+
*
37+
* @param <R> the type of the rule (e.g. Permission, Duty, etc.)
38+
* @param <C> the type of the agreement policy context
39+
*/
40+
public abstract class AbstractDataEndDurationDaysConstraintFunction<R extends Rule, C extends AgreementPolicyContext> implements AtomicConstraintRuleFunction<R, C> {
41+
private static final Set<Operator> ALLOWED_OPERATORS = Set.of(
42+
Operator.EQ
43+
);
44+
45+
private Result<Integer> extractRightValue(Object rightOperand) {
46+
if (rightOperand instanceof Integer) {
47+
return Result.success((Integer) rightOperand);
48+
} else if (rightOperand instanceof String rightValue) {
49+
try {
50+
return Result.success(Integer.parseInt(rightValue));
51+
} catch (NumberFormatException e) {
52+
return Result.failure("Invalid right-operand: value must be a valid integer, but got '%s'.".formatted(rightOperand));
53+
}
54+
}
55+
56+
return Result.failure("Invalid right-operand: this constraint only allows integer values or strings representing integers, but got '%s'."
57+
.formatted(rightOperand != null ? rightOperand.getClass().getName() : "null"));
58+
}
59+
60+
@Override
61+
public boolean evaluate(Operator operator, Object rightOperand, R rule, C context) {
62+
return extractRightValue(rightOperand)
63+
.map(rightValue -> Instant.ofEpochSecond(context.contractAgreement().getContractSigningDate())
64+
.truncatedTo(ChronoUnit.DAYS)
65+
.plus(rightValue, ChronoUnit.DAYS))
66+
.map(expiryDate -> context.now().truncatedTo(ChronoUnit.DAYS).isBefore(expiryDate))
67+
.orElse(failure -> {
68+
context.reportProblem("Failed to evaluate constraint due to invalid right operand: '%s'. Problems: %s".formatted(rightOperand, failure));
69+
return false;
70+
});
71+
}
72+
73+
@Override
74+
public Result<Void> validate(Operator operator, Object rightValue, R rule) {
75+
if (operator == null) {
76+
return Result.failure("Invalid operator: this constraint only allows the following operators: %s, but received null.".formatted(ALLOWED_OPERATORS));
77+
}
78+
79+
if (!ALLOWED_OPERATORS.contains(operator)) {
80+
return Result.failure("Invalid operator: this constraint only allows the following operators: %s, but received '%s'.".formatted(ALLOWED_OPERATORS, operator));
81+
}
82+
83+
return extractRightValue(rightValue).mapEmpty();
84+
}
85+
}

edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/dataprovisioning/DataProvisioningEndDateConstraintFunction.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,11 @@
1919

2020
package org.eclipse.tractusx.edc.policy.cx.dataprovisioning;
2121

22-
import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext;
22+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext;
2323
import org.eclipse.edc.policy.model.Duty;
24-
import org.eclipse.edc.policy.model.Operator;
25-
import org.eclipse.tractusx.edc.policy.cx.common.ValueValidatingConstraintFunction;
24+
import org.eclipse.tractusx.edc.policy.cx.common.AbstractDataEndDateConstraintFunction;
2625

27-
import java.util.Set;
2826

29-
/**
30-
* This is a placeholder constraint function for DataProvisioningEndDate. It always returns true but allows
31-
* the validation of policies to be strictly enforced.
32-
*/
33-
public class DataProvisioningEndDateConstraintFunction<C extends ParticipantAgentPolicyContext> extends ValueValidatingConstraintFunction<Duty, C> {
27+
public class DataProvisioningEndDateConstraintFunction<C extends AgreementPolicyContext> extends AbstractDataEndDateConstraintFunction<Duty, C> {
3428
public static final String DATA_PROVISIONING_END_DATE = "DataProvisioningEndDate";
35-
36-
public DataProvisioningEndDateConstraintFunction() {
37-
super(
38-
Set.of(Operator.EQ),
39-
"^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(Z|[+-]\\d{2}:\\d{2}))$"
40-
);
41-
}
42-
}
29+
}

edc-extensions/cx-policy/src/main/java/org/eclipse/tractusx/edc/policy/cx/dataprovisioning/DataProvisioningEndDurationDaysConstraintFunction.java

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,11 @@
1919

2020
package org.eclipse.tractusx.edc.policy.cx.dataprovisioning;
2121

22-
import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext;
23-
import org.eclipse.edc.policy.engine.spi.AtomicConstraintRuleFunction;
22+
import org.eclipse.edc.connector.controlplane.contract.spi.policy.AgreementPolicyContext;
2423
import org.eclipse.edc.policy.model.Duty;
25-
import org.eclipse.edc.policy.model.Operator;
26-
import org.eclipse.edc.spi.result.Result;
24+
import org.eclipse.tractusx.edc.policy.cx.common.AbstractDataEndDurationDaysConstraintFunction;
2725

28-
import java.util.Set;
2926

30-
/**
31-
* This is a placeholder constraint function for DataProvisioningEndDurationDays. It always returns true but allows
32-
* the validation of policies to be strictly enforced.
33-
*/
34-
public class DataProvisioningEndDurationDaysConstraintFunction<C extends ParticipantAgentPolicyContext> implements AtomicConstraintRuleFunction<Duty, C> {
27+
public class DataProvisioningEndDurationDaysConstraintFunction<C extends AgreementPolicyContext> extends AbstractDataEndDurationDaysConstraintFunction<Duty, C> {
3528
public static final String DATA_PROVISIONING_END_DURATION_DAYS = "DataProvisioningEndDurationDays";
36-
private static final Set<Operator> ALLOWED_OPERATORS = Set.of(
37-
Operator.EQ
38-
);
39-
40-
@Override
41-
public boolean evaluate(Operator operator, Object rightOperand, Duty permission, C c) {
42-
return true;
43-
}
44-
45-
@Override
46-
public Result<Void> validate(Operator operator, Object rightValue, Duty rule) {
47-
if (!ALLOWED_OPERATORS.contains(operator)) {
48-
return Result.failure("Invalid operator: this constraint only allows the following operators: %s, but received '%s'.".formatted(ALLOWED_OPERATORS, operator));
49-
}
50-
51-
if (rightValue instanceof Integer) {
52-
return Result.success();
53-
}
54-
55-
if (rightValue instanceof String stringValue) {
56-
try {
57-
Integer.parseInt(stringValue);
58-
return Result.success();
59-
} catch (NumberFormatException e) {
60-
return Result.failure("Invalid right-operand: String value must be a valid integer value, but got '%s'.".formatted(stringValue));
61-
}
62-
}
63-
64-
return Result.failure("Invalid right-operand: right operand must be an integer value");
65-
}
6629
}

0 commit comments

Comments
 (0)