Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1839,10 +1839,6 @@ public boolean isEmpty() {

class PydanticType {

private static final String LESS_THAN = "lt";
private static final String GREATER_THAN = "gt";
private static final String GREATER_OR_EQUAL_TO = "ge";
private static final String LESS_OR_EQUAL_TO = "le";
private static final String TYPING = "typing";

private static final String DECIMAL = "Decimal";
Expand Down Expand Up @@ -1872,12 +1868,7 @@ public PydanticType(

private PythonType arrayType(IJsonSchemaValidationProperties cp) {
PythonType pt = new PythonType();
if (cp.getMaxItems() != null) {
pt.constrain("max_length", cp.getMaxItems());
}
if (cp.getMinItems() != null) {
pt.constrain("min_length", cp.getMinItems());
}
ConstraintApplier.applyConstraints(cp, pt, ConstraintType.ARRAY);
if (cp.getUniqueItems()) {
// A unique "array" is a set
// TODO: pydantic v2: Pydantic suggest to convert this to a set, but this has some implications:
Expand Down Expand Up @@ -1915,12 +1906,7 @@ private PythonType stringType(IJsonSchemaValidationProperties cp) {

// e.g. constr(regex=r'/[a-z]/i', strict=True)
pt.constrain("strict", true);
if (cp.getMaxLength() != null) {
pt.constrain("max_length", cp.getMaxLength());
}
if (cp.getMinLength() != null) {
pt.constrain("min_length", cp.getMinLength());
}
ConstraintApplier.applyConstraints(cp, pt, ConstraintType.STRING);

if (cp.getPattern() != null) {
moduleImports.add(PYDANTIC, "field_validator");
Expand Down Expand Up @@ -1952,28 +1938,8 @@ private PythonType numberType(IJsonSchemaValidationProperties cp) {
PythonType floatt = new PythonType("float");
PythonType intt = new PythonType("int");

// e.g. confloat(ge=10, le=100, strict=True)
if (cp.getMaximum() != null) {
if (cp.getExclusiveMaximum()) {
floatt.constrain(LESS_THAN, cp.getMaximum(), false);
intt.constrain(LESS_THAN, (int) Math.ceil(Double.valueOf(cp.getMaximum()))); // e.g. < 7.59 => < 8
} else {
floatt.constrain(LESS_OR_EQUAL_TO, cp.getMaximum(), false);
intt.constrain(LESS_OR_EQUAL_TO, (int) Math.floor(Double.valueOf(cp.getMaximum()))); // e.g. <= 7.59 => <= 7
}
}
if (cp.getMinimum() != null) {
if (cp.getExclusiveMinimum()) {
floatt.constrain(GREATER_THAN, cp.getMinimum(), false);
intt.constrain(GREATER_THAN, (int) Math.floor(Double.valueOf(cp.getMinimum()))); // e.g. > 7.59 => > 7
} else {
floatt.constrain(GREATER_OR_EQUAL_TO, cp.getMinimum(), false);
intt.constrain(GREATER_OR_EQUAL_TO, (int) Math.ceil(Double.valueOf(cp.getMinimum()))); // e.g. >= 7.59 => >= 8
}
}
if (cp.getMultipleOf() != null) {
floatt.constrain("multiple_of", cp.getMultipleOf());
}
ConstraintApplier.applyConstraints(cp, floatt, ConstraintType.NUMBER);
ConstraintApplier.applyConstraints(cp, intt, ConstraintType.ROUNDED_NUMBER);

if ("Union[StrictFloat, StrictInt]".equals(mapNumberTo)) {
floatt.constrain("strict", true);
Expand Down Expand Up @@ -2013,7 +1979,7 @@ private PythonType intType(IJsonSchemaValidationProperties cp) {
PythonType pt = new PythonType("int");
// e.g. conint(ge=10, le=100, strict=True)
pt.constrain("strict", true);
applyConstraints(pt, cp);
ConstraintApplier.applyConstraints(cp, pt, ConstraintType.NUMBER);
return pt;
} else {
moduleImports.add(PYDANTIC, "StrictInt");
Expand All @@ -2029,14 +1995,8 @@ private PythonType binaryType(IJsonSchemaValidationProperties cp) {
// e.g. conbytes(min_length=2, max_length=10)
bytest.constrain("strict", true);
strt.constrain("strict", true);
if (cp.getMaxLength() != null) {
bytest.constrain("max_length", cp.getMaxLength());
strt.constrain("max_length", cp.getMaxLength());
}
if (cp.getMinLength() != null) {
bytest.constrain("min_length", cp.getMinLength());
strt.constrain("min_length", cp.getMinLength());
}
ConstraintApplier.applyConstraints(cp, bytest, ConstraintType.BINARY);
ConstraintApplier.applyConstraints(cp, strt, ConstraintType.BINARY);
if (cp.getPattern() != null) {
moduleImports.add(PYDANTIC, "field_validator");
// use validator instead as regex doesn't support flags, e.g. IGNORECASE
Expand Down Expand Up @@ -2097,7 +2057,7 @@ private PythonType decimalType(IJsonSchemaValidationProperties cp) {
if (cp.getHasValidation()) {
// e.g. condecimal(ge=10, le=100, strict=True)
pt.constrain("strict", true);
applyConstraints(pt, cp);
ConstraintApplier.applyConstraints(cp, pt, ConstraintType.NUMBER);
}

return pt;
Expand Down Expand Up @@ -2315,26 +2275,6 @@ private PythonType getType(CodegenParameter cp) {
return result;
}

private void applyConstraints(PythonType pythonType, IJsonSchemaValidationProperties cp) {
if (cp.getMaximum() != null) {
if (cp.getExclusiveMaximum()) {
pythonType.constrain(LESS_THAN, cp.getMaximum(), false);
} else {
pythonType.constrain(LESS_OR_EQUAL_TO, cp.getMaximum(), false);
}
}
if (cp.getMinimum() != null) {
if (cp.getExclusiveMinimum()) {
pythonType.constrain(GREATER_THAN, cp.getMinimum(), false);
} else {
pythonType.constrain(GREATER_OR_EQUAL_TO, cp.getMinimum(), false);
}
}
if (cp.getMultipleOf() != null) {
pythonType.constrain("multiple_of", cp.getMultipleOf());
}
}

private String finalizeType(CodegenParameter cp, PythonType pt) {
if (!cp.required || cp.isNullable) {
moduleImports.add("typing", "Optional");
Expand All @@ -2351,4 +2291,94 @@ private String finalizeType(CodegenParameter cp, PythonType pt) {
return pt.asTypeConstraintWithAnnotations(moduleImports);
}
}

private enum ConstraintType {
ARRAY, BINARY, STRING, NUMBER, ROUNDED_NUMBER
}

private static class ConstraintApplier {

private static final String MAX_LENGTH = "max_length";
private static final String MIN_LENGTH = "min_length";
private static final String LESS_THAN = "lt";
private static final String GREATER_THAN = "gt";
private static final String GREATER_OR_EQUAL_TO = "ge";
private static final String LESS_OR_EQUAL_TO = "le";
private static final String MULTIPLE_OF = "multiple_of";

static void applyConstraints(IJsonSchemaValidationProperties cp, PythonType pythonType, ConstraintType type) {
if (cp == null || pythonType == null) {
return;
}

switch (type) {
case ARRAY:
if (cp.getMaxItems() != null) {
pythonType.constrain(MAX_LENGTH, cp.getMaxItems());
}
if (cp.getMinItems() != null) {
pythonType.constrain(MIN_LENGTH, cp.getMinItems());
}
case BINARY:
case STRING:
if (cp.getMaxLength() != null) {
pythonType.constrain(MAX_LENGTH, cp.getMaxLength());
}
if (cp.getMinLength() != null) {
pythonType.constrain(MIN_LENGTH, cp.getMinLength());
}
break;
case NUMBER:
case ROUNDED_NUMBER:
applyNumericConstraints(cp, pythonType, type == ConstraintType.ROUNDED_NUMBER);
break;
}
}

private static void applyNumericConstraints(IJsonSchemaValidationProperties cp, PythonType pythonType, boolean rounded) {
if (cp.getMaximum() != null) {
if (rounded) {
if (cp.getExclusiveMaximum()) {
pythonType.constrain(LESS_THAN, ceilValue(cp.getMaximum())); // e.g. < 7.59 => < 8
} else {
pythonType.constrain(LESS_OR_EQUAL_TO, floorValue(cp.getMaximum())); // e.g. <= 7.59 => <= 7
}
} else {
if (cp.getExclusiveMaximum()) {
pythonType.constrain(LESS_THAN, cp.getMaximum(), false);
} else {
pythonType.constrain(LESS_OR_EQUAL_TO, cp.getMaximum(), false);
}
}
}

if (cp.getMinimum() != null) {
if (rounded) {
if (cp.getExclusiveMinimum()) {
pythonType.constrain(GREATER_THAN, floorValue(cp.getMinimum())); // e.g. > 7.59 => > 7
} else {
pythonType.constrain(GREATER_OR_EQUAL_TO, ceilValue(cp.getMinimum())); // e.g. >= 7.59 => >= 8
}
} else {
if (cp.getExclusiveMinimum()) {
pythonType.constrain(GREATER_THAN, cp.getMinimum(), false);
} else {
pythonType.constrain(GREATER_OR_EQUAL_TO, cp.getMinimum(), false);
}
}
}

if (!rounded && cp.getMultipleOf() != null) {
pythonType.constrain(MULTIPLE_OF, cp.getMultipleOf());
}
}

private static int ceilValue(String value) {
return (int) Math.ceil(Double.parseDouble(value));
}

private static int floorValue(String value) {
return (int) Math.floor(Double.parseDouble(value));
}
}
}
Loading