@@ -17,41 +17,77 @@ public final class ProblemDetailRenderer {
1717 /** Initial capacity of the JSON buffer; sized for a typical problem-detail document. */
1818 private static final int INITIAL_BUFFER_CAPACITY = 128 ;
1919
20+ /** Codepoints below this value are control characters and must be unicode-escaped in JSON. */
21+ private static final int FIRST_PRINTABLE_ASCII = 0x20 ;
22+
2023 private ProblemDetailRenderer () {}
2124
2225 public static String render (int status , String title , String detail ) {
2326 StringBuilder out = new StringBuilder (INITIAL_BUFFER_CAPACITY );
2427 out .append ('{' );
25- JsonStrings . appendStringField (out , "type" , PROBLEM_TYPE );
28+ appendStringField (out , "type" , PROBLEM_TYPE );
2629 out .append (',' );
27- JsonStrings . appendStringField (out , "title" , title );
30+ appendStringField (out , "title" , title );
2831 out .append (',' );
2932 appendIntField (out , "status" , status );
3033 out .append (',' );
31- JsonStrings . appendStringField (out , "detail" , detail );
34+ appendStringField (out , "detail" , detail );
3235 out .append ('}' );
3336 return out .toString ();
3437 }
3538
3639 public static String render (ValidationError error ) {
3740 StringBuilder out = new StringBuilder (INITIAL_BUFFER_CAPACITY );
3841 out .append ('{' );
39- JsonStrings . appendStringField (out , "type" , PROBLEM_TYPE );
42+ appendStringField (out , "type" , PROBLEM_TYPE );
4043 out .append (',' );
41- JsonStrings . appendStringField (out , "title" , PROBLEM_TITLE );
44+ appendStringField (out , "title" , PROBLEM_TITLE );
4245 out .append (',' );
4346 appendIntField (out , "status" , PROBLEM_STATUS );
4447 out .append (',' );
45- JsonStrings . appendStringField (out , "detail" , error .message ());
48+ appendStringField (out , "detail" , error .message ());
4649 out .append (',' );
47- JsonStrings . appendStringField (out , "pointer" , error .pointer ());
50+ appendStringField (out , "pointer" , error .pointer ());
4851 out .append (',' );
49- JsonStrings . appendStringField (out , "keyword" , error .keyword ());
52+ appendStringField (out , "keyword" , error .keyword ());
5053 out .append ('}' );
5154 return out .toString ();
5255 }
5356
57+ private static void appendStringField (StringBuilder out , String name , String value ) {
58+ out .append ('"' ).append (name ).append ("\" :\" " );
59+ appendEscaped (out , value );
60+ out .append ('"' );
61+ }
62+
5463 private static void appendIntField (StringBuilder out , String name , int value ) {
5564 out .append ('"' ).append (name ).append ("\" :" ).append (value );
5665 }
66+
67+ /**
68+ * Appends {@code value} to {@code out} with JSON-string escaping applied. Handles the six
69+ * mandatory escape sequences and emits {@code \uXXXX} for control characters below {@link
70+ * #FIRST_PRINTABLE_ASCII}.
71+ */
72+ private static void appendEscaped (StringBuilder out , String value ) {
73+ for (int i = 0 ; i < value .length (); i ++) {
74+ char c = value .charAt (i );
75+ switch (c ) {
76+ case '\\' -> out .append ("\\ \\ " );
77+ case '"' -> out .append ("\\ \" " );
78+ case '\n' -> out .append ("\\ n" );
79+ case '\r' -> out .append ("\\ r" );
80+ case '\t' -> out .append ("\\ t" );
81+ default -> appendUnicodeOrLiteral (out , c );
82+ }
83+ }
84+ }
85+
86+ private static void appendUnicodeOrLiteral (StringBuilder out , char c ) {
87+ if (c < FIRST_PRINTABLE_ASCII ) {
88+ out .append (String .format ("\\ u%04x" , (int ) c ));
89+ } else {
90+ out .append (c );
91+ }
92+ }
5793}
0 commit comments