| 123 | * ══════════════════════════════════════════════════════════════════ */ |
| 124 | |
| 125 | int cbm_jsonrpc_parse(const char *line, cbm_jsonrpc_request_t *out) { |
| 126 | memset(out, 0, sizeof(*out)); |
| 127 | out->id = CBM_NOT_FOUND; |
| 128 | |
| 129 | yyjson_doc *doc = yyjson_read(line, strlen(line), 0); |
| 130 | if (!doc) { |
| 131 | return CBM_NOT_FOUND; |
| 132 | } |
| 133 | |
| 134 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 135 | if (!yyjson_is_obj(root)) { |
| 136 | yyjson_doc_free(doc); |
| 137 | return CBM_NOT_FOUND; |
| 138 | } |
| 139 | |
| 140 | yyjson_val *v_jsonrpc = yyjson_obj_get(root, "jsonrpc"); |
| 141 | yyjson_val *v_method = yyjson_obj_get(root, "method"); |
| 142 | yyjson_val *v_id = yyjson_obj_get(root, "id"); |
| 143 | yyjson_val *v_params = yyjson_obj_get(root, "params"); |
| 144 | |
| 145 | if (!v_method || !yyjson_is_str(v_method)) { |
| 146 | yyjson_doc_free(doc); |
| 147 | return CBM_NOT_FOUND; |
| 148 | } |
| 149 | |
| 150 | out->jsonrpc = |
| 151 | heap_strdup(v_jsonrpc && yyjson_is_str(v_jsonrpc) ? yyjson_get_str(v_jsonrpc) : "2.0"); |
| 152 | out->method = heap_strdup(yyjson_get_str(v_method)); |
| 153 | |
| 154 | if (v_id) { |
| 155 | out->has_id = true; |
| 156 | if (yyjson_is_int(v_id)) { |
| 157 | out->id = yyjson_get_int(v_id); |
| 158 | } else if (yyjson_is_str(v_id)) { |
| 159 | /* JSON-RPC 2.0 §4 permits string ids (Claude Desktop uses them). |
| 160 | * Preserve verbatim instead of coercing via strtol (issue #253). */ |
| 161 | out->id_str = heap_strdup(yyjson_get_str(v_id)); |
| 162 | } |
| 163 | } |
| 164 | |
| 165 | if (v_params) { |
| 166 | out->params_raw = yyjson_val_write(v_params, 0, NULL); |
| 167 | } |
| 168 | |
| 169 | yyjson_doc_free(doc); |
| 170 | return 0; |
| 171 | } |
| 172 | |
| 173 | void cbm_jsonrpc_request_free(cbm_jsonrpc_request_t *r) { |
| 174 | if (!r) { |
no test coverage detected