| 
									
										
										
										
											2023-03-12 16:00:57 +01:00
										 |  |  | // GoToSocial | 
					
						
							|  |  |  | // Copyright (C) GoToSocial Authors admin@gotosocial.org | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-or-later | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is free software: you can redistribute it and/or modify | 
					
						
							|  |  |  | // it under the terms of the GNU Affero General Public License as published by | 
					
						
							|  |  |  | // the Free Software Foundation, either version 3 of the License, or | 
					
						
							|  |  |  | // (at your option) any later version. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  | // but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  | // GNU Affero General Public License for more details. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // You should have received a copy of the GNU Affero General Public License | 
					
						
							|  |  |  | // along with this program.  If not, see <http://www.gnu.org/licenses/>. | 
					
						
							| 
									
										
										
										
											2021-05-17 19:06:58 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | package gtserror | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | // Custom http response codes + text that | 
					
						
							|  |  |  | // aren't included in the net/http package. | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	StatusClientClosedRequest     = 499 | 
					
						
							|  |  |  | 	StatusTextClientClosedRequest = "Client Closed Request" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | // WithCode wraps an internal error with an http code, and a 'safe' version of | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | // the error that can be served to clients without revealing internal business logic. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // A typical use of this error would be to first log the Original error, then return | 
					
						
							|  |  |  | // the Safe error and the StatusCode to an API caller. | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | type WithCode interface { | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | 	// Unwrap returns the original error. | 
					
						
							|  |  |  | 	// This should *NEVER* be returned to a client as it may contain sensitive information. | 
					
						
							|  |  |  | 	Unwrap() error | 
					
						
							| 
									
										
										
										
											2023-11-30 16:22:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | 	// Error serializes the original internal error for debugging within the GoToSocial logs. | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	// This should *NEVER* be returned to a client as it may contain sensitive information. | 
					
						
							|  |  |  | 	Error() string | 
					
						
							| 
									
										
										
										
											2023-11-30 16:22:34 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	// Safe returns the API-safe version of the error for serialization towards a client. | 
					
						
							|  |  |  | 	// There's not much point logging this internally because it won't contain much helpful information. | 
					
						
							|  |  |  | 	Safe() string | 
					
						
							| 
									
										
										
										
											2023-11-30 16:22:34 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Code returns the status code for serving to a client. | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	Code() int | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | type withCode struct { | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	err  error | 
					
						
							|  |  |  | 	safe string | 
					
						
							|  |  |  | 	code int | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | func (e *withCode) Unwrap() error { | 
					
						
							|  |  |  | 	return e.err | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | func (e *withCode) Error() string { | 
					
						
							|  |  |  | 	return e.err.Error() | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | func (e *withCode) Safe() string { | 
					
						
							|  |  |  | 	return e.safe | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | func (e *withCode) Code() int { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	return e.code | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | // NewWithCode returns a new gtserror.WithCode that implements the error interface | 
					
						
							|  |  |  | // with given HTTP status code, providing status message of "${httpStatus}: ${msg}". | 
					
						
							|  |  |  | func NewWithCode(code int, msg string) WithCode { | 
					
						
							|  |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  newAt(3, msg), | 
					
						
							|  |  |  | 		safe: http.StatusText(code) + ": " + msg, | 
					
						
							|  |  |  | 		code: code, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewfWithCode returns a new formatted gtserror.WithCode that implements the error interface | 
					
						
							|  |  |  | // with given HTTP status code, provided formatted status message of "${httpStatus}: ${msg}". | 
					
						
							|  |  |  | func NewfWithCode(code int, msgf string, args ...any) WithCode { | 
					
						
							|  |  |  | 	msg := fmt.Sprintf(msgf, args...) | 
					
						
							|  |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  newAt(3, msg), | 
					
						
							|  |  |  | 		safe: http.StatusText(code) + ": " + msg, | 
					
						
							|  |  |  | 		code: code, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewWithCodeSafe returns a new gtserror.WithCode wrapping error with given HTTP status | 
					
						
							|  |  |  | // code, hiding error message externally, providing status message of "${httpStatus}: ${safe}". | 
					
						
							|  |  |  | func NewWithCodeSafe(code int, err error, safe string) WithCode { | 
					
						
							|  |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  err, | 
					
						
							|  |  |  | 		safe: http.StatusText(code) + ": " + safe, | 
					
						
							|  |  |  | 		code: code, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // WrapWithCode returns a new gtserror.WithCode wrapping error with given HTTP | 
					
						
							|  |  |  | // status code, hiding error message externally, providing standard status message. | 
					
						
							|  |  |  | func WrapWithCode(code int, err error) WithCode { | 
					
						
							|  |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  err, | 
					
						
							|  |  |  | 		safe: http.StatusText(code), | 
					
						
							|  |  |  | 		code: code, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | // NewErrorBadRequest returns an ErrorWithCode 400 with the given original error and optional help text. | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | func NewErrorBadRequest(original error, helpText ...string) WithCode { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	safe := http.StatusText(http.StatusBadRequest) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusBadRequest, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | // NewErrorUnauthorized returns an ErrorWithCode 401 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorUnauthorized(original error, helpText ...string) WithCode { | 
					
						
							|  |  |  | 	safe := http.StatusText(http.StatusUnauthorized) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusUnauthorized, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewErrorForbidden returns an ErrorWithCode 403 with the given original error and optional help text. | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | func NewErrorForbidden(original error, helpText ...string) WithCode { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	safe := http.StatusText(http.StatusForbidden) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusForbidden, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewErrorNotFound returns an ErrorWithCode 404 with the given original error and optional help text. | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | func NewErrorNotFound(original error, helpText ...string) WithCode { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	safe := http.StatusText(http.StatusNotFound) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusNotFound, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewErrorInternalError returns an ErrorWithCode 500 with the given original error and optional help text. | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | func NewErrorInternalError(original error, helpText ...string) WithCode { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	safe := http.StatusText(http.StatusInternalServerError) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusInternalServerError, | 
					
						
							| 
									
										
										
										
											2021-05-08 14:25:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-15 17:36:15 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // NewErrorConflict returns an ErrorWithCode 409 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorConflict(original error, helpText ...string) WithCode { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	safe := http.StatusText(http.StatusConflict) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2022-01-15 17:36:15 +01:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusConflict, | 
					
						
							| 
									
										
										
										
											2022-01-15 17:36:15 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | // NewErrorNotAcceptable returns an ErrorWithCode 406 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorNotAcceptable(original error, helpText ...string) WithCode { | 
					
						
							|  |  |  | 	safe := http.StatusText(http.StatusNotAcceptable) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusNotAcceptable, | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // NewErrorUnprocessableEntity returns an ErrorWithCode 422 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorUnprocessableEntity(original error, helpText ...string) WithCode { | 
					
						
							|  |  |  | 	safe := http.StatusText(http.StatusUnprocessableEntity) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusUnprocessableEntity, | 
					
						
							| 
									
										
										
										
											2022-06-08 20:38:03 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-11-11 12:18:38 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // NewErrorGone returns an ErrorWithCode 410 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorGone(original error, helpText ...string) WithCode { | 
					
						
							|  |  |  | 	safe := http.StatusText(http.StatusGone) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2022-11-11 12:18:38 +01:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusGone, | 
					
						
							| 
									
										
										
										
											2022-11-11 12:18:38 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-10-05 19:14:53 +02:00
										 |  |  | // NewErrorNotImplemented returns an ErrorWithCode 501 with the given original error and optional help text. | 
					
						
							|  |  |  | func NewErrorNotImplemented(original error, helpText ...string) WithCode { | 
					
						
							|  |  |  | 	safe := http.StatusText(http.StatusNotImplemented) | 
					
						
							| 
									
										
										
										
											2025-06-24 17:24:34 +02:00
										 |  |  | 	if len(helpText) > 0 { | 
					
						
							| 
									
										
										
										
											2024-10-05 19:14:53 +02:00
										 |  |  | 		safe = safe + ": " + strings.Join(helpText, ": ") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: safe, | 
					
						
							|  |  |  | 		code: http.StatusNotImplemented, | 
					
						
							| 
									
										
										
										
											2024-10-05 19:14:53 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | // NewErrorClientClosedRequest returns an ErrorWithCode 499 with the given original error. | 
					
						
							|  |  |  | // This error type should only be used when an http caller has already hung up their request. | 
					
						
							|  |  |  | // See: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#nginx | 
					
						
							|  |  |  | func NewErrorClientClosedRequest(original error) WithCode { | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: StatusTextClientClosedRequest, | 
					
						
							|  |  |  | 		code: StatusClientClosedRequest, | 
					
						
							| 
									
										
										
										
											2023-06-02 15:19:43 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-11-13 19:48:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | // NewErrorRequestTimeout returns an ErrorWithCode 408 with the given original error. | 
					
						
							|  |  |  | // This error type should only be used when the server has decided to hang up a client | 
					
						
							|  |  |  | // request after x amount of time, to avoid keeping extremely slow client requests open. | 
					
						
							|  |  |  | func NewErrorRequestTimeout(original error) WithCode { | 
					
						
							| 
									
										
										
										
											2025-01-28 20:22:23 +00:00
										 |  |  | 	return &withCode{ | 
					
						
							|  |  |  | 		err:  original, | 
					
						
							|  |  |  | 		safe: http.StatusText(http.StatusRequestTimeout), | 
					
						
							|  |  |  | 		code: http.StatusRequestTimeout, | 
					
						
							| 
									
										
										
										
											2023-11-13 19:48:51 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | } |