Skip to content

Commit b844dd5

Browse files
committed
refactor: Hoist parameter coercion into ValueCoercion helper
1 parent 2f96ae2 commit b844dd5

3 files changed

Lines changed: 132 additions & 43 deletions

File tree

src/main/java/com/retailsvc/http/internal/RequestPreparationFilter.java

Lines changed: 1 addition & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
import com.retailsvc.http.spec.Parameter;
1212
import com.retailsvc.http.spec.RequestBody;
1313
import com.retailsvc.http.spec.Spec;
14-
import com.retailsvc.http.spec.schema.BooleanSchema;
15-
import com.retailsvc.http.spec.schema.IntegerSchema;
16-
import com.retailsvc.http.spec.schema.NumberSchema;
17-
import com.retailsvc.http.spec.schema.Schema;
1814
import com.retailsvc.http.validate.ValidationError;
1915
import com.retailsvc.http.validate.Validator;
2016
import com.sun.net.httpserver.Filter;
@@ -128,48 +124,10 @@ private void validateParameters(
128124
}
129125
continue;
130126
}
131-
validator.validate(coerceParameterValue(value, p.schema(), pointer), p.schema(), pointer);
127+
validator.validate(ValueCoercion.coerce(value, p.schema(), pointer), p.schema(), pointer);
132128
}
133129
}
134130

135-
/**
136-
* Converts a raw parameter string into a typed value matching the schema's primitive kind, so the
137-
* validator (which is faithful to JSON Schema {@code type} semantics) sees the value the spec
138-
* describes rather than its string serialization. Strings that fail to parse are passed through
139-
* unchanged so the validator surfaces a {@code type} error with the original input.
140-
*/
141-
private static Object coerceParameterValue(String raw, Schema schema, String pointer) {
142-
return switch (schema) {
143-
case IntegerSchema _ -> {
144-
try {
145-
yield Long.parseLong(raw);
146-
} catch (NumberFormatException _) {
147-
throw new ValidationException(
148-
new ValidationError(pointer, "type", "expected integer", raw));
149-
}
150-
}
151-
case NumberSchema _ -> {
152-
try {
153-
yield Double.parseDouble(raw);
154-
} catch (NumberFormatException _) {
155-
throw new ValidationException(
156-
new ValidationError(pointer, "type", "expected number", raw));
157-
}
158-
}
159-
case BooleanSchema _ -> {
160-
if ("true".equals(raw)) {
161-
yield Boolean.TRUE;
162-
}
163-
if ("false".equals(raw)) {
164-
yield Boolean.FALSE;
165-
}
166-
throw new ValidationException(
167-
new ValidationError(pointer, "type", "expected boolean", raw));
168-
}
169-
default -> raw;
170-
};
171-
}
172-
173131
private Object validateAndParseBody(HttpExchange exchange, Operation op, byte[] body) {
174132
Optional<RequestBody> rb = op.requestBody();
175133
if (rb.isEmpty()) {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.retailsvc.http.internal;
2+
3+
import com.retailsvc.http.ValidationException;
4+
import com.retailsvc.http.spec.schema.BooleanSchema;
5+
import com.retailsvc.http.spec.schema.IntegerSchema;
6+
import com.retailsvc.http.spec.schema.NumberSchema;
7+
import com.retailsvc.http.spec.schema.Schema;
8+
import com.retailsvc.http.validate.ValidationError;
9+
10+
/** Coerces wire-format strings (parameters, form-field values) to the target schema type. */
11+
public final class ValueCoercion {
12+
13+
private ValueCoercion() {}
14+
15+
public static Object coerce(String raw, Schema schema, String pointer) {
16+
return switch (schema) {
17+
case IntegerSchema _ -> {
18+
try {
19+
yield Long.parseLong(raw);
20+
} catch (NumberFormatException _) {
21+
throw new ValidationException(
22+
new ValidationError(pointer, "type", "expected integer", raw));
23+
}
24+
}
25+
case NumberSchema _ -> {
26+
try {
27+
yield Double.parseDouble(raw);
28+
} catch (NumberFormatException _) {
29+
throw new ValidationException(
30+
new ValidationError(pointer, "type", "expected number", raw));
31+
}
32+
}
33+
case BooleanSchema _ -> {
34+
if ("true".equals(raw)) {
35+
yield Boolean.TRUE;
36+
}
37+
if ("false".equals(raw)) {
38+
yield Boolean.FALSE;
39+
}
40+
throw new ValidationException(
41+
new ValidationError(pointer, "type", "expected boolean", raw));
42+
}
43+
default -> raw;
44+
};
45+
}
46+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.retailsvc.http.internal;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
6+
import com.retailsvc.http.ValidationException;
7+
import com.retailsvc.http.spec.schema.BooleanSchema;
8+
import com.retailsvc.http.spec.schema.IntegerSchema;
9+
import com.retailsvc.http.spec.schema.NumberSchema;
10+
import com.retailsvc.http.spec.schema.Schema;
11+
import com.retailsvc.http.spec.schema.StringSchema;
12+
import com.retailsvc.http.spec.schema.TypeName;
13+
import java.util.Map;
14+
import java.util.Set;
15+
import org.junit.jupiter.api.Test;
16+
17+
class ValueCoercionTest {
18+
19+
private final Schema intSchema = anIntegerSchema();
20+
private final Schema numSchema = aNumberSchema();
21+
private final Schema boolSchema = aBooleanSchema();
22+
private final Schema strSchema = aStringSchema();
23+
24+
@Test
25+
void coercesIntegerString() {
26+
assertThat(ValueCoercion.coerce("42", intSchema, "/a")).isEqualTo(42L);
27+
}
28+
29+
@Test
30+
void coercesNumberString() {
31+
assertThat(ValueCoercion.coerce("3.14", numSchema, "/a")).isEqualTo(3.14);
32+
}
33+
34+
@Test
35+
void coercesBooleanTrue() {
36+
assertThat(ValueCoercion.coerce("true", boolSchema, "/a")).isEqualTo(Boolean.TRUE);
37+
}
38+
39+
@Test
40+
void coercesBooleanFalse() {
41+
assertThat(ValueCoercion.coerce("false", boolSchema, "/a")).isEqualTo(Boolean.FALSE);
42+
}
43+
44+
@Test
45+
void leavesStringSchemaUntouched() {
46+
assertThat(ValueCoercion.coerce("hello", strSchema, "/a")).isEqualTo("hello");
47+
}
48+
49+
@Test
50+
void integerCoercionFailureThrowsValidationException() {
51+
assertThatThrownBy(() -> ValueCoercion.coerce("abc", intSchema, "/a"))
52+
.isInstanceOf(ValidationException.class)
53+
.extracting("error.pointer", "error.keyword")
54+
.containsExactly("/a", "type");
55+
}
56+
57+
@Test
58+
void numberCoercionFailureThrowsValidationException() {
59+
assertThatThrownBy(() -> ValueCoercion.coerce("not-a-number", numSchema, "/x"))
60+
.isInstanceOf(ValidationException.class);
61+
}
62+
63+
@Test
64+
void booleanCoercionFailureThrowsValidationException() {
65+
assertThatThrownBy(() -> ValueCoercion.coerce("yes", boolSchema, "/b"))
66+
.isInstanceOf(ValidationException.class);
67+
}
68+
69+
private static IntegerSchema anIntegerSchema() {
70+
return new IntegerSchema(
71+
Set.of(TypeName.INTEGER), null, null, null, null, null, null, Map.of());
72+
}
73+
74+
private static NumberSchema aNumberSchema() {
75+
return new NumberSchema(Set.of(TypeName.NUMBER), null, null, null, null, null, null, Map.of());
76+
}
77+
78+
private static BooleanSchema aBooleanSchema() {
79+
return new BooleanSchema(Set.of(TypeName.BOOLEAN), Map.of());
80+
}
81+
82+
private static StringSchema aStringSchema() {
83+
return new StringSchema(Set.of(TypeName.STRING), null, null, null, null, null, Map.of());
84+
}
85+
}

0 commit comments

Comments
 (0)