- LSPMessageValidator: JSON-RPC 2.0 shape validation (method, id type, params type) - LSPCapabilityNegotiator: per-source capability tracking; shouldRoute gates on declared caps - LSPRequestThrottler: per-URI/method debounce; blocks rapid didChange floods - LSPErrorRecovery: per-source health tracking; stale-cache fallback when crashed - Sprint278IntegrationSummary: poly-parse (C++ + Haskell) end-to-end integration 26/26 tests passing (5+5+5+5+6). All new headers well under 600 lines. LoRA recorded: sprint278-2026-03-01 (9 calls, 6437 tokens). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
98 lines
3.4 KiB
C++
98 lines
3.4 KiB
C++
// Step 1918: LSPMessageValidator
|
|
// Validates JSON-RPC 2.0 message shape before routing.
|
|
//
|
|
// t1: valid request passes
|
|
// t2: missing jsonrpc field rejected
|
|
// t3: wrong jsonrpc version rejected
|
|
// t4: non-string method rejected; missing method rejected
|
|
// t5: bad id type rejected; bad params type rejected; notification/request detection
|
|
|
|
#include "LSPMessageValidator.h"
|
|
#include <iostream>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using json = nlohmann::json;
|
|
namespace ws = whetstone;
|
|
|
|
static int p=0,f=0;
|
|
#define T(n) { std::cout<<" "<<#n<<"... "; }
|
|
#define P() { std::cout<<"PASS\n"; ++p; }
|
|
#define F(m) { std::cout<<"FAIL: "<<m<<"\n"; ++f; }
|
|
#define C(c,m) if(!(c)){F(m);return;}
|
|
|
|
void t1(){
|
|
T(valid_request_passes);
|
|
ws::LSPMessageValidator v;
|
|
json msg = {{"jsonrpc","2.0"},{"id",1},{"method","textDocument/definition"},
|
|
{"params",{{"textDocument",{{"uri","file:///a.go"}}}}}};
|
|
auto r = v.validate(msg);
|
|
C(r.valid, "valid");
|
|
C(r.errorCode == 0, "no error code");
|
|
C(r.errorMessage.empty(),"no error message");
|
|
// notification (no id) also valid
|
|
json notif = {{"jsonrpc","2.0"},{"method","textDocument/didOpen"},{"params",json::object()}};
|
|
C(v.validate(notif).valid, "notification valid");
|
|
P();
|
|
}
|
|
|
|
void t2(){
|
|
T(missing_jsonrpc_rejected);
|
|
ws::LSPMessageValidator v;
|
|
json msg = {{"method","textDocument/hover"}};
|
|
auto r = v.validate(msg);
|
|
C(!r.valid, "invalid");
|
|
C(r.errorCode == -32600, "code -32600");
|
|
P();
|
|
}
|
|
|
|
void t3(){
|
|
T(wrong_jsonrpc_version_rejected);
|
|
ws::LSPMessageValidator v;
|
|
json msg = {{"jsonrpc","1.0"},{"method","textDocument/hover"}};
|
|
auto r = v.validate(msg);
|
|
C(!r.valid, "invalid");
|
|
C(r.errorCode == -32600, "code -32600");
|
|
P();
|
|
}
|
|
|
|
void t4(){
|
|
T(non_string_method_and_missing_method_rejected);
|
|
ws::LSPMessageValidator v;
|
|
json bad1 = {{"jsonrpc","2.0"},{"method",42}};
|
|
C(!v.validate(bad1).valid, "number method invalid");
|
|
json bad2 = {{"jsonrpc","2.0"},{"id",1}};
|
|
C(!v.validate(bad2).valid, "missing method invalid");
|
|
C(v.validate(bad2).errorCode == -32600, "code -32600");
|
|
P();
|
|
}
|
|
|
|
void t5(){
|
|
T(bad_id_bad_params_and_detection);
|
|
ws::LSPMessageValidator v;
|
|
// id as object: invalid
|
|
json badId = {{"jsonrpc","2.0"},{"method","x"},{"id",json::object()}};
|
|
C(!v.validate(badId).valid, "object id invalid");
|
|
// id as null: valid
|
|
json nullId = {{"jsonrpc","2.0"},{"method","x"},{"id",nullptr}};
|
|
C(v.validate(nullId).valid, "null id valid");
|
|
// params as string: invalid
|
|
json badParams = {{"jsonrpc","2.0"},{"method","x"},{"params","oops"}};
|
|
C(!v.validate(badParams).valid, "string params invalid");
|
|
C(v.validate(badParams).errorCode == -32602, "params error code -32602");
|
|
// isNotification / isRequest
|
|
json notif = {{"jsonrpc","2.0"},{"method","didOpen"}};
|
|
json req = {{"jsonrpc","2.0"},{"method","definition"},{"id",2}};
|
|
C( ws::LSPMessageValidator::isNotification(notif), "notif is notification");
|
|
C(!ws::LSPMessageValidator::isNotification(req), "req is not notification");
|
|
C( ws::LSPMessageValidator::isRequest(req), "req is request");
|
|
C(!ws::LSPMessageValidator::isRequest(notif), "notif is not request");
|
|
P();
|
|
}
|
|
|
|
int main(){
|
|
std::cout << "Step 1918: LSPMessageValidator\n";
|
|
t1(); t2(); t3(); t4(); t5();
|
|
std::cout << "\n" << p << "/" << (p+f) << " passed\n";
|
|
return f > 0 ? 1 : 0;
|
|
}
|