This commit is contained in:
2025-03-24 06:44:39 +01:00
parent 0df10f5cb3
commit 598b312140
371 changed files with 8238 additions and 9082 deletions

View File

@@ -30,7 +30,9 @@ mut:
// Returns:
// - A pointer to a new Client instance
pub fn new_client(client Client) &Client {
return &Client{...client}
return &Client{
...client
}
}
// SendParams defines configuration options for sending JSON-RPC requests.
@@ -40,9 +42,9 @@ pub struct SendParams {
pub:
// Maximum time in seconds to wait for a response (default: 60)
timeout int = 60
// Number of times to retry the request if it fails
retry int
retry int
}
// send sends a JSON-RPC request with parameters of type T and expects a response with result of type D.
@@ -61,16 +63,14 @@ pub:
pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !D {
// Send the encoded request through the transport layer
response_json := c.transport.send(request.encode(), params)!
// Decode the response JSON into a strongly-typed response object
response := decode_response_generic[D](response_json) or {
return error('Unable to decode response.\n- Response: ${response_json}\n- Error: ${err}')
}
// Validate the response according to the JSON-RPC specification
response.validate() or {
return error('Received invalid response: ${err}')
}
response.validate() or { return error('Received invalid response: ${err}') }
// Ensure the response ID matches the request ID to prevent response/request mismatch
if response.id != request.id {
@@ -79,4 +79,4 @@ pub fn (mut c Client) send[T, D](request RequestGeneric[T], params SendParams) !
// Return the result or propagate any error from the response
return response.result()!
}
}

View File

@@ -31,14 +31,14 @@ fn (t TestRPCTransportClient) send(request_json string, params SendParams) !stri
new_response(request.id, request.params)
} else if request.method == 'test_error' {
error := RPCError{
code: 1
code: 1
message: 'intentional jsonrpc error response'
}
new_error_response(request.id, error)
} else {
new_error_response(request.id, method_not_found)
}
return response.encode()
}
@@ -81,7 +81,7 @@ fn test_send_json_rpc() {
assert err.code() == 1
assert err.msg() == 'intentional jsonrpc error response'
}
// Test case 3: Method not found error
request2 := new_request_generic[string]('nonexistent_method', '')
if response2 := client.send[string, string](request2) {

View File

@@ -30,7 +30,9 @@ pub type ProcedureHandler = fn (payload string) !string
// Returns:
// - A pointer to a new Handler instance or an error if creation fails
pub fn new_handler(handler Handler) !&Handler {
return &Handler{...handler}
return &Handler{
...handler
}
}
// register_procedure registers a new procedure handler for the specified method.
@@ -68,7 +70,7 @@ pub fn (handler Handler) handle(message string) !string {
// Extract the method name from the request
method := decode_request_method(message)!
// log.info('Handling remote procedure call to method: ${method}')
// Look up the procedure handler for the requested method
procedure_func := handler.procedures[method] or {
// log.error('No procedure handler for method ${method} found')

View File

@@ -56,17 +56,17 @@ fn method_error(text string) !string {
fn method_echo_handler(data string) !string {
// Decode the request with string parameters
request := decode_request_generic[string](data)!
// Call the echo method and handle any errors
result := method_echo(request.params) or {
// If an error occurs, create an error response
response := new_error_response(request.id,
code: err.code()
code: err.code()
message: err.msg()
)
return response.encode()
}
// Create a success response with the result
response := new_response_generic(request.id, result)
return response.encode()
@@ -83,17 +83,17 @@ fn method_echo_handler(data string) !string {
fn method_echo_struct_handler(data string) !string {
// Decode the request with TestStruct parameters
request := decode_request_generic[TestStruct](data)!
// Call the echo struct method and handle any errors
result := method_echo_struct(request.params) or {
// If an error occurs, create an error response
response := new_error_response(request.id,
code: err.code()
code: err.code()
message: err.msg()
)
return response.encode()
}
// Create a success response with the struct result
response := new_response_generic[TestStruct](request.id, result)
return response.encode()
@@ -110,17 +110,17 @@ fn method_echo_struct_handler(data string) !string {
fn method_error_handler(data string) !string {
// Decode the request with string parameters
request := decode_request_generic[string](data)!
// Call the error method, which always returns an error
result := method_error(request.params) or {
// Create an error response with the error details
response := new_error_response(request.id,
code: err.code()
code: err.code()
message: err.msg()
)
return response.encode()
}
// This code should never be reached since method_error always returns an error
response := new_response_generic(request.id, result)
return response.encode()
@@ -141,9 +141,9 @@ fn test_handle() {
// Create a new handler with three test procedures
handler := new_handler(Handler{
procedures: {
'method_echo': method_echo_handler
'method_echo': method_echo_handler
'method_echo_struct': method_echo_struct_handler
'method_error': method_error_handler
'method_error': method_error_handler
}
})!

View File

@@ -7,45 +7,45 @@ module jsonrpc
// This error is returned when the server is unable to parse the request.
// Error code: -32700
pub const parse_error = RPCError{
code: -32700
code: -32700
message: 'Parse error'
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
data: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.'
}
// invalid_request indicates that the sent JSON is not a valid Request object.
// This error is returned when the request object doesn't conform to the JSON-RPC 2.0 specification.
// Error code: -32600
pub const invalid_request = RPCError{
code: -32600
code: -32600
message: 'Invalid Request'
data: 'The JSON sent is not a valid Request object.'
data: 'The JSON sent is not a valid Request object.'
}
// method_not_found indicates that the requested method doesn't exist or is not available.
// This error is returned when the method specified in the request is not supported.
// Error code: -32601
pub const method_not_found = RPCError{
code: -32601
code: -32601
message: 'Method not found'
data: 'The method does not exist / is not available.'
data: 'The method does not exist / is not available.'
}
// invalid_params indicates that the method parameters are invalid.
// This error is returned when the parameters provided to the method are incorrect or incompatible.
// Error code: -32602
pub const invalid_params = RPCError{
code: -32602
code: -32602
message: 'Invalid params'
data: 'Invalid method parameter(s).'
data: 'Invalid method parameter(s).'
}
// internal_error indicates an internal JSON-RPC error.
// This is a generic server-side error when no more specific error is applicable.
// Error code: -32603
pub const internal_error = RPCError{
code: -32603
code: -32603
message: 'Internal Error'
data: 'Internal JSON-RPC error.'
data: 'Internal JSON-RPC error.'
}
// RPCError represents a JSON-RPC 2.0 error object as defined in the specification.
@@ -55,13 +55,13 @@ pub struct RPCError {
pub mut:
// Numeric error code. Predefined codes are in the range -32768 to -32000.
// Custom error codes should be outside this range.
code int
code int
// Short description of the error
message string
// Additional information about the error (optional)
data string
data string
}
// new_error creates a new error response for a given request ID.
@@ -76,8 +76,8 @@ pub mut:
pub fn new_error(id int, error RPCError) Response {
return Response{
jsonrpc: jsonrpc_version
error_: error
id: id
error_: error
id: id
}
}
@@ -106,4 +106,4 @@ pub fn (err RPCError) code() int {
// - true if the error is empty, false otherwise
pub fn (err RPCError) is_empty() bool {
return err.code == 0
}
}

View File

@@ -4,25 +4,25 @@ module jsonrpc
// It contains all the required fields according to the JSON-RPC 2.0 specification.
// See: https://www.jsonrpc.org/specification#notification
pub struct Notification {
pub mut:
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string = "2.0" @[required]
jsonrpc string = '2.0' @[required]
// The name of the method to be invoked on the server
method string @[required]
method string @[required]
}
// Notification represents a JSON-RPC 2.0 notification object.
// It contains all the required fields according to the JSON-RPC 2.0 specification.
// See: https://www.jsonrpc.org/specification#notification
pub struct NotificationGeneric[T] {
pub mut:
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string = "2.0" @[required]
jsonrpc string = '2.0' @[required]
// The name of the method to be invoked on the server
method string @[required]
params ?T
method string @[required]
params ?T
}
// new_notification creates a new JSON-RPC notification with the specified method and parameters.
@@ -36,15 +36,15 @@ pub struct NotificationGeneric[T] {
// - A fully initialized Notification object
pub fn new_notification[T](method string, params T) NotificationGeneric[T] {
return NotificationGeneric[T]{
jsonrpc: jsonrpc.jsonrpc_version
method: method
params: params
jsonrpc: jsonrpc_version
method: method
params: params
}
}
pub fn new_blank_notification(method string) Notification {
return Notification{
jsonrpc: jsonrpc.jsonrpc_version
method: method
jsonrpc: jsonrpc_version
method: method
}
}
}

View File

@@ -10,18 +10,18 @@ import rand
pub struct Request {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
jsonrpc string @[required]
// The name of the method to be invoked on the server
method string @[required]
method string @[required]
// The parameters to the method, encoded as a JSON string
// This can be omitted if the method doesn't require parameters
params string
params string
// An identifier established by the client that must be included in the response
// This is used to correlate requests with their corresponding responses
id int @[required]
id int @[required]
}
// new_request creates a new JSON-RPC request with the specified method and parameters.
@@ -35,10 +35,10 @@ pub mut:
// - A fully initialized Request object
pub fn new_request(method string, params string) Request {
return Request{
jsonrpc: jsonrpc.jsonrpc_version
method: method
params: params
id: rand.i32() // Automatically generate a unique ID using UUID v4
jsonrpc: jsonrpc_version
method: method
params: params
id: rand.i32() // Automatically generate a unique ID using UUID v4
}
}
@@ -83,15 +83,15 @@ pub struct RequestGeneric[T] {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
// The name of the method to be invoked on the server
method string @[required]
method string @[required]
// The parameters to the method, with a specific type T
params T
params T
// An identifier established by the client
id int @[required]
id int @[required]
}
// new_request_generic creates a new generic JSON-RPC request with strongly-typed parameters.
@@ -105,10 +105,10 @@ pub mut:
// - A fully initialized RequestGeneric object with parameters of type T
pub fn new_request_generic[T](method string, params T) RequestGeneric[T] {
return RequestGeneric[T]{
jsonrpc: jsonrpc.jsonrpc_version
method: method
params: params
id: rand.i32()
jsonrpc: jsonrpc_version
method: method
params: params
id: rand.i32()
}
}
@@ -159,4 +159,4 @@ pub fn decode_request_generic[T](data string) !RequestGeneric[T] {
// - A JSON string representation of the RequestGeneric object
pub fn (req RequestGeneric[T]) encode[T]() string {
return json2.encode(req)
}
}

View File

@@ -2,7 +2,7 @@ module jsonrpc
fn test_new_request() {
request := new_request('test_method', 'test_params')
assert request.jsonrpc == jsonrpc.jsonrpc_version
assert request.jsonrpc == jsonrpc_version
assert request.method == 'test_method'
assert request.params == 'test_params'
assert request.id != -1 // Ensure the ID is generated
@@ -11,7 +11,7 @@ fn test_new_request() {
fn test_decode_request() {
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
request := decode_request(data) or {
assert false, 'Failed to decode request: $err'
assert false, 'Failed to decode request: ${err}'
return
}
assert request.jsonrpc == '2.0'
@@ -23,13 +23,16 @@ fn test_decode_request() {
fn test_request_encode() {
request := new_request('test_method', 'test_params')
json := request.encode()
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"') && json.contains('"id"')
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"')
&& json.contains('"id"')
}
fn test_new_request_generic() {
params := {'key': 'value'}
params := {
'key': 'value'
}
request := new_request_generic('test_method', params)
assert request.jsonrpc == jsonrpc.jsonrpc_version
assert request.jsonrpc == jsonrpc_version
assert request.method == 'test_method'
assert request.params == params
assert request.id != -1 // Ensure the ID is generated
@@ -38,7 +41,7 @@ fn test_new_request_generic() {
fn test_decode_request_id() {
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
id := decode_request_id(data) or {
assert false, 'Failed to decode request ID: $err'
assert false, 'Failed to decode request ID: ${err}'
return
}
assert id == 123
@@ -47,7 +50,7 @@ fn test_decode_request_id() {
fn test_decode_request_method() {
data := '{"jsonrpc":"2.0","method":"test_method","params":"test_params","id":"123"}'
method := decode_request_method(data) or {
assert false, 'Failed to decode request method: $err'
assert false, 'Failed to decode request method: ${err}'
return
}
assert method == 'test_method'
@@ -56,18 +59,23 @@ fn test_decode_request_method() {
fn test_decode_request_generic() {
data := '{"jsonrpc":"2.0","method":"test_method","params":{"key":"value"},"id":123}'
request := decode_request_generic[map[string]string](data) or {
assert false, 'Failed to decode generic request: $err'
assert false, 'Failed to decode generic request: ${err}'
return
}
assert request.jsonrpc == '2.0'
assert request.method == 'test_method'
assert request.params == {'key': 'value'}
assert request.params == {
'key': 'value'
}
assert request.id == 123
}
fn test_request_generic_encode() {
params := {'key': 'value'}
params := {
'key': 'value'
}
request := new_request_generic('test_method', params)
json := request.encode()
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"') && json.contains('"id"')
}
assert json.contains('"jsonrpc"') && json.contains('"method"') && json.contains('"params"')
&& json.contains('"id"')
}

View File

@@ -13,15 +13,15 @@ pub struct Response {
pub:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
// The result of the method invocation (only present if the call was successful)
result ?string
result ?string
// Error object if the request failed (only present if the call failed)
error_ ?RPCError @[json: 'error']
error_ ?RPCError @[json: 'error']
// Must match the id of the request that generated this response
id int @[required]
id int @[required]
}
// new_response creates a successful JSON-RPC response with the given result.
@@ -34,9 +34,9 @@ pub:
// - A Response object containing the result
pub fn new_response(id int, result string) Response {
return Response{
jsonrpc: jsonrpc.jsonrpc_version
result: result
id: id
jsonrpc: jsonrpc_version
result: result
id: id
}
}
@@ -50,9 +50,9 @@ pub fn new_response(id int, result string) Response {
// - A Response object containing the error
pub fn new_error_response(id int, error RPCError) Response {
return Response{
jsonrpc: jsonrpc.jsonrpc_version
error_: error
id: id
jsonrpc: jsonrpc_version
error_: error
id: id
}
}
@@ -77,19 +77,19 @@ pub fn decode_response(data string) !Response {
// Handle error responses
if err := raw_map['error'] {
id_any := raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}
return Response {
id: id_any.int()
id_any := raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }
return Response{
id: id_any.int()
jsonrpc: jsonrpc_version
error_: json2.decode[RPCError](err.str())!
error_: json2.decode[RPCError](err.str())!
}
}
// Handle successful responses
return Response {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
return Response{
id: raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }.int()
jsonrpc: jsonrpc_version
result: raw_map['result']!.str()
result: raw_map['result']!.str()
}
}
@@ -148,8 +148,9 @@ pub fn (resp Response) error() ?RPCError {
pub fn (resp Response) result() !string {
if err := resp.error() {
return err
} // Ensure no error is present
return resp.result or {''}
}
// Ensure no error is present
return resp.result or { '' }
}
// ResponseGeneric is a type-safe version of the Response struct that allows
@@ -159,15 +160,15 @@ pub struct ResponseGeneric[D] {
pub mut:
// The JSON-RPC protocol version, must be exactly "2.0"
jsonrpc string @[required]
// The result of the method invocation with a specific type D
result ?D
result ?D
// Error object if the request failed
error_ ?RPCError @[json: 'error']
error_ ?RPCError @[json: 'error']
// Must match the id of the request that generated this response
id int @[required]
id int @[required]
}
// new_response_generic creates a successful generic JSON-RPC response with a strongly-typed result.
@@ -180,9 +181,9 @@ pub mut:
// - A ResponseGeneric object with result of type D
pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
return ResponseGeneric[D]{
jsonrpc: jsonrpc.jsonrpc_version
result: result
id: id
jsonrpc: jsonrpc_version
result: result
id: id
}
}
@@ -196,7 +197,7 @@ pub fn new_response_generic[D](id int, result D) ResponseGeneric[D] {
// - A ResponseGeneric object with result of type D, or an error if parsing fails
pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
// Debug output - consider removing in production
raw := json2.raw_decode(data)!
raw_map := raw.as_map()
@@ -209,19 +210,21 @@ pub fn decode_response_generic[D](data string) !ResponseGeneric[D] {
// Handle error responses
if err := raw_map['error'] {
return ResponseGeneric[D] {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
return ResponseGeneric[D]{
id: raw_map['id'] or {
return error('Invalid JSONRPC response, no ID Field found')
}.int()
jsonrpc: jsonrpc_version
error_: json2.decode[RPCError](err.str())!
error_: json2.decode[RPCError](err.str())!
}
}
// Handle successful responses
resp := json.decode(ResponseGeneric[D], data)!
return ResponseGeneric[D] {
id: raw_map['id'] or {return error('Invalid JSONRPC response, no ID Field found')}.int()
return ResponseGeneric[D]{
id: raw_map['id'] or { return error('Invalid JSONRPC response, no ID Field found') }.int()
jsonrpc: jsonrpc_version
result: resp.result
result: resp.result
}
}
@@ -276,6 +279,7 @@ pub fn (resp ResponseGeneric[D]) error() ?RPCError {
pub fn (resp ResponseGeneric[D]) result() !D {
if err := resp.error() {
return err
} // Ensure no error is present
return resp.result or {D{}}
}
}
// Ensure no error is present
return resp.result or { D{} }
}

View File

@@ -2,9 +2,9 @@ module jsonrpc
fn test_new_response() {
response := new_response(123, 'test_result')
assert response.jsonrpc == jsonrpc.jsonrpc_version
assert response.jsonrpc == jsonrpc_version
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
result := response.result() or {
@@ -16,18 +16,18 @@ fn test_new_response() {
fn test_new_error_response() {
error := RPCError{
code: 123
code: 123
message: 'Test error'
data: 'Error details'
data: 'Error details'
}
response := new_error_response(123, error)
assert response.jsonrpc == jsonrpc.jsonrpc_version
assert response.jsonrpc == jsonrpc_version
response.validate()!
assert response.is_error()
assert !response.is_result() // Ensure no result is set
assert response.id == 123
response_error := response.error()?
assert response_error == error
}
@@ -35,7 +35,7 @@ fn test_new_error_response() {
fn test_decode_response() {
data := '{"jsonrpc":"2.0","result":"test_result","id":"123"}'
response := decode_response(data) or {
assert false, 'Failed to decode response: $err'
assert false, 'Failed to decode response: ${err}'
return
}
assert response.jsonrpc == '2.0'
@@ -58,18 +58,18 @@ fn test_response_encode() {
fn test_response_validate() {
response := new_response(123, 'test_result')
response.validate() or { assert false, 'Validation failed for valid response: $err' }
response.validate() or { assert false, 'Validation failed for valid response: ${err}' }
error := RPCError{
code: 123
code: 123
message: 'Test error'
data: 'Error details'
data: 'Error details'
}
invalid_response := Response{
jsonrpc: '2.0'
result: 'test_result'
error_: error
id: 123
result: 'test_result'
error_: error
id: 123
}
invalid_response.validate() or {
assert err.msg().contains('Response contains both error and result.')
@@ -78,13 +78,13 @@ fn test_response_validate() {
fn test_response_error() {
error := RPCError{
code: 123
code: 123
message: 'Test error'
data: 'Error details'
data: 'Error details'
}
response := new_error_response(123, error)
err := response.error() or {
assert false, 'Failed to get error: $err'
assert false, 'Failed to get error: ${err}'
return
}
assert err.code == 123
@@ -95,64 +95,76 @@ fn test_response_error() {
fn test_response_result() {
response := new_response(123, 'test_result')
result := response.result() or {
assert false, 'Failed to get result: $err'
assert false, 'Failed to get result: ${err}'
return
}
assert result == 'test_result'
}
fn test_new_response_generic() {
response := new_response_generic(123, {'key': 'value'})
assert response.jsonrpc == jsonrpc.jsonrpc_version
response := new_response_generic(123, {
'key': 'value'
})
assert response.jsonrpc == jsonrpc_version
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
result := response.result() or {
assert false, 'Response should have result'
return
}
assert result == {'key': 'value'}
assert result == {
'key': 'value'
}
}
fn test_decode_response_generic() {
data := '{"jsonrpc":"2.0","result":{"key":"value"},"id":"123"}'
response := decode_response_generic[map[string]string](data) or {
assert false, 'Failed to decode generic response: $err'
assert false, 'Failed to decode generic response: ${err}'
return
}
assert response.jsonrpc == '2.0'
assert response.id == 123
assert response.is_result()
assert !response.is_error() // Ensure no error is set
result := response.result() or {
assert false, 'Response should have result'
return
}
assert result == {'key': 'value'}
assert result == {
'key': 'value'
}
}
fn test_response_generic_encode() {
response := new_response_generic(123, {'key': 'value'})
response := new_response_generic(123, {
'key': 'value'
})
json := response.encode()
assert json.contains('"jsonrpc"') && json.contains('"result"') && json.contains('"id"')
}
fn test_response_generic_validate() {
response := new_response_generic(123, {'key': 'value'})
response.validate() or { assert false, 'Validation failed for valid response: $err' }
response := new_response_generic(123, {
'key': 'value'
})
response.validate() or { assert false, 'Validation failed for valid response: ${err}' }
error := RPCError{
code: 123
code: 123
message: 'Test error'
data: 'Error details'
data: 'Error details'
}
invalid_response := ResponseGeneric{
jsonrpc: '2.0'
result: {'key': 'value'}
error_: error
id: 123
result: {
'key': 'value'
}
error_: error
id: 123
}
invalid_response.validate() or {
assert err.msg().contains('Response contains both error and result.')
@@ -161,17 +173,17 @@ fn test_response_generic_validate() {
fn test_response_generic_error() {
error := RPCError{
code: 123
code: 123
message: 'Test error'
data: 'Error details'
data: 'Error details'
}
response := ResponseGeneric[map[string]string]{
jsonrpc: '2.0'
error_: error
id: 123
error_: error
id: 123
}
err := response.error() or {
assert false, 'Failed to get error: $err'
assert false, 'Failed to get error: ${err}'
return
}
assert err.code == 123
@@ -180,10 +192,14 @@ fn test_response_generic_error() {
}
fn test_response_generic_result() {
response := new_response_generic(123, {'key': 'value'})
response := new_response_generic(123, {
'key': 'value'
})
result := response.result() or {
assert false, 'Failed to get result: $err'
assert false, 'Failed to get result: ${err}'
return
}
assert result == {'key': 'value'}
}
assert result == {
'key': 'value'
}
}

View File

@@ -15,8 +15,8 @@ fn testfunction0_handler(data string) !string {
result := testfunction0(request.params)
response := jsonrpc.JsonRpcResponse[string]{
jsonrpc: '2.0.0'
id: request.id
result: result
id: request.id
result: result
}
return response.to_json()
}
@@ -26,8 +26,8 @@ fn testfunction1_handler(data string) !string {
result := testfunction1(request.params)
response := jsonrpc.JsonRpcResponse[[]string]{
jsonrpc: '2.0.0'
id: request.id
result: result
id: request.id
result: result
}
return response.to_json()
}