| 153 | * ══════════════════════════════════════════════════════════════════ */ |
| 154 | |
| 155 | int cbm_jsonrpc_parse(const char *line, cbm_jsonrpc_request_t *out) { |
| 156 | memset(out, 0, sizeof(*out)); |
| 157 | out->id = CBM_NOT_FOUND; |
| 158 | |
| 159 | yyjson_doc *doc = yyjson_read(line, strlen(line), 0); |
| 160 | if (!doc) { |
| 161 | return CBM_NOT_FOUND; |
| 162 | } |
| 163 | |
| 164 | yyjson_val *root = yyjson_doc_get_root(doc); |
| 165 | if (!yyjson_is_obj(root)) { |
| 166 | yyjson_doc_free(doc); |
| 167 | return CBM_NOT_FOUND; |
| 168 | } |
| 169 | |
| 170 | yyjson_val *v_jsonrpc = yyjson_obj_get(root, "jsonrpc"); |
| 171 | yyjson_val *v_method = yyjson_obj_get(root, "method"); |
| 172 | yyjson_val *v_id = yyjson_obj_get(root, "id"); |
| 173 | yyjson_val *v_params = yyjson_obj_get(root, "params"); |
| 174 | |
| 175 | if (!v_method || !yyjson_is_str(v_method)) { |
| 176 | yyjson_doc_free(doc); |
| 177 | return CBM_NOT_FOUND; |
| 178 | } |
| 179 | |
| 180 | out->jsonrpc = |
| 181 | heap_strdup(v_jsonrpc && yyjson_is_str(v_jsonrpc) ? yyjson_get_str(v_jsonrpc) : "2.0"); |
| 182 | out->method = heap_strdup(yyjson_get_str(v_method)); |
| 183 | |
| 184 | if (v_id) { |
| 185 | out->has_id = true; |
| 186 | if (yyjson_is_int(v_id)) { |
| 187 | out->id = yyjson_get_int(v_id); |
| 188 | } else if (yyjson_is_str(v_id)) { |
| 189 | /* JSON-RPC 2.0 §4 permits string ids (Claude Desktop uses them). |
| 190 | * Preserve verbatim instead of coercing via strtol (issue #253). */ |
| 191 | out->id_str = heap_strdup(yyjson_get_str(v_id)); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | if (v_params) { |
| 196 | out->params_raw = yyjson_val_write(v_params, 0, NULL); |
| 197 | } |
| 198 | |
| 199 | yyjson_doc_free(doc); |
| 200 | return 0; |
| 201 | } |
| 202 | |
| 203 | void cbm_jsonrpc_request_free(cbm_jsonrpc_request_t *r) { |
| 204 | if (!r) { |
no test coverage detected