StatusLine.java

1
package com.renomad.minum.web;
2
3
import java.util.Arrays;
4
import java.util.regex.Matcher;
5
import java.util.regex.Pattern;
6
7
import static com.renomad.minum.utils.Invariants.mustBeTrue;
8
9
/**
10
 * This class represents the text that is sent back in a {@link Response}
11
 */
12
public record StatusLine(StatusCode status, HttpVersion version, String rawValue) {
13
14
    static final StatusLine EMPTY = new StatusLine(StatusCode.NULL, HttpVersion.NONE, "");
15
16
    /**
17
     * This is the regex used to analyze a status line sent by the server and
18
     * read by the client.  Servers will send messages like: "HTTP/1.1 200 OK" or "HTTP/1.1 500 Internal Server Error"
19
     */
20
    static final String statusLinePattern = "^HTTP/(...) (\\d{3}) (.*)$";
21
    static final Pattern statusLineRegex = Pattern.compile(statusLinePattern);
22
23
    /**
24
     * See <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status">Status Codes</a>
25
     */
26
    public enum StatusCode{
27
28
        /* Information responses */
29
        CODE_100_CONTINUE(100, "CONTINUE"),
30
        CODE_101_SWITCHING_PROTOCOLS(101, "SWITCHING PROTOCOLS"),
31
        CODE_102_PROCESSING(102, "PROCESSING"),
32
        CODE_103_EARLY_HINTS(103, "EARLY HINTS"),
33
34
35
        /* Successful responses (200 – 299) */
36
37
        CODE_200_OK(200, "OK"),
38
        CODE_201_CREATED(201, "CREATED"),
39
        CODE_202_ACCEPTED(202, "ACCEPTED"),
40
        CODE_203_NON_AUTHORITATIVE_INFORMATION(203, "NON-AUTHORITATIVE INFORMATION"),
41
        CODE_204_NO_CONTENT(204, "NO CONTENT"),
42
        CODE_205_RESET_CONTENT(205, "RESET CONTENT"),
43
        CODE_206_PARTIAL_CONTENT(206, "PARTIAL CONTENT"),
44
        CODE_207_MULTI_STATUS(207, "MULTI-STATUS"),
45
        CODE_208_ALREADY_REPORTED(208, "ALREADY REPORTED"),
46
        CODE_226_IM_USED(226, "IM USED"),
47
48
        /* Redirection messages (300 – 399) */
49
50
        CODE_300_MULTIPLE_CHOICES(300, "MULTIPLE CHOICES"),
51
        CODE_301_MOVED_PERMANENTLY(301, "MOVED PERMANENTLY"),
52
        CODE_302_FOUND(302, "FOUND"),
53
54
        /**
55
         * Used a lot after receiving a post response.  The pattern is to
56
         * receive the post, then redirect to a new page. See <a href="https://en.wikipedia.org/wiki/Post/Redirect/Get">...</a>
57
         */
58
        CODE_303_SEE_OTHER(303, "SEE OTHER"),
59
        CODE_304_NOT_MODIFIED(304, "NOT MODIFIED"),
60
        CODE_305_USE_PROXY(305, "USE PROXY"),
61
        CODE_306_UNUSED(306, "UNUSED"),
62
        CODE_307_TEMPORARY_REDIRECT(307, "TEMPORARY REDIRECT"),
63
        CODE_308_PERMANENT_REDIRECT(308, "PERMANENT REDIRECT"),
64
65
        /* Client error responses (400 – 499) */
66
67
        CODE_400_BAD_REQUEST(400, "BAD REQUEST"),
68
        CODE_401_UNAUTHORIZED(401, "UNAUTHORIZED"),
69
        CODE_402_PAYMENT_REQUIRED(402, "PAYMENT REQUIRED"),
70
        CODE_403_FORBIDDEN(403, "FORBIDDEN"),
71
        CODE_404_NOT_FOUND(404, "NOT FOUND"),
72
        CODE_405_METHOD_NOT_ALLOWED(405, "METHOD NOT ALLOWED"),
73
        CODE_406_NOT_ACCEPTABLE(406, "NOT ACCEPTABLE"),
74
        CODE_407_PROXY_AUTHENTICATION_REQUIRED(407, "PROXY AUTHENTICATION REQUIRED"),
75
        CODE_408_REQUEST_TIMEOUT(408, "REQUEST TIMEOUT"),
76
        CODE_409_CONFLICT(409, "CONFLICT"),
77
        CODE_410_GONE(410, "GONE"),
78
        CODE_411_LENGTH_REQUIRED(411, "LENGTH REQUIRED"),
79
        CODE_412_PRECONDITION_FAILED(412, "PRECONDITION FAILED"),
80
        CODE_413_PAYLOAD_TOO_LARGE(413, "PAYLOAD TOO LARGE"),
81
        CODE_414_URI_TOO_LONG(414, "URI TOO LONG"),
82
        CODE_415_UNSUPPORTED_MEDIA_TYPE(415, "UNSUPPORTED MEDIA TYPE"),
83
        CODE_416_RANGE_NOT_SATISFIABLE(416, "RANGE NOT SATISFIABLE"),
84
        CODE_417_EXPECTATION_FAILED(417, "EXPECTATION FAILED"),
85
        CODE_418_IM_A_TEAPOT(418, "IM A TEAPOT"),
86
        CODE_421_MISDIRECTED_REQUEST(421, "MISDIRECTED REQUEST"),
87
        CODE_422_UNPROCESSABLE_CONTENT(422, "UNPROCESSABLE CONTENT"),
88
        CODE_423_LOCKED(423, "LOCKED"),
89
        CODE_424_FAILED_DEPENDENCY(424, "FAILED DEPENDENCY"),
90
        CODE_425_TOO_EARLY(425, "TOO EARLY"),
91
        CODE_426_UPGRADE_REQUIRED(426, "UPGRADE REQUIRED"),
92
        CODE_428_PRECONDITION_REQUIRED(428, "PRECONDITION REQUIRED"),
93
        CODE_429_TOO_MANY_REQUESTS(429, "TOO MANY REQUESTS"),
94
        CODE_431_REQUEST_HEADER_FIELDS_TOO_LARGE(431, "REQUEST HEADER FIELDS TOO LARGE"),
95
        CODE_451_UNAVAILABLE_FOR_LEGAL_REASONS(451, "UNAVAILABLE FOR LEGAL REASONS"),
96
97
        /* Server error responses (500 – 599) */
98
99
        CODE_500_INTERNAL_SERVER_ERROR(500, "INTERNAL SERVER ERROR"),
100
        CODE_501_NOT_IMPLEMENTED(501, "NOT IMPLEMENTED"),
101
        CODE_502_BAD_GATEWAY(502, "BAD GATEWAY"),
102
        CODE_503_SERVICE_UNAVAILABLE(503, "SERVICE UNAVAILABLE"),
103
        CODE_504_GATEWAY_TIMEOUT(504, "GATEWAY TIMEOUT"),
104
        CODE_505_HTTP_VERSION_NOT_SUPPORTED(505, "HTTP VERSION NOT SUPPORTED"),
105
        CODE_506_VARIANT_ALSO_NEGOTIATES(506, "VARIANT ALSO NEGOTIATES"),
106
        CODE_507_INSUFFICIENT_STORAGE(507, "INSUFFICIENT STORAGE"),
107
        CODE_508_LOOP_DETECTED(508, "LOOP DETECTED"),
108
        CODE_510_NOT_EXTENDED(510, "NOT EXTENDED"),
109
        CODE_511_NETWORK_AUTHENTICATION_REQUIRED(511, "NETWORK AUTHENTICATION REQUIRED"),
110
111
        /**
112
         * The null object, meant to represent "no status code"
113
         */
114
        NULL(0, "NULL OBJECT")
115
        ;
116
117
        public final int code;
118
        public final String shortDescription;
119
120
        StatusCode(int code, String shortDescription) {
121
            this.code = code;
122
            this.shortDescription = shortDescription;
123
        }
124
125
        static StatusCode findByCode(int code) {
126 1 1. findByCode : replaced return value with null for com/renomad/minum/web/StatusLine$StatusCode::findByCode → KILLED
            return Arrays.stream(StatusCode.values())
127 2 1. lambda$findByCode$0 : replaced boolean return with true for com/renomad/minum/web/StatusLine$StatusCode::lambda$findByCode$0 → KILLED
2. lambda$findByCode$0 : negated conditional → KILLED
                    .filter(x -> x.code == code)
128
                    .findFirst()
129
                    .orElseThrow();
130
        }
131
    }
132
133
    /**
134
     * Parses a string value of a status line from an HTTP
135
     * server.  If the input value is null or empty, we'll
136
     * return a {@link StatusLine} with null-object values
137
     */
138
    public static StatusLine extractStatusLine(String value) {
139 2 1. extractStatusLine : negated conditional → KILLED
2. extractStatusLine : negated conditional → KILLED
        if (value == null || value.isBlank()) {
140 1 1. extractStatusLine : replaced return value with null for com/renomad/minum/web/StatusLine::extractStatusLine → KILLED
            return StatusLine.EMPTY;
141
        }
142
        Matcher mr = StatusLine.statusLineRegex.matcher(value);
143
        mustBeTrue(mr.matches(), String.format("%s must match the statusLinePattern: %s", value, statusLinePattern));
144
        String version = mr.group(1);
145
        HttpVersion httpVersion = switch (version) {
146
            case "1.1" -> HttpVersion.ONE_DOT_ONE;
147
            case "1.0" -> HttpVersion.ONE_DOT_ZERO;
148
            default -> throw new WebServerException(String.format("HTTP version was not an acceptable value. Given: %s", version));
149
        };
150
        StatusCode status = StatusCode.findByCode(Integer.parseInt(mr.group(2)));
151
152 1 1. extractStatusLine : replaced return value with null for com/renomad/minum/web/StatusLine::extractStatusLine → KILLED
        return new StatusLine(status, httpVersion, value);
153
    }
154
}

Mutations

126

1.1
Location : findByCode
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/StatusLine$StatusCode::findByCode → KILLED

127

1.1
Location : lambda$findByCode$0
Killed by : com.renomad.minum.web.WebTests
replaced boolean return with true for com/renomad/minum/web/StatusLine$StatusCode::lambda$findByCode$0 → KILLED

2.2
Location : lambda$findByCode$0
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

139

1.1
Location : extractStatusLine
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

2.2
Location : extractStatusLine
Killed by : com.renomad.minum.web.WebTests
negated conditional → KILLED

140

1.1
Location : extractStatusLine
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/StatusLine::extractStatusLine → KILLED

152

1.1
Location : extractStatusLine
Killed by : com.renomad.minum.web.WebTests
replaced return value with null for com/renomad/minum/web/StatusLine::extractStatusLine → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0