Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.netflix.maestro.models.parameter.ParamDefinition;
import com.netflix.maestro.models.signal.SignalDependenciesDefinition;
import com.netflix.maestro.models.signal.SignalOutputsDefinition;
import com.netflix.maestro.validations.MaestroNameSizeConstraint;
import com.netflix.maestro.validations.MaestroReferenceIdConstraint;
import com.netflix.maestro.validations.SignalDependenciesDefinitionConstraint;
import com.netflix.maestro.validations.SignalOutputsDefinitionConstraint;
Expand All @@ -44,7 +45,7 @@ public abstract class AbstractStep implements Step {
private String id;

@Getter(onMethod = @__({@Override}))
@Size(max = Constants.NAME_LENGTH_LIMIT)
@MaestroNameSizeConstraint
private String name;

@Getter(onMethod = @__({@Override}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.netflix.maestro.models.trigger.TimeTrigger;
import com.netflix.maestro.utils.MapHelper;
import com.netflix.maestro.validations.MaestroIdConstraint;
import com.netflix.maestro.validations.MaestroNameSizeConstraint;
import com.netflix.maestro.validations.SignalTriggerConstraint;
import com.netflix.maestro.validations.TagListConstraint;
import com.netflix.maestro.validations.TimeTriggerConstraint;
Expand Down Expand Up @@ -73,8 +74,7 @@ public class Workflow {
* Name of the workflow. Can be absent by user. Can be filled by workflow id if absent - use
* helper method in WorkflowHelper.
*/
@Size(max = Constants.NAME_LENGTH_LIMIT)
private final String name;
@MaestroNameSizeConstraint private final String name;

@Size(max = Constants.FIELD_SIZE_LIMIT)
private final String description;
Expand All @@ -96,7 +96,7 @@ public class Workflow {
@Builder(toBuilder = true)
Workflow(
String id,
@Size(max = Constants.NAME_LENGTH_LIMIT) String name,
String name,
@Size(max = Constants.FIELD_SIZE_LIMIT) String description,
@Valid TagList tags,
ParsableLong timeout,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2024 Netflix, Inc.
*
* 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 com.netflix.maestro.utils;

import com.netflix.maestro.models.Constants;

/** Interface exposing configurable validation limits used by constraint validators. */
public interface MaestroIdNameValidationLimits {

/**
* Default instance backed by compile-time {@link Constants} values. Used by validators when no
* configured {@link MaestroIdNameValidationLimits} bean is injected (e.g. in unit tests without
* Spring). Keeps each limit independently defaulted so id and name limits remain decoupled.
*/
MaestroIdNameValidationLimits DEFAULTS =
new MaestroIdNameValidationLimits() {
@Override
public int getIdLengthLimit() {
return Constants.ID_LENGTH_LIMIT;
}

@Override
public int getNameLengthLimit() {
return Constants.NAME_LENGTH_LIMIT;
}
};

/** Returns the maximum allowed length for Maestro IDs. */
int getIdLengthLimit();

/** Returns the maximum allowed length for Maestro names. */
int getNameLengthLimit();
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
package com.netflix.maestro.validations;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.utils.MaestroIdNameValidationLimits;
import jakarta.inject.Inject;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
Expand Down Expand Up @@ -44,6 +46,8 @@
class MaestroIdValidator implements ConstraintValidator<MaestroIdConstraint, String> {
private static final Pattern ID_PATTERN = Pattern.compile("[_a-zA-Z0-9][.\\-_a-zA-Z0-9]*+");

@Inject private MaestroIdNameValidationLimits maestroIdNameValidationLimits;

@Override
public void initialize(MaestroIdConstraint constraint) {}

Expand All @@ -56,12 +60,17 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.ID_LENGTH_LIMIT) {
MaestroIdNameValidationLimits limits =
maestroIdNameValidationLimits != null
? maestroIdNameValidationLimits
: MaestroIdNameValidationLimits.DEFAULTS;
int idLimit = limits.getIdLengthLimit();
if (id.length() > idLimit) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro id] cannot be more than id length limit %s - rejected length is [%s] for value [%s]",
Constants.ID_LENGTH_LIMIT, id.length(), id))
idLimit, id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
package com.netflix.maestro.validations;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.utils.MaestroIdNameValidationLimits;
import com.netflix.maestro.utils.StringUtils;
import jakarta.inject.Inject;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
Expand Down Expand Up @@ -46,6 +48,8 @@ class MaestroNameValidator implements ConstraintValidator<MaestroNameConstraint,
private static final Pattern NAME_PATTERN =
Pattern.compile("[\\w \\.,:@&='(){}$+\\[\\]\\-\\/\\\\]+");

@Inject private MaestroIdNameValidationLimits maestroIdNameValidationLimits;

@Override
public void initialize(MaestroNameConstraint constraint) {}

Expand All @@ -59,13 +63,18 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.NAME_LENGTH_LIMIT) {
MaestroIdNameValidationLimits limits =
maestroIdNameValidationLimits != null
? maestroIdNameValidationLimits
: MaestroIdNameValidationLimits.DEFAULTS;
int nameLimit = limits.getNameLengthLimit();
if (id.length() > nameLimit) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro name] cannot be more than name length limit %s "
+ "- rejected length is [%s] for value [%s]",
Constants.NAME_LENGTH_LIMIT, id.length(), id))
nameLimit, id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright 2024 Netflix, Inc.
*
* 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 com.netflix.maestro.validations;

import com.netflix.maestro.utils.MaestroIdNameValidationLimits;
import jakarta.inject.Inject;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Validates that a name field does not exceed the configurable name length limit. Unlike {@link
* MaestroNameConstraint}, this annotation only enforces length and does not apply regex or reserved
* prefix/suffix checks. It is null-safe (null is treated as absent and passes). It replaces
* {@code @Size(max = Constants.NAME_LENGTH_LIMIT)} on optional model name fields where the limit
* must be configurable at runtime.
*/
@Documented
@Constraint(validatedBy = MaestroNameSizeConstraint.MaestroNameSizeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MaestroNameSizeConstraint {
/** input constraint message. */
String message() default "";

/** input constraint groups. */
Class<?>[] groups() default {};

/** input constraint payload. */
Class<? extends Payload>[] payload() default {};

/** Validates that the string length does not exceed the name length limit. */
class MaestroNameSizeValidator implements ConstraintValidator<MaestroNameSizeConstraint, String> {

@Inject private MaestroIdNameValidationLimits maestroIdNameValidationLimits;

@Override
public void initialize(MaestroNameSizeConstraint constraint) {}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // null means absent; use @NotNull separately if the field is required
}
MaestroIdNameValidationLimits limits =
maestroIdNameValidationLimits != null
? maestroIdNameValidationLimits
: MaestroIdNameValidationLimits.DEFAULTS;
int limit = limits.getNameLengthLimit();
if (value.length() > limit) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro name] cannot be more than name length limit %s"
+ " - rejected length is [%s] for value [%s]",
limit, value.length(), value))
.addConstraintViolation();
return false;
}
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import com.netflix.maestro.annotations.VisibleForTesting;
import com.netflix.maestro.models.Constants;
import com.netflix.maestro.utils.MaestroIdNameValidationLimits;
import com.netflix.maestro.utils.StepParamSeparator;
import jakarta.inject.Inject;
import jakarta.validation.Constraint;
Expand Down Expand Up @@ -48,6 +49,8 @@ class MaestroIdValidator implements ConstraintValidator<MaestroReferenceIdConstr
private static final Pattern ID_PATTERN = Pattern.compile("[_a-zA-Z][.\\-_a-zA-Z0-9]*+");
private static final String REJECTED_VALUE = "- rejected value is [%s]";

@Inject private MaestroIdNameValidationLimits maestroIdNameValidationLimits;

@Inject private StepParamSeparator stepParamSeparator;

@VisibleForTesting
Expand All @@ -74,13 +77,18 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.ID_LENGTH_LIMIT) {
MaestroIdNameValidationLimits limits =
maestroIdNameValidationLimits != null
? maestroIdNameValidationLimits
: MaestroIdNameValidationLimits.DEFAULTS;
int idLimit = limits.getIdLengthLimit();
if (id.length() > idLimit) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro id or name reference] cannot be more than id length limit %s "
+ "- rejected length is [%s] for value [%s]",
Constants.ID_LENGTH_LIMIT, id.length(), id))
idLimit, id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,14 @@ public void isIdTooLong() {
new TestId(new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")));
assertEquals(1, violations.size());
ConstraintViolation<TestId> violation = violations.iterator().next();
assertEquals(129, ((String) violation.getInvalidValue()).length());
assertEquals(Constants.ID_LENGTH_LIMIT + 1, ((String) violation.getInvalidValue()).length());
assertEquals(
String.format(
"[maestro id] cannot be more than id length limit 128 "
"[maestro id] cannot be more than id length limit %s "
+ "- rejected length is [%s] for value [%s]",
129, new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
Constants.ID_LENGTH_LIMIT,
Constants.ID_LENGTH_LIMIT + 1,
new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
violation.getMessage());
}

Expand Down
Loading
Loading