Skip to content

Commit 4113fb9

Browse files
committed
feat: Add ContentTypeHeader helper for parsing media-type headers
1 parent b844dd5 commit 4113fb9

2 files changed

Lines changed: 106 additions & 0 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.retailsvc.http.internal;
2+
3+
import java.util.Locale;
4+
import java.util.Optional;
5+
6+
/** Parses {@code Content-Type} header values. */
7+
public final class ContentTypeHeader {
8+
9+
private ContentTypeHeader() {}
10+
11+
/** Returns the bare media type, stripping parameters. {@code null} → {@code application/json}. */
12+
public static String subtype(String header) {
13+
if (header == null) {
14+
return "application/json";
15+
}
16+
int semi = header.indexOf(';');
17+
String bare = (semi < 0 ? header : header.substring(0, semi));
18+
return bare.trim();
19+
}
20+
21+
/** Returns the named parameter value (e.g. {@code charset}), or empty if absent. */
22+
public static Optional<String> parameter(String header, String name) {
23+
if (header == null) {
24+
return Optional.empty();
25+
}
26+
String target = name.toLowerCase(Locale.ROOT);
27+
int semi = header.indexOf(';');
28+
if (semi < 0) {
29+
return Optional.empty();
30+
}
31+
String[] parts = header.substring(semi + 1).split(";");
32+
for (String p : parts) {
33+
String trimmed = p.trim();
34+
int eq = trimmed.indexOf('=');
35+
if (eq <= 0) {
36+
continue;
37+
}
38+
String key = trimmed.substring(0, eq).trim().toLowerCase(Locale.ROOT);
39+
if (!key.equals(target)) {
40+
continue;
41+
}
42+
String value = trimmed.substring(eq + 1).trim();
43+
if (value.length() >= 2 && value.startsWith("\"") && value.endsWith("\"")) {
44+
value = value.substring(1, value.length() - 1);
45+
}
46+
return Optional.of(value);
47+
}
48+
return Optional.empty();
49+
}
50+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.retailsvc.http.internal;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import org.junit.jupiter.api.Test;
6+
7+
class ContentTypeHeaderTest {
8+
9+
@Test
10+
void subtypeReturnsBareMediaType() {
11+
assertThat(ContentTypeHeader.subtype("application/json")).isEqualTo("application/json");
12+
}
13+
14+
@Test
15+
void subtypeStripsParameters() {
16+
assertThat(ContentTypeHeader.subtype("text/plain; charset=utf-8")).isEqualTo("text/plain");
17+
}
18+
19+
@Test
20+
void subtypeTrimsWhitespace() {
21+
assertThat(ContentTypeHeader.subtype(" application/json ")).isEqualTo("application/json");
22+
}
23+
24+
@Test
25+
void subtypeDefaultsToApplicationJsonWhenNull() {
26+
assertThat(ContentTypeHeader.subtype(null)).isEqualTo("application/json");
27+
}
28+
29+
@Test
30+
void parameterReturnsValue() {
31+
assertThat(ContentTypeHeader.parameter("text/plain; charset=iso-8859-1", "charset"))
32+
.contains("iso-8859-1");
33+
}
34+
35+
@Test
36+
void parameterUnquotesValue() {
37+
assertThat(ContentTypeHeader.parameter("text/plain; charset=\"utf-8\"", "charset"))
38+
.contains("utf-8");
39+
}
40+
41+
@Test
42+
void parameterReturnsEmptyWhenMissing() {
43+
assertThat(ContentTypeHeader.parameter("text/plain", "charset")).isEmpty();
44+
}
45+
46+
@Test
47+
void parameterNameMatchIsCaseInsensitive() {
48+
assertThat(ContentTypeHeader.parameter("text/plain; CHARSET=utf-8", "charset"))
49+
.contains("utf-8");
50+
}
51+
52+
@Test
53+
void parameterReturnsEmptyForNullHeader() {
54+
assertThat(ContentTypeHeader.parameter(null, "charset")).isEmpty();
55+
}
56+
}

0 commit comments

Comments
 (0)