1
0
mirror of https://github.com/weechat/weechat.git synced 2026-06-12 14:14:48 +02:00

relay: add completion resource

This commit is contained in:
Nils
2024-10-30 15:58:29 +01:00
committed by Sébastien Helleu
parent cfe34388fb
commit c6c420c698
8 changed files with 466 additions and 17 deletions
+66
View File
@@ -721,6 +721,72 @@ relay_api_msg_nick_group_to_json (struct t_gui_nick_group *nick_group,
return json;
}
/*
* Creates a JSON object with a completion entry.
*/
cJSON *
relay_api_msg_completion_to_json (struct t_gui_completion *completion)
{
struct t_hdata *hdata;
struct t_gui_completion *pointer;
struct t_gui_completion_word *word;
const char *ptr_string;
struct t_arraylist *ptr_list;
cJSON *json, *json_array;
int context, i, size;
hdata = relay_hdata_completion;
pointer = completion;
json = cJSON_CreateObject ();
if (!json)
return NULL;
if (!completion)
return json;
ptr_list = weechat_hdata_pointer (relay_hdata_completion, completion, "list");
if (!ptr_list)
return json;
/* context */
context = weechat_hdata_integer (relay_hdata_completion, completion, "context");
switch (context)
{
case 0:
MSG_ADD_STR_PTR("context", "null");
break;
case 1:
MSG_ADD_STR_PTR("context", "command");
break;
case 2:
MSG_ADD_STR_PTR("context", "command_arg");
break;
default:
MSG_ADD_STR_PTR("context", "auto");
break;
}
MSG_ADD_HDATA_STR("base_word", "base_word");
MSG_ADD_HDATA_VAR(Number, "position_replace", integer, "position_replace");
MSG_ADD_HDATA_VAR(Bool, "add_space", integer, "add_space");
json_array = cJSON_CreateArray ();
size = weechat_arraylist_size (ptr_list);
for (i = 0; i < size; i++)
{
word = (struct t_gui_completion_word *)weechat_arraylist_get (ptr_list, i);
cJSON_AddItemToArray (
json_array,
cJSON_CreateString (
weechat_hdata_string (relay_hdata_completion_word, word, "word")));
}
cJSON_AddItemToObject (json, "list", json_array);
return json;
}
/*
* Creates a JSON object with a hotlist entry.
*/
+1
View File
@@ -54,6 +54,7 @@ extern cJSON *relay_api_msg_nick_to_json (struct t_gui_nick *nick,
enum t_relay_api_colors colors);
extern cJSON *relay_api_msg_nick_group_to_json (struct t_gui_nick_group *nick_group,
enum t_relay_api_colors colors);
extern cJSON *relay_api_msg_completion_to_json (struct t_gui_completion *completion);
extern cJSON *relay_api_msg_hotlist_to_json (struct t_gui_hotlist *hotlist);
#endif /* WEECHAT_PLUGIN_RELAY_API_MSG_H */
+147 -12
View File
@@ -713,6 +713,7 @@ RELAY_API_PROTOCOL_CALLBACK(input)
if (!json_body)
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
/* get buffer either by id or by name */
ptr_buffer = NULL;
json_buffer_id = cJSON_GetObjectItem (json_body, "buffer_id");
if (json_buffer_id)
@@ -726,7 +727,7 @@ RELAY_API_PROTOCOL_CALLBACK(input)
{
relay_api_msg_send_error_json (
client,
RELAY_HTTP_404_NOT_FOUND, NULL,
RELAY_HTTP_400_BAD_REQUEST, NULL,
"Buffer \"%lld\" not found",
(long long)cJSON_GetNumberValue (json_buffer_id));
cJSON_Delete (json_body);
@@ -747,7 +748,7 @@ RELAY_API_PROTOCOL_CALLBACK(input)
{
relay_api_msg_send_error_json (
client,
RELAY_HTTP_404_NOT_FOUND, NULL,
RELAY_HTTP_400_BAD_REQUEST, NULL,
"Buffer \"%s\" not found",
ptr_buffer_name);
cJSON_Delete (json_body);
@@ -819,6 +820,138 @@ RELAY_API_PROTOCOL_CALLBACK(input)
return RELAY_API_PROTOCOL_RC_OK;
}
/*
* Callback for resource "completion".
*
* Routes:
* POST /api/completion
*/
RELAY_API_PROTOCOL_CALLBACK(completion)
{
cJSON *json_response, *json_body;
cJSON *json_buffer_id, *json_buffer_name;
cJSON *json_command, *json_position;
const char *ptr_buffer_name, *ptr_command;
int position;
char str_id[64];
struct t_gui_completion *ptr_completion;
struct t_gui_buffer *ptr_buffer;
json_body = cJSON_Parse (client->http_req->body);
if (!json_body)
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
/* get buffer either by id or by name */
ptr_buffer = NULL;
json_buffer_id = cJSON_GetObjectItem (json_body, "buffer_id");
if (json_buffer_id)
{
if (cJSON_IsNumber (json_buffer_id))
{
snprintf (str_id, sizeof(str_id),
"%lld", (long long)cJSON_GetNumberValue (json_buffer_id));
ptr_buffer = weechat_buffer_search ("==id", str_id);
if (!ptr_buffer)
{
relay_api_msg_send_error_json (
client,
RELAY_HTTP_400_BAD_REQUEST, NULL,
"Buffer \"%lld\" not found",
(long long)cJSON_GetNumberValue (json_buffer_id));
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
}
}
else
{
json_buffer_name = cJSON_GetObjectItem (json_body, "buffer_name");
if (json_buffer_name)
{
if (cJSON_IsString (json_buffer_name))
{
ptr_buffer_name = cJSON_GetStringValue (json_buffer_name);
ptr_buffer = weechat_buffer_search ("==", ptr_buffer_name);
if (!ptr_buffer)
{
relay_api_msg_send_error_json (
client,
RELAY_HTTP_400_BAD_REQUEST, NULL,
"Buffer \"%s\" not found",
ptr_buffer_name);
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
}
}
else
{
ptr_buffer = weechat_buffer_search_main ();
}
}
if (!ptr_buffer)
{
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
/* get command and position (optional) from input json object */
json_command = cJSON_GetObjectItem (json_body, "command");
if (json_command && cJSON_IsString (json_command))
{
ptr_command = cJSON_GetStringValue (json_command);
}
else
{
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
json_position = cJSON_GetObjectItem (json_body, "position");
if (json_position)
{
if (cJSON_IsNumber (json_position))
{
position = cJSON_GetNumberValue (json_position);
}
else
{
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
}
else
{
position = strlen (ptr_command);
}
/* perform completion */
ptr_completion = weechat_completion_new (ptr_buffer);
if (!ptr_completion)
{
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_MEMORY;
}
if (!weechat_completion_search (ptr_completion, ptr_command, position, 1))
{
weechat_completion_free (ptr_completion);
cJSON_Delete (json_body);
return RELAY_API_PROTOCOL_RC_BAD_REQUEST;
}
/* create response */
json_response = relay_api_msg_completion_to_json (ptr_completion);
relay_api_msg_send_json (client, RELAY_HTTP_200_OK, NULL, "completion",
json_response);
cJSON_Delete (json_response);
cJSON_Delete (json_body);
weechat_completion_free (ptr_completion);
return RELAY_API_PROTOCOL_RC_OK;
}
/*
* Callback for resource "ping".
*
@@ -1062,16 +1195,18 @@ relay_api_protocol_recv_http (struct t_relay_client *client)
int i, num_args;
enum t_relay_api_protocol_rc return_code;
struct t_relay_api_protocol_cb protocol_cb[] = {
/* method, resource, auth, min args, max args, callback */
{ "OPTIONS", "*", 0, 0, -1, &relay_api_protocol_cb_options },
{ "POST", "handshake", 0, 0, 0, &relay_api_protocol_cb_handshake },
{ "GET", "version", 1, 0, 0, &relay_api_protocol_cb_version },
{ "GET", "buffers", 1, 0, 3, &relay_api_protocol_cb_buffers },
{ "GET", "hotlist", 1, 0, 3, &relay_api_protocol_cb_hotlist },
{ "POST", "input", 1, 0, 0, &relay_api_protocol_cb_input },
{ "POST", "ping", 1, 0, 0, &relay_api_protocol_cb_ping },
{ "POST", "sync", 1, 0, 0, &relay_api_protocol_cb_sync },
{ NULL, NULL, 0, 0, 0, NULL },
/* method, resource, auth, args, callback */
/* min,max */
{ "OPTIONS", "*", 0, 0, -1, RELAY_API_CB(options) },
{ "POST", "handshake", 0, 0, 0, RELAY_API_CB(handshake) },
{ "GET", "version", 1, 0, 0, RELAY_API_CB(version) },
{ "GET", "buffers", 1, 0, 3, RELAY_API_CB(buffers) },
{ "GET", "hotlist", 1, 0, 3, RELAY_API_CB(hotlist) },
{ "POST", "input", 1, 0, 0, RELAY_API_CB(input) },
{ "POST", "completion", 1, 0, 0, RELAY_API_CB(completion) },
{ "POST", "ping", 1, 0, 0, RELAY_API_CB(ping) },
{ "POST", "sync", 1, 0, 0, RELAY_API_CB(sync) },
{ NULL, NULL, 0, 0, 0, NULL },
};
if (!client->http_req || RELAY_STATUS_HAS_ENDED(client->status))
@@ -20,6 +20,7 @@
#ifndef WEECHAT_PLUGIN_RELAY_API_PROTOCOL_H
#define WEECHAT_PLUGIN_RELAY_API_PROTOCOL_H
#define RELAY_API_CB(__command) &relay_api_protocol_cb_##__command
#define RELAY_API_PROTOCOL_CALLBACK(__command) \
enum t_relay_api_protocol_rc \
relay_api_protocol_cb_##__command (struct t_relay_client *client)
+1 -1
View File
@@ -24,7 +24,7 @@ struct t_relay_client;
enum t_relay_status;
#define RELAY_API_VERSION_MAJOR 0
#define RELAY_API_VERSION_MINOR 3
#define RELAY_API_VERSION_MINOR 4
#define RELAY_API_VERSION_PATCH 0
#define RELAY_API_VERSION_NUMBER \
((RELAY_API_VERSION_MAJOR << 16) \
+97 -3
View File
@@ -13,7 +13,7 @@ info:
license:
name: CC BY-NC-SA 4.0
url: https://creativecommons.org/licenses/by-nc-sa/4.0/
version: 0.3.0
version: 0.4.0
externalDocs:
url: https://weechat.org/doc/
@@ -29,6 +29,7 @@ tags:
- name: buffers
- name: hotlist
- name: input
- name: completion
- name: ping
- name: sync
@@ -367,8 +368,30 @@ paths:
description: Bad request
'401':
description: Unauthorized
'404':
description: Buffer not found
security:
- password: []
/completion:
post:
tags:
- completion
description: |
Complete user command or text.
operationId: input
parameters:
- $ref: '#/components/parameters/totp'
requestBody:
$ref: '#/components/requestBodies/CompletionBody'
responses:
'200':
description: Successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Completion'
'400':
description: Bad request
'401':
description: Unauthorized
security:
- password: []
/ping:
@@ -944,6 +967,42 @@ components:
- date
- buffer_id
- count
Completion:
type: object
properties:
context:
type: string
enum:
- 'null'
- 'command'
- 'command_arg'
- 'auto'
example: 'command_arg'
base_word:
type: string
example: 'qu'
position_replace:
type: integer
format: int32
example: 1
add_space:
type: boolean
example: true
list:
type: array
items:
type: string
example:
- 'query'
- 'quiet'
- 'quit'
- 'quote'
required:
- context
- base_word
- position_replace
- add_space
- list
Ping:
type: object
properties:
@@ -1002,6 +1061,41 @@ components:
example: 'hello, world!'
required:
- command
example:
buffer_id: 1709932823238637
command: 'hello, world!'
CompletionBody:
description: Buffer and user text/command to complete
required: true
content:
application/json:
schema:
type: object
properties:
buffer_id:
type: integer
format: int64
description: Buffer identifier (≥ 0)
example: 1709932823238637
buffer_name:
type: string
description: >-
Buffer full name
example: 'irc.libera.#weechat'
command:
type: string
description: user command or text to complete
example: '/qu'
position:
type: integer
format: int32
description: Position in data (≥ 0)
example: 3
required:
- data
example:
buffer_id: 1709932823238637
command: '/qu'
PingBody:
description: Custom data that will be returned in the response
required: false
@@ -32,6 +32,7 @@ extern "C"
#include "src/gui/gui-chat.h"
#include "src/gui/gui-color.h"
#include "src/gui/gui-hotlist.h"
#include "src/gui/gui-input.h"
#include "src/gui/gui-line.h"
#include "src/gui/gui-nicklist.h"
#include "src/plugins/relay/relay.h"
@@ -514,6 +515,77 @@ TEST(RelayApiMsg, LinesToJson)
cJSON_Delete (json);
}
/*
* Tests functions:
* relay_api_msg_completion_to_json
*/
TEST(RelayApiMsg, CompletionToJson)
{
cJSON *json, *json_obj, *json_item;
// check empty json result
json = relay_api_msg_completion_to_json (NULL);
CHECK(json);
CHECK(cJSON_IsObject (json));
POINTERS_EQUAL(NULL, cJSON_GetObjectItem (json, "priority"));
cJSON_Delete (json);
// set example input
gui_buffer_set (gui_buffers, "input", "/co");
gui_buffer_set (gui_buffers, "input_pos", "3");
// perform completion
gui_input_complete_next (gui_buffers);
STRCMP_EQUAL("/color ", gui_buffers->input_buffer);
// convert to json
json = relay_api_msg_completion_to_json (gui_buffers->completion);
CHECK(json);
CHECK(cJSON_IsObject (json));
json_obj = cJSON_GetObjectItem (json, "context");
CHECK(json_obj);
CHECK(cJSON_IsString (json_obj));
STRCMP_EQUAL("command", cJSON_GetStringValue (json_obj));
json_obj = cJSON_GetObjectItem (json, "base_word");
CHECK(json_obj);
CHECK(cJSON_IsString (json_obj));
STRCMP_EQUAL("co", cJSON_GetStringValue (json_obj));
json_obj = cJSON_GetObjectItem (json, "position_replace");
CHECK(json_obj);
CHECK(cJSON_IsNumber (json_obj));
CHECK_EQUAL(1, cJSON_GetNumberValue (json_obj));
json_obj = cJSON_GetObjectItem (json, "add_space");
CHECK(json_obj);
CHECK(cJSON_IsBool (json_obj));
CHECK(cJSON_IsTrue (json_obj));
json_obj = cJSON_GetObjectItem (json, "list");
CHECK(json_obj);
CHECK(cJSON_IsArray (json_obj));
CHECK_EQUAL(3, cJSON_GetArraySize (json_obj));
json_item = cJSON_GetArrayItem (json_obj, 0);
CHECK(json_item);
CHECK(cJSON_IsString (json_item));
STRCMP_EQUAL("color", cJSON_GetStringValue (json_item));
json_item = cJSON_GetArrayItem (json_obj, 1);
CHECK(json_item);
CHECK(cJSON_IsString (json_item));
STRCMP_EQUAL("command", cJSON_GetStringValue (json_item));
json_item = cJSON_GetArrayItem (json_obj, 2);
CHECK(json_item);
CHECK(cJSON_IsString (json_item));
STRCMP_EQUAL("connect", cJSON_GetStringValue (json_item));
cJSON_Delete (json);
gui_buffer_set (gui_buffers, "input", "");
}
/*
* Tests functions:
* relay_api_msg_hotlist_to_json
@@ -598,6 +598,86 @@ TEST(RelayApiProtocolWithClient, CbHotlist)
gui_hotlist_remove_buffer (gui_buffers, 1);
}
/*
* Tests functions:
* relay_api_protocol_cb_completion
*/
TEST(RelayApiProtocolWithClient, CbCompletion)
{
cJSON *json, *json_obj, *json_array;
/* error: no body */
test_client_recv_http ("POST /api/completion", NULL, NULL);
WEE_CHECK_HTTP_CODE(400, "Bad Request");
STRCMP_EQUAL("HTTP/1.1 400 Bad Request\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: application/json; charset=utf-8\r\n"
"Content-Length: 0\r\n"
"\r\n",
data_sent[0]);
/* error: invalid buffer name */
test_client_recv_http ("POST /api/completion",
NULL,
"{\"buffer_name\": \"invalid\", "
"\"command\": \"test\"}");
WEE_CHECK_HTTP_CODE(400, "Bad Request");
STRCMP_EQUAL("HTTP/1.1 400 Bad Request\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: application/json; charset=utf-8\r\n"
"Content-Length: 41\r\n"
"\r\n"
"{\"error\": \"Buffer \\\"invalid\\\" not found\"}",
data_sent[0]);
/* on core buffer, with buffer name. examples from relay protocol examples:
* https://weechat.org/files/doc/weechat/stable/weechat_relay_weechat.en.html#command_completion
*/
/* completion core.weechat -1 /help fi */
test_client_recv_http ("POST /api/completion",
NULL,
"{\"buffer_name\": \"core.weechat\", "
"\"command\": \"/help fi\"}");
WEE_CHECK_HTTP_CODE(200, "OK");
json = json_body_sent[0];
CHECK(json);
CHECK(cJSON_IsObject (json));
WEE_CHECK_OBJ_STR("command_arg", json, "context");
WEE_CHECK_OBJ_STR("fi", json, "base_word");
WEE_CHECK_OBJ_NUM(6, json, "position_replace");
WEE_CHECK_OBJ_BOOL(0, json, "add_space");
json_array = cJSON_GetObjectItem (json, "list");
CHECK(json_array);
CHECK(cJSON_IsArray (json_array));
CHECK(cJSON_GetArraySize (json_array) == 4);
STRCMP_EQUAL("fifo", cJSON_GetStringValue (cJSON_GetArrayItem (json_array, 0)));
STRCMP_EQUAL("fifo.file.enabled", cJSON_GetStringValue (cJSON_GetArrayItem (json_array, 1)));
STRCMP_EQUAL("fifo.file.path", cJSON_GetStringValue (cJSON_GetArrayItem (json_array, 2)));
STRCMP_EQUAL("filter", cJSON_GetStringValue (cJSON_GetArrayItem (json_array, 3)));
/* completion core.weechat 5 /quernick */
test_client_recv_http ("POST /api/completion",
NULL,
"{\"buffer_name\": \"core.weechat\", "
"\"command\": \"/quernick\", "
"\"position\": 5}");
WEE_CHECK_HTTP_CODE(200, "OK");
json = json_body_sent[0];
CHECK(json);
CHECK(cJSON_IsObject (json));
WEE_CHECK_OBJ_STR("command", json, "context");
WEE_CHECK_OBJ_STR("quer", json, "base_word");
WEE_CHECK_OBJ_NUM(1, json, "position_replace");
WEE_CHECK_OBJ_BOOL(1, json, "add_space");
json_array = cJSON_GetObjectItem (json, "list");
CHECK(json_array);
CHECK(cJSON_IsArray (json_array));
CHECK(cJSON_GetArraySize (json_array) == 1);
STRCMP_EQUAL("query", cJSON_GetStringValue (cJSON_GetArrayItem (json_array, 0)));
}
/*
* Tests functions:
* relay_api_protocol_cb_input
@@ -622,7 +702,7 @@ TEST(RelayApiProtocolWithClient, CbInput)
NULL,
"{\"buffer_name\": \"invalid\", "
"\"command\": \"/print test\"}");
STRCMP_EQUAL("HTTP/1.1 404 Not Found\r\n"
STRCMP_EQUAL("HTTP/1.1 400 Bad Request\r\n"
"Access-Control-Allow-Origin: *\r\n"
"Content-Type: application/json; charset=utf-8\r\n"
"Content-Length: 41\r\n"