///|
/// Decode one complete JSON-RPC message from JSON text.
///
/// This function parses exactly one JSON value and then validates its complete
/// envelope. It does not implement line framing; callers that use stdio must
/// split frames before calling it.
pub fn jsonrpc_decode(
source : String,
) -> JsonRpcMessage raise JsonRpcCodecError {
let value = @json.parse(source) catch {
error => raise ParseError(message=error.to_string())
}
jsonrpc_decode_json(value)
}
///|
/// Decode one JSON-RPC message from an already parsed JSON value.
pub fn jsonrpc_decode_json(
value : Json,
) -> JsonRpcMessage raise JsonRpcCodecError {
match value {
Object(fields) => decode_message_object(fields)
_ => raise InvalidRequest(reason="JSON-RPC envelope must be an object")
}
}
///|
/// Encode one JSON-RPC message as compact JSON text.
///
/// The returned text contains no framing newline. Newline-delimited stdio is
/// a runtime concern and must append its own delimiter.
pub fn jsonrpc_encode(
message : JsonRpcMessage,
) -> String raise JsonRpcCodecError {
jsonrpc_to_json(message).stringify()
}
///|
/// Encode one JSON-RPC message as a JSON value.
pub fn jsonrpc_to_json(
message : JsonRpcMessage,
) -> Json raise JsonRpcCodecError {
match message {
Request(request) => request_to_json(request)
Notification(notification) => notification_to_json(notification)
Response(response) => response_to_json(response)
}
}
///|
fn request_to_json(request : JsonRpcRequest) -> Json raise JsonRpcCodecError {
validate_params(request.params)
let fields : Map[String, Json] = Map([])
fields["jsonrpc"] = Json::string(JSON_RPC_VERSION)
fields["id"] = encode_request_id(request.id)
fields["method"] = Json::string(request.method_name)
match request.params {
None => ()
Some(params) => fields["params"] = params
}
Json::object(fields)
}
///|
fn notification_to_json(
notification : JsonRpcNotification,
) -> Json raise JsonRpcCodecError {
validate_params(notification.params)
let fields : Map[String, Json] = Map([])
fields["jsonrpc"] = Json::string(JSON_RPC_VERSION)
fields["method"] = Json::string(notification.method_name)
match notification.params {
None => ()
Some(params) => fields["params"] = params
}
Json::object(fields)
}
///|
fn response_to_json(response : JsonRpcResponse) -> Json raise JsonRpcCodecError {
let fields : Map[String, Json] = Map([])
fields["jsonrpc"] = Json::string(JSON_RPC_VERSION)
match response {
Success(success) => {
fields["id"] = encode_request_id(success.id)
fields["result"] = success.result
}
Error(failure) => {
fields["id"] = encode_id(failure.id)
fields["error"] = error_to_json(failure.error)
}
}
Json::object(fields)
}
///|
fn error_to_json(error : JsonRpcError) -> Json raise JsonRpcCodecError {
let fields : Map[String, Json] = Map([])
validate_error_code(error.code)
let code = error.code.to_int()
fields["code"] = Json::number(code.to_double(), repr=code.to_string())
fields["message"] = Json::string(error.message)
match error.data {
None => ()
Some(data) => fields["data"] = data
}
Json::object(fields)
}
///|
fn encode_id(id : JsonRpcId) -> Json {
match id {
String(value) => Json::string(value)
Number(value) => Json::number(value.to_double(), repr=value.to_string())
Null => Json::null()
}
}
///|
fn encode_request_id(id : RequestId) -> Json {
match id {
String(value) => Json::string(value)
Number(value) => Json::number(value.to_double(), repr=value.to_string())
Null => Json::null()
}
}
///|
fn validate_params(params : Json?) -> Unit raise JsonRpcCodecError {
match params {
None => ()
Some(value) =>
match value {
Array(_) | Object(_) | Null => ()
_ =>
raise InvalidParams(reason="params must be an object, array, or null")
}
}
}
///|
fn decode_message_object(
fields : Map[String, Json],
) -> JsonRpcMessage raise JsonRpcCodecError {
decode_version(fields)
let has_id = fields.contains("id")
let has_method = fields.contains("method")
let has_params = fields.contains("params")
let has_result = fields.contains("result")
let has_error = fields.contains("error")
if has_method {
if has_result || has_error {
raise InvalidRequest(
reason="method envelope cannot contain result or error",
)
}
let method_name = decode_method_name(fields.get("method"), path="method")
let params = decode_params(fields.get("params"))
if has_id {
let id = decode_request_id(fields.get("id"))
Request({ id, method_name, params })
} else {
Notification({ method_name, params })
}
} else if has_result || has_error {
if has_params {
raise InvalidRequest(reason="response envelope cannot contain params")
}
if !has_id {
raise MissingField(path="id")
}
if has_result && has_error {
raise ConflictingFields(path="result,error")
}
if has_result {
let id = decode_request_id(fields.get("id"))
let result = match fields.get("result") {
Some(value) => value
None => raise MissingField(path="result")
}
Response(Success({ id, result }))
} else {
let id = decode_id(fields.get("id"))
let error_value = match fields.get("error") {
Some(value) => value
None => raise MissingField(path="error")
}
let error = decode_error(error_value)
Response(Error({ id, error }))
}
} else {
raise InvalidRequest(reason="message must contain method or result/error")
}
}
///|
fn decode_version(fields : Map[String, Json]) -> Unit raise JsonRpcCodecError {
let value = match fields.get("jsonrpc") {
Some(value) => value
None => raise MissingField(path="jsonrpc")
}
match value {
String(version) =>
if version != JSON_RPC_VERSION {
raise InvalidField(path="jsonrpc", reason="must be exactly \"2.0\"")
}
_ => raise InvalidField(path="jsonrpc", reason="must be a string")
}
}
///|
fn decode_method_name(
value : Json?,
path~ : String,
) -> String raise JsonRpcCodecError {
match value {
Some(String(method_name)) => method_name
Some(_) => raise InvalidField(path~, reason="must be a string")
None => raise MissingField(path~)
}
}
///|
fn decode_params(value : Json?) -> Json? raise JsonRpcCodecError {
match value {
None => None
Some(params) =>
match params {
Array(_) | Object(_) | Null => Some(params)
_ =>
raise InvalidParams(reason="params must be an object, array, or null")
}
}
}
///|
fn decode_request_id(value : Json?) -> RequestId raise JsonRpcCodecError {
match value {
None => raise MissingField(path="id")
Some(String(value)) => String(value)
Some(Number(value, repr~)) => Number(decode_request_integer(value, repr))
Some(Null) => Null
Some(_) =>
raise InvalidId(reason="request id must be a string, integer, or null")
}
}
///|
fn decode_id(value : Json?) -> JsonRpcId raise JsonRpcCodecError {
match value {
None => raise MissingField(path="id")
Some(String(value)) => String(value)
Some(Null) => Null
Some(Number(value, repr~)) => Number(decode_request_integer(value, repr))
Some(_) =>
raise InvalidId(reason="response id must be a string, integer, or null")
}
}
///|
priv enum JsonRpcInt64NumberError {
Malformed
NonFinite
Fractional
OutOfRange
PrecisionUnavailable
}
///|
/// Decode an integer-valued JSON number without trusting a rounded `Double`.
///
/// The core JSON parser retains the original number text in `repr` whenever a
/// literal cannot be represented by a finite, lossless `Double`. For those
/// values this function parses the retained decimal text directly. A finite
/// number without `repr` is accepted only inside the exact binary-integer
/// range; larger values are rejected rather than silently rounded.
fn jsonrpc_int64_number(
value : Double,
repr : String?,
) -> Result[Int64, JsonRpcInt64NumberError] {
match repr {
Some(text) => jsonrpc_parse_int64_repr(text)
None =>
if value.is_nan() || value.is_inf() {
Err(NonFinite)
} else if value != value.floor() {
Err(Fractional)
} else if value.abs() > 9007199254740991.0 {
Err(PrecisionUnavailable)
} else {
Ok(value.to_int64())
}
}
}
///|
fn jsonrpc_parse_int64_repr(
text : String,
) -> Result[Int64, JsonRpcInt64NumberError] {
let chars = text.to_array()
let length = chars.length()
if length == 0 {
return Err(Malformed)
}
let mut index = 0
if chars[index] == '-' {
index += 1
if index == length {
return Err(Malformed)
}
}
let start = index
if chars[index] == '0' {
index += 1
if index < length && jsonrpc_is_digit(chars[index]) {
return Err(Malformed)
}
} else if jsonrpc_is_digit(chars[index]) {
index += 1
while index < length && jsonrpc_is_digit(chars[index]) {
index += 1
}
} else {
return Err(Malformed)
}
let decimal_index : Int? = if index < length && chars[index] == '.' {
let decimal = index
index += 1
let fraction_start = index
while index < length && jsonrpc_is_digit(chars[index]) {
index += 1
}
if index == fraction_start {
return Err(Malformed)
}
Some(decimal)
} else {
None
}
let exponent_index : Int? = if index < length &&
(chars[index] == 'e' || chars[index] == 'E') {
let exponent = index
index += 1
if index < length && (chars[index] == '+' || chars[index] == '-') {
index += 1
}
let exponent_start = index
while index < length && jsonrpc_is_digit(chars[index]) {
index += 1
}
if index == exponent_start {
return Err(Malformed)
}
Some(exponent)
} else {
None
}
if index != length {
return Err(Malformed)
}
match (decimal_index, exponent_index) {
(None, None) => jsonrpc_parse_int64_text(text)
_ =>
jsonrpc_parse_int64_decimal(chars, start, decimal_index, exponent_index)
}
}
///|
fn jsonrpc_is_digit(value : Char) -> Bool {
let digit = value.to_int() - '0'
digit >= 0 && digit <= 9
}
///|
fn jsonrpc_parse_int64_text(
text : String,
) -> Result[Int64, JsonRpcInt64NumberError] {
let chars = text.to_array()
let length = chars.length()
if length == 0 {
return Err(OutOfRange)
}
let negative = chars[0] == '-'
let start = if negative { 1 } else { 0 }
if start == length {
return Err(OutOfRange)
}
let min_value : Int64 = -9223372036854775808L
let max_value : Int64 = 9223372036854775807L
let mut value : Int64 = 0L
for index in start.. 9 {
return Err(OutOfRange)
}
let digit64 = digit.to_int64()
if negative {
if value < min_value / 10L || (value == min_value / 10L && digit64 > 8L) {
return Err(OutOfRange)
}
value = value * 10L - digit64
} else {
if value > max_value / 10L || (value == max_value / 10L && digit64 > 7L) {
return Err(OutOfRange)
}
value = value * 10L + digit64
}
}
Ok(value)
}
///|
priv enum JsonRpcDecimalShift {
Finite(negative~ : Bool, magnitude~ : Int)
Huge(negative~ : Bool)
}
///|
fn jsonrpc_decimal_shift(
chars : Array[Char],
start : Int,
end : Int,
) -> JsonRpcDecimalShift {
let negative = chars[start] == '-'
let digit_start = if negative || chars[start] == '+' {
start + 1
} else {
start
}
let mut magnitude = 0
let mut huge = false
for index in digit_start.. 100000 / 10 || magnitude * 10 > 100000 - digit {
huge = true
} else {
magnitude = magnitude * 10 + digit
}
}
}
if huge {
Huge(negative~)
} else {
Finite(negative~, magnitude~)
}
}
///|
fn jsonrpc_trim_leading_zeroes(text : String) -> String {
let chars = text.to_array()
let mut first = 0
for index in 0.. Result[Int64, JsonRpcInt64NumberError] {
let mantissa_end = match exponent_index {
Some(index) => index
None => chars.length()
}
let fractional_digits = match decimal_index {
Some(index) => mantissa_end - index - 1
None => 0
}
let digits_builder = StringBuilder(size_hint=mantissa_end - start)
let mut nonzero = false
for index in start.. jsonrpc_decimal_shift(chars, index + 1, chars.length())
None => Finite(negative=false, magnitude=0)
}
let scale = match shift {
Finite(negative=true, magnitude~) => -magnitude - fractional_digits
Finite(negative=false, magnitude~) => magnitude - fractional_digits
Huge(negative=true) => return Err(Fractional)
Huge(negative=false) => return Err(OutOfRange)
}
if scale < 0 {
let remove = -scale
if remove >= digits.length() {
return Err(Fractional)
}
let digit_chars = digits.to_array()
for index in (digits.length() - remove).. 19 {
return Err(OutOfRange)
}
let zeros = "0".repeat(scale)
let sign = if start == 1 { "-" } else { "" }
jsonrpc_parse_int64_text(sign + digits + zeros)
}
}
///|
fn jsonrpc_int64_error_reason(error : JsonRpcInt64NumberError) -> String {
match error {
Malformed => "must be a valid JSON number"
NonFinite => "must be a finite integer"
Fractional => "must be an integer"
OutOfRange => "must fit in signed 64-bit integer"
PrecisionUnavailable =>
"must be an exactly represented signed 64-bit integer"
}
}
///|
fn decode_request_integer(
value : Double,
repr : String?,
) -> Int64 raise JsonRpcCodecError {
match jsonrpc_int64_number(value, repr) {
Ok(integer) => integer
Err(error) => raise InvalidId(reason=jsonrpc_int64_error_reason(error))
}
}
///|
pub fn jsonrpc_decode_int64(
value : Double,
repr : String?,
path~ : String,
) -> Int64 raise JsonRpcCodecError {
match jsonrpc_int64_number(value, repr) {
Ok(integer) => integer
Err(error) =>
raise InvalidInteger(path~, reason=jsonrpc_int64_error_reason(error))
}
}
///|
fn decode_integer(value : Double) -> Int raise JsonRpcCodecError {
if value.is_nan() || value.is_inf() {
raise InvalidId(reason="number must be finite")
}
if value != value.floor() {
raise InvalidId(reason="number must be an integer")
}
if value < -2147483648.0 || value > 2147483647.0 {
raise InvalidId(reason="integer is outside the Int32 range")
}
value.to_int()
}
///|
fn decode_error(value : Json) -> JsonRpcError raise JsonRpcCodecError {
match value {
Object(fields) => {
let code_value = match fields.get("code") {
Some(value) => value
None => raise InvalidError(reason="error.code is required")
}
let code = match code_value {
Number(value, ..) =>
decode_integer(value) catch {
_ =>
raise InvalidError(reason="error.code must be a finite integer")
}
_ => raise InvalidError(reason="error.code must be a number")
}
let message = match fields.get("message") {
Some(String(value)) => value
Some(_) => raise InvalidError(reason="error.message must be a string")
None => raise InvalidError(reason="error.message is required")
}
let data = fields.get("data")
{ code: JsonRpcErrorCode::from_int(code), message, data }
}
_ => raise InvalidError(reason="error must be an object")
}
}