///|
/// Escapes a string for JSON output.
pub fn json_escape(value : String) -> String {
let builder = StringBuilder(size_hint=value.length())
for index = 0; index < value.length(); index = index + 1 {
match value.unsafe_get(index) {
'"' => builder.write_string("\\\"")
'\\' => builder.write_string("\\\\")
'\n' => builder.write_string("\\n")
'\r' => builder.write_string("\\r")
'\t' => builder.write_string("\\t")
unit => builder.write_char(unit.to_int().unsafe_to_char())
}
}
builder.to_string()
}
///|
fn optional_string_json(value : String?) -> String {
match value {
Some(text) => "\"\{json_escape(text)}\""
None => "null"
}
}
///|
/// Encodes one placement.
pub fn KeyPlacement::to_json(self : KeyPlacement) -> String {
let builder = StringBuilder()
builder.write_string("{\"key\":\"\{json_escape(self.key)}\",\"owners\":[")
for index = 0; index < self.owners.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
builder.write_string("\"\{json_escape(self.owners[index])}\"")
}
builder.write_string(
"],\"complete\":\{self.complete},\"message\":\"\{json_escape(self.message)}\"}",
)
builder.to_string()
}
///|
/// Returns a stable name for one migration action.
pub fn move_kind_name(kind : MoveKind) -> String {
match kind {
PrimaryChanged => "primary_changed"
ReplicaAdded => "replica_added"
ReplicaRemoved => "replica_removed"
}
}
///|
/// Returns the stable machine-readable name of a workflow phase.
pub fn migration_phase_name(phase : MigrationPhase) -> String {
match phase {
AddTarget => "add_target"
Backfill => "backfill"
Verify => "verify"
SwitchPrimary => "switch_primary"
RemoveSource => "remove_source"
}
}
///|
fn write_migration_action(
builder : StringBuilder,
action : MigrationAction,
) -> Unit {
builder.write_string(
"{\"key\":\"\{json_escape(action.key)}\",\"phase\":\"\{migration_phase_name(action.phase)}\",\"from\":\{optional_string_json(action.from_node)},\"to\":\{optional_string_json(action.to_node)}}",
)
}
///|
/// Encodes a safe migration workflow for audit logs and control planes.
pub fn SafeMigrationPlan::to_json(self : SafeMigrationPlan) -> String {
let builder = StringBuilder()
builder.write_string(
"{\"keys\":\{self.keys},\"migrating_keys\":\{self.migrating_keys},\"blocked_keys\":[",
)
for index = 0; index < self.blocked_keys.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
builder.write_string("\"\{json_escape(self.blocked_keys[index])}\"")
}
builder.write_string(
"],\"max_actions_per_wave\":\{self.max_actions_per_wave},\"max_actions_per_node\":\{self.max_actions_per_node},\"waves\":[",
)
for wave_index = 0
wave_index < self.waves.length()
wave_index = wave_index + 1 {
if wave_index > 0 {
builder.write_char(',')
}
let wave = self.waves[wave_index]
builder.write_string(
"{\"index\":\{wave.index},\"phase\":\"\{migration_phase_name(wave.phase)}\",\"actions\":[",
)
for action_index = 0
action_index < wave.actions.length()
action_index = action_index + 1 {
if action_index > 0 {
builder.write_char(',')
}
write_migration_action(builder, wave.actions[action_index])
}
builder.write_string("]}")
}
builder.write_string("]}")
builder.to_string()
}
///|
/// Encodes one migration plan.
pub fn MigrationPlan::to_json(self : MigrationPlan) -> String {
let builder = StringBuilder()
builder.write_string(
"{\"keys\":\{self.keys},\"moved_keys\":\{self.moved_keys},\"unchanged_keys\":\{self.unchanged_keys},\"moves\":[",
)
for index = 0; index < self.moves.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
let item = self.moves[index]
builder.write_string(
"{\"key\":\"\{json_escape(item.key)}\",\"from\":\{optional_string_json(item.from_node)},\"to\":\{optional_string_json(item.to_node)},\"kind\":\"\{move_kind_name(item.kind)}\"}",
)
}
builder.write_string("]}")
builder.to_string()
}
///|
/// Encodes distribution evidence.
pub fn DistributionReport::to_json(self : DistributionReport) -> String {
let builder = StringBuilder()
builder.write_string(
"{\"keys\":\{self.keys},\"replicas\":\{self.replicas},\"min_primary\":\{self.min_primary},\"max_primary\":\{self.max_primary},\"spread\":\{self.spread},\"incomplete_placements\":\{self.incomplete_placements},\"zone_violations\":\{self.zone_violations},\"rack_violations\":\{self.rack_violations},\"nodes\":[",
)
for index = 0; index < self.nodes.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
let load = self.nodes[index]
builder.write_string(
"{\"node_id\":\"\{json_escape(load.node_id)}\",\"primary_keys\":\{load.primary_keys},\"replica_keys\":\{load.replica_keys}}",
)
}
builder.write_string("]}")
builder.to_string()
}
///|
/// Encodes capacity-admitted placement evidence for control-plane logs.
pub fn CapacityPlan::to_json(self : CapacityPlan) -> String {
let builder = StringBuilder()
builder.write_string(
"{\"replicas\":\{self.replicas},\"accepted_keys\":\{self.accepted_keys()},\"rejected_keys\":[",
)
for index = 0; index < self.rejected_keys.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
builder.write_string("\"\{json_escape(self.rejected_keys[index])}\"")
}
builder.write_string("],\"loads\":[")
for index = 0; index < self.loads.length(); index = index + 1 {
if index > 0 {
builder.write_char(',')
}
let load = self.loads[index]
builder.write_string(
"{\"node_id\":\"\{json_escape(load.node_id)}\",\"primary_keys\":\{load.primary_keys},\"replica_keys\":\{load.replica_keys},\"max_primary\":\{load.max_primary},\"max_replicas\":\{load.max_replicas}}",
)
}
builder.write_string("],\"within_capacity\":\{self.is_within_capacity()}}")
builder.to_string()
}