Skip to content

Commit 4e2e5be

Browse files
committed
Introduce HttpStatusCode
RESTCONF fall into two categories a success, indicating a 20x status or a failure, indicating a 40x status. RestconfDocumentedException acts as a wrapper for the latter case, with the additional twist of being able to report multiple errors via the 'yang-errors' template. All failure modes in RESTCONF end up being driven by a YANG ErrorTag, hence our server-size response structure really wants to be split along success/failure lines. This patch takes the first step towards that split, defining a restconf.api.HttpStatusCode, which acts as a semantic capture of a HTTP Status Code and use that in error mapping logic. While we are visiting here, we make the status code for data-missing configurable for each northbound instance -- rather than our previous use of a global property. JIRA: NETCONF-1188 Change-Id: I1365256f9fad4ffe66358e6e9da4dfa337a755fd Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
1 parent 2bb1482 commit 4e2e5be

File tree

15 files changed

+434
-118
lines changed

15 files changed

+434
-118
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/*
2+
* Copyright (c) 2024 PANTHEON.tech, s.r.o. and others. All rights reserved.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v1.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v10.html
7+
*/
8+
package org.opendaylight.restconf.api;
9+
10+
import com.google.common.annotations.Beta;
11+
import org.eclipse.jdt.annotation.NonNullByDefault;
12+
import org.eclipse.jdt.annotation.Nullable;
13+
14+
/**
15+
* A simple DTO definitiong an <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15">HTTP Status Code</a>. Integer
16+
* values used here are assigned through the
17+
* <a href="https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml">IANA Status Code Registry</a>.
18+
*/
19+
@Beta
20+
@NonNullByDefault
21+
public final class HttpStatusCode {
22+
/**
23+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.1">200 OK</a>.
24+
*/
25+
public static final HttpStatusCode OK = new HttpStatusCode(200, "OK");
26+
/**
27+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.2">201 Created</a>.
28+
*/
29+
public static final HttpStatusCode CREATED = new HttpStatusCode(201, "Created");
30+
/**
31+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.3">202 Accepted</a>.
32+
*/
33+
public static final HttpStatusCode ACCEPTED = new HttpStatusCode(202, "Accepted");
34+
/**
35+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.4">203 Non-Authoritative Information</a>.
36+
*/
37+
public static final HttpStatusCode NON_AUTHORITATIVE_INFORMATION =
38+
new HttpStatusCode(203, "Non-Authoritative Information");
39+
/**
40+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.5">204 No Content</a>.
41+
*/
42+
public static final HttpStatusCode NO_CONTENT = new HttpStatusCode(204, "No Content");
43+
/**
44+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.6">205 Reset Content</a>.
45+
*/
46+
public static final HttpStatusCode RESET_CONTENT = new HttpStatusCode(205, "Reset Content");
47+
/**
48+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.3.7">206 Partial Content</a>.
49+
*/
50+
public static final HttpStatusCode PARTIAL_CONTENT = new HttpStatusCode(206, "Partial Content");
51+
/**
52+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.1">300 Multiple Choices</a>.
53+
*/
54+
public static final HttpStatusCode MULTIPLE_CHOICES = new HttpStatusCode(300, "Multiple Choices");
55+
/**
56+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.2">301 Moved Permanently</a>.
57+
*/
58+
public static final HttpStatusCode MOVED_PERMANENTLY = new HttpStatusCode(301, "Moved Permanently");
59+
/**
60+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3">302 Found</a>.
61+
*/
62+
public static final HttpStatusCode FOUND = new HttpStatusCode(302, "Found");
63+
/**
64+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.4">303 See Other</a>.
65+
*/
66+
public static final HttpStatusCode SEE_OTHER = new HttpStatusCode(303, "See Other");
67+
/**
68+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.5">304 Not Modified</a>.
69+
*/
70+
public static final HttpStatusCode NOT_MODIFIED = new HttpStatusCode(304, "Not Modified");
71+
/**
72+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.6">305 Use Proxy</a>.
73+
*/
74+
public static final HttpStatusCode USE_PROXY = new HttpStatusCode(305, "Use Proxy");
75+
/**
76+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.8">307 Temporary Redirect</a>.
77+
*/
78+
public static final HttpStatusCode TEMPORARY_REDIRECT = new HttpStatusCode(307, "Temporary Redirect");
79+
/**
80+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.4.9">308 Permanent Redirect</a>.
81+
*/
82+
public static final HttpStatusCode PERMANENT_REDIRECT = new HttpStatusCode(308, "Permanent Redirect");
83+
/**
84+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.1">400 Bad Request</a>.
85+
*/
86+
public static final HttpStatusCode BAD_REQUEST = new HttpStatusCode(400, "Bad Request");
87+
/**
88+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.2">401 Unauthorized</a>.
89+
*/
90+
public static final HttpStatusCode UNAUTHORIZED = new HttpStatusCode(401, "Unauthorized");
91+
/**
92+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.3">402 Payment Required</a>.
93+
*/
94+
public static final HttpStatusCode PAYMENT_REQUIRED = new HttpStatusCode(402, "Payment Required");
95+
/**
96+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.4">403 Forbidden</a>.
97+
*/
98+
public static final HttpStatusCode FORBIDDEN = new HttpStatusCode(403, "Forbidden");
99+
/**
100+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.5">404 Not Found</a>.
101+
*/
102+
public static final HttpStatusCode NOT_FOUND = new HttpStatusCode(404, "Not Found");
103+
/**
104+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.6">405 Method Not Allowed</a>.
105+
*/
106+
public static final HttpStatusCode METHOD_NOT_ALLOWED = new HttpStatusCode(405, "Method Not Allowed");
107+
/**
108+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.7">406 Not Acceptable</a>.
109+
*/
110+
public static final HttpStatusCode NOT_ACCEPTABLE = new HttpStatusCode(406, "Not Acceptable");
111+
/**
112+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.8">407 Proxy Authentication Required</a>.
113+
*/
114+
public static final HttpStatusCode PROXY_AUTHENTICATION_REQUIRED =
115+
new HttpStatusCode(407, "Proxy Authentication Required");
116+
/**
117+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.9">408 Request Timeout</a>.
118+
*/
119+
public static final HttpStatusCode REQUEST_TIMEOUT = new HttpStatusCode(408, "Request Timeout");
120+
/**
121+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.10">409 Conflict</a>.
122+
*/
123+
public static final HttpStatusCode CONFLICT = new HttpStatusCode(409, "Conflict");
124+
/**
125+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.11">410 Gone</a>.
126+
*/
127+
public static final HttpStatusCode GONE = new HttpStatusCode(410, "Gone");
128+
/**
129+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.12">411 Length Required</a>.
130+
*/
131+
public static final HttpStatusCode LENGTH_REQUIRED = new HttpStatusCode(411, "Length Required");
132+
/**
133+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.13">412 Precondition Failed</a>.
134+
*/
135+
public static final HttpStatusCode PRECONDITION_FAILED = new HttpStatusCode(412, "Precondition Failed");
136+
/**
137+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.14">413 Content Too Large</a>.
138+
*/
139+
public static final HttpStatusCode CONTENT_TOO_LARGE = new HttpStatusCode(413, "Content Too Large");
140+
/**
141+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.15">414 Content Too Long</a>.
142+
*/
143+
public static final HttpStatusCode URI_TOO_LONG = new HttpStatusCode(414, "URI Too Long");
144+
/**
145+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.16">415 Unsupported Media Type</a>.
146+
*/
147+
public static final HttpStatusCode UNSUPPORTED_MEDIA_TYPE = new HttpStatusCode(415, "Unsupported Media Type");
148+
/**
149+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.17">416 Requested Range Not Satisfiable</a>.
150+
*/
151+
public static final HttpStatusCode REQUESTED_RANGE_NOT_SATISFIABLE =
152+
new HttpStatusCode(416, "Requested Range Not Satisfiable");
153+
/**
154+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.18">417 Expectation Failed</a>.
155+
*/
156+
public static final HttpStatusCode EXPECTATION_FAILED = new HttpStatusCode(417, "Expectation Failed");
157+
/**
158+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.19">418 (Unused)</a>.
159+
*/
160+
@Deprecated(forRemoval = true)
161+
public static final HttpStatusCode I_M_A_TEAPOT = new HttpStatusCode(418, "I'm a teapot");
162+
/**
163+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.20">421 Misdirected Request</a>.
164+
*/
165+
public static final HttpStatusCode MISDIRECTED_REQUEST = new HttpStatusCode(421, "Misdirected Request");
166+
/**
167+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.21">422 Unprocessable Content</a>.
168+
*/
169+
public static final HttpStatusCode UNPROCESSABLE_CONTENT = new HttpStatusCode(422, "Unprocessable Content");
170+
/**
171+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.5.22">426 Upgrade Required</a>.
172+
*/
173+
public static final HttpStatusCode UPGRADE_REQUIRED = new HttpStatusCode(426, "Upgrade Required");
174+
/**
175+
* <a href="https://www.rfc-editor.org/rfc/rfc6585#section-3">428 Precondition Required</a>.
176+
*/
177+
public static final HttpStatusCode PRECONDITION_REQUIRED = new HttpStatusCode(428, "Precondition Required");
178+
/**
179+
* <a href="https://www.rfc-editor.org/rfc/rfc6585#section-4">429 Too Many Requests</a>.
180+
*/
181+
public static final HttpStatusCode TOO_MANY_REQUESTS = new HttpStatusCode(429, "Too Many Requests");
182+
/**
183+
* <a href="https://www.rfc-editor.org/rfc/rfc6585#section-5">431 Request Header Fields Too Large</a>.
184+
*/
185+
public static final HttpStatusCode REQUEST_HEADER_FIELDS_TOO_LARGE =
186+
new HttpStatusCode(431, "Request Header Fields Too Large");
187+
/**
188+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.1">500 Internal Server Error</a>.
189+
*/
190+
public static final HttpStatusCode INTERNAL_SERVER_ERROR = new HttpStatusCode(500, "Internal Server Error");
191+
/**
192+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.2">501 Not Implemented</a>.
193+
*/
194+
public static final HttpStatusCode NOT_IMPLEMENTED = new HttpStatusCode(501, "Not Implemented");
195+
/**
196+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.3">502 Bad Gateway</a>.
197+
*/
198+
public static final HttpStatusCode BAD_GATEWAY = new HttpStatusCode(502, "Bad Gateway");
199+
/**
200+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.4">503 Service Unavailable</a>.
201+
*/
202+
public static final HttpStatusCode SERVICE_UNAVAILABLE = new HttpStatusCode(503, "Service Unavailable");
203+
/**
204+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.5">504 Gateway Timeout</a>.
205+
*/
206+
public static final HttpStatusCode GATEWAY_TIMEOUT = new HttpStatusCode(504, "Gateway Timeout");
207+
/**
208+
* <a href="https://www.rfc-editor.org/rfc/rfc9110#section-15.6.6">505 HTTP Version Not Supported</a>.
209+
*/
210+
public static final HttpStatusCode HTTP_VERSION_NOT_SUPPORTED =
211+
new HttpStatusCode(505, "HTTP Version Not Supported");
212+
/**
213+
* <a href="https://www.rfc-editor.org/rfc/rfc6585#section-6">511 Network Authentication Required</a>.
214+
*/
215+
public static final HttpStatusCode NETWORK_AUTHENTICATION_REQUIRED =
216+
new HttpStatusCode(511, "Network Authentication Required");
217+
218+
private final int code;
219+
private final String phrase;
220+
221+
public HttpStatusCode(final int code, final @Nullable String phrase) {
222+
if (code < 100 || code > 599) {
223+
throw new IllegalArgumentException("Invalid statusCode " + code);
224+
}
225+
this.code = code;
226+
this.phrase = phrase;
227+
}
228+
229+
/**
230+
* Returns the HTTP status code, {@code 100-599}.
231+
*
232+
* @return the HTTP status code
233+
*/
234+
public int code() {
235+
return code;
236+
}
237+
238+
/**
239+
* Returns the phrase or {@code null}.
240+
*
241+
* @return the phrase or {@code null}
242+
*/
243+
public @Nullable String phrase() {
244+
return phrase;
245+
}
246+
247+
@Override
248+
public int hashCode() {
249+
return code;
250+
}
251+
252+
@Override
253+
public boolean equals(final @Nullable Object obj) {
254+
return obj == this || obj instanceof HttpStatusCode other && code == other.code;
255+
}
256+
257+
@Override
258+
public String toString() {
259+
final var sb = new StringBuilder(HttpStatusCode.class.getSimpleName()).append('(').append(code);
260+
if (phrase != null) {
261+
sb.append(' ').append(phrase);
262+
}
263+
return sb.append(')').toString();
264+
}
265+
}

restconf/restconf-nb/src/main/java/org/opendaylight/restconf/nb/jaxrs/JaxRsRestconf.java

+21-12
Original file line numberDiff line numberDiff line change
@@ -38,22 +38,22 @@
3838
import javax.ws.rs.core.MediaType;
3939
import javax.ws.rs.core.Response;
4040
import javax.ws.rs.core.Response.ResponseBuilder;
41-
import javax.ws.rs.core.Response.Status;
4241
import javax.ws.rs.core.UriInfo;
4342
import javax.ws.rs.ext.ParamConverter;
4443
import javax.ws.rs.ext.ParamConverterProvider;
4544
import org.eclipse.jdt.annotation.NonNull;
4645
import org.eclipse.jdt.annotation.Nullable;
4746
import org.opendaylight.restconf.api.ApiPath;
4847
import org.opendaylight.restconf.api.FormatParameters;
48+
import org.opendaylight.restconf.api.HttpStatusCode;
4949
import org.opendaylight.restconf.api.MediaTypes;
5050
import org.opendaylight.restconf.api.QueryParameters;
5151
import org.opendaylight.restconf.api.query.PrettyPrintParam;
5252
import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
5353
import org.opendaylight.restconf.common.errors.RestconfError;
5454
import org.opendaylight.restconf.common.errors.RestconfFuture;
55+
import org.opendaylight.restconf.nb.rfc8040.ErrorTagMapping;
5556
import org.opendaylight.restconf.nb.rfc8040.URLConstants;
56-
import org.opendaylight.restconf.nb.rfc8040.legacy.ErrorTags;
5757
import org.opendaylight.restconf.nb.rfc8040.legacy.NormalizedNodePayload;
5858
import org.opendaylight.restconf.server.api.ConfigurationMetadata;
5959
import org.opendaylight.restconf.server.api.CreateResourceResult;
@@ -121,11 +121,19 @@ public String toString(final ApiPath value) {
121121
private final @NonNull RestconfServer server;
122122
private final @NonNull ServerRequest emptyRequest;
123123
private final @NonNull PrettyPrintParam prettyPrint;
124+
private final @NonNull ErrorTagMapping errorTagMapping;
124125

125-
public JaxRsRestconf(final RestconfServer server, final PrettyPrintParam prettyPrint) {
126+
public JaxRsRestconf(final RestconfServer server, final ErrorTagMapping errorTagMapping,
127+
final PrettyPrintParam prettyPrint) {
126128
this.server = requireNonNull(server);
129+
this.errorTagMapping = requireNonNull(errorTagMapping);
127130
this.prettyPrint = requireNonNull(prettyPrint);
128131
emptyRequest = ServerRequest.of(QueryParameters.of(), prettyPrint);
132+
133+
LOG.info("RESTCONF data-missing condition is reported as HTTP status {}", switch (errorTagMapping) {
134+
case ERRATA_5565 -> "404 (Errata 5565)";
135+
case RFC8040 -> "409 (RFC8040)";
136+
});
129137
}
130138

131139
private @NonNull ServerRequest requestOf(final UriInfo uriInfo) {
@@ -409,21 +417,22 @@ public void dataYangXmlPATCH(@Encoded @PathParam("identifier") final ApiPath ide
409417
}
410418
}
411419

412-
private static void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
420+
private void completeDataYangPATCH(final RestconfFuture<DataYangPatchResult> future,
413421
final AsyncResponse ar) {
414422
future.addCallback(new JaxRsRestconfCallback<>(ar) {
415423
@Override
416424
Response transform(final DataYangPatchResult result) {
417-
final var status = result.status();
418-
final var builder = Response.status(statusOf(status))
419-
.entity(new YangPatchStatusBody(status));
425+
final var patchStatus = result.status();
426+
final var statusCode = statusOf(patchStatus);
427+
final var builder = Response.status(statusCode.code(), statusCode.phrase())
428+
.entity(new YangPatchStatusBody(patchStatus));
420429
fillConfigurationMetadata(builder, result);
421430
return builder.build();
422431
}
423432

424-
private static Status statusOf(final PatchStatusContext result) {
433+
private HttpStatusCode statusOf(final PatchStatusContext result) {
425434
if (result.ok()) {
426-
return Status.OK;
435+
return HttpStatusCode.OK;
427436
}
428437
final var globalErrors = result.globalErrors();
429438
if (globalErrors != null && !globalErrors.isEmpty()) {
@@ -437,11 +446,11 @@ private static Status statusOf(final PatchStatusContext result) {
437446
}
438447
}
439448
}
440-
return Status.INTERNAL_SERVER_ERROR;
449+
return HttpStatusCode.INTERNAL_SERVER_ERROR;
441450
}
442451

443-
private static Status statusOfFirst(final List<RestconfError> error) {
444-
return ErrorTags.statusOf(error.get(0).getErrorTag());
452+
private @NonNull HttpStatusCode statusOfFirst(final List<RestconfError> error) {
453+
return errorTagMapping.statusOf(error.get(0).getErrorTag());
445454
}
446455
});
447456
}

0 commit comments

Comments
 (0)