///|
pub(all) struct QuotaKey {
scope : String
subject : String
resource : String
} derive(Debug, Eq)
///|
pub(all) struct Decision {
allowed : Bool
reason : String
cost : Int
remaining : Int
retry_after_ms : Int64
reset_after_ms : Int64
trace : Array[String]
} derive(Debug, Eq)
///|
pub(all) struct LimitSpec {
name : String
capacity : Int
refill : Int
window_ms : Int64
burst : Int
} derive(Debug, Eq)
///|
pub(all) struct BucketState {
tokens : Int
last_refill_ms : Int64
} derive(Debug, Eq)
///|
pub(all) struct WindowCounter {
window_start_ms : Int64
used : Int
} derive(Debug, Eq)
///|
pub(all) struct GcraState {
theoretical_arrival_ms : Int64
} derive(Debug, Eq)
///|
pub(all) struct EvaluationInput {
key : QuotaKey
now_ms : Int64
cost : Int
} derive(Debug, Eq)
///|
pub(all) struct QuotaCharge {
key : QuotaKey
limit_name : String
cost : Int
} derive(Debug, Eq)
///|
pub(all) struct AtomicQuotaReport {
allowed : Bool
committed : Bool
failed_index : Int
decisions : Array[Decision]
} derive(Debug)
///|
pub(all) struct BatchReport {
total : Int
allowed : Int
denied : Int
decisions : Array[Decision]
} derive(Debug)
///|
pub(all) struct ValidationIssue {
code : String
message : String
} derive(Debug, Eq)
///|
pub(all) struct QuotaStats {
limits : Int
token_buckets : Int
windows : Int
gcra_flows : Int
subjects : Int
} derive(Debug, Eq)
///|
/// A deterministic in-memory snapshot for application-managed persistence.
/// The library deliberately does not perform I/O; adapters may serialize this
/// value to a database, distributed cache, or file without changing policy.
pub(all) struct QuotaSnapshot {
limits : Array[LimitSpec]
buckets : Array[TrackedBucket]
windows : Array[TrackedWindow]
gcra_flows : Array[TrackedGcra]
links : Array[QuotaLink]
subjects : Array[SubjectBudget]
} derive(Debug)
///|
pub(all) struct TrackedBucket {
key : QuotaKey
limit_name : String
state : BucketState
} derive(Debug, Eq)
///|
pub(all) struct TrackedWindow {
key : QuotaKey
limit_name : String
state : WindowCounter
} derive(Debug, Eq)
///|
pub(all) struct TrackedGcra {
key : QuotaKey
limit_name : String
state : GcraState
} derive(Debug, Eq)
///|
pub(all) struct QuotaLink {
child : QuotaKey
parent : QuotaKey
limit_name : String
ratio : Int
} derive(Debug, Eq)
///|
pub(all) struct SubjectBudget {
subject : String
weight : Int
used : Int
} derive(Debug, Eq)
///|
pub(all) struct FairShareItem {
subject : String
weight : Int
used : Int
score : Int
} derive(Debug, Eq)
///|
pub(all) struct QuotaEngine {
limits : Array[LimitSpec]
buckets : Array[TrackedBucket]
windows : Array[TrackedWindow]
gcra_flows : Array[TrackedGcra]
links : Array[QuotaLink]
subjects : Array[SubjectBudget]
} derive(Debug)
///|
fn max_int(a : Int, b : Int) -> Int {
if a > b {
a
} else {
b
}
}
///|
fn min_int(a : Int, b : Int) -> Int {
if a < b {
a
} else {
b
}
}
///|
fn non_negative(value : Int) -> Int {
if value < 0 {
0
} else {
value
}
}
///|
fn positive_cost(cost : Int) -> Int {
max_int(1, cost)
}
///|
pub fn QuotaKey::new(
scope : String,
subject : String,
resource : String,
) -> QuotaKey {
{ scope, subject, resource }
}
///|
pub fn QuotaKey::key(self : QuotaKey) -> String {
"\{self.scope}:\{self.subject}:\{self.resource}"
}
///|
pub fn EvaluationInput::new(
key : QuotaKey,
now_ms : Int64,
cost : Int,
) -> EvaluationInput {
{ key, now_ms, cost }
}
///|
pub fn QuotaCharge::new(
key : QuotaKey,
limit_name : String,
cost : Int,
) -> QuotaCharge {
{ key, limit_name, cost: positive_cost(cost) }
}
///|
pub fn AtomicQuotaReport::to_json(self : AtomicQuotaReport) -> String {
"{ \"allowed\": \{self.allowed}, \"committed\": \{self.committed}, \"failed_index\": \{self.failed_index}, \"decisions\": \{self.decisions.length()} }"
}
///|
pub fn LimitSpec::new(
name : String,
capacity : Int,
refill : Int,
window_ms : Int64,
burst : Int,
) -> LimitSpec {
{ name, capacity, refill, window_ms, burst }
}
///|
pub fn LimitSpec::per_second(name : String, capacity : Int) -> LimitSpec {
{ name, capacity, refill: capacity, window_ms: 1000L, burst: capacity }
}
///|
pub fn LimitSpec::per_minute(name : String, capacity : Int) -> LimitSpec {
{ name, capacity, refill: capacity, window_ms: 60000L, burst: capacity }
}
///|
pub fn Decision::allow(
reason : String,
cost : Int,
remaining : Int,
reset_after_ms : Int64,
trace : Array[String],
) -> Decision {
{
allowed: true,
reason,
cost,
remaining,
retry_after_ms: 0L,
reset_after_ms,
trace,
}
}
///|
pub fn Decision::deny(
reason : String,
cost : Int,
remaining : Int,
retry_after_ms : Int64,
reset_after_ms : Int64,
trace : Array[String],
) -> Decision {
{
allowed: false,
reason,
cost,
remaining,
retry_after_ms,
reset_after_ms,
trace,
}
}
///|
pub fn Decision::to_json(self : Decision) -> String {
"{ \"allowed\": \{self.allowed}, \"reason\": \"\{self.reason}\", \"cost\": \{self.cost}, \"remaining\": \{self.remaining}, \"retry_after_ms\": \{self.retry_after_ms}, \"reset_after_ms\": \{self.reset_after_ms} }"
}
///|
pub fn BatchReport::to_json(self : BatchReport) -> String {
"{ \"total\": \{self.total}, \"allowed\": \{self.allowed}, \"denied\": \{self.denied} }"
}
///|
pub fn QuotaStats::to_json(self : QuotaStats) -> String {
"{ \"limits\": \{self.limits}, \"token_buckets\": \{self.token_buckets}, \"windows\": \{self.windows}, \"gcra_flows\": \{self.gcra_flows}, \"subjects\": \{self.subjects} }"
}
///|
pub fn ValidationIssue::to_json(self : ValidationIssue) -> String {
"{ \"code\": \"\{self.code}\", \"message\": \"\{self.message}\" }"
}
///|
pub fn BucketState::new(tokens : Int, last_refill_ms : Int64) -> BucketState {
{ tokens: non_negative(tokens), last_refill_ms }
}
///|
pub fn TrackedBucket::new(
key : QuotaKey,
limit_name : String,
state : BucketState,
) -> TrackedBucket {
{ key, limit_name, state }
}
///|
pub fn QuotaEngine::new() -> QuotaEngine {
{
limits: [],
buckets: [],
windows: [],
gcra_flows: [],
links: [],
subjects: [],
}
}
///|
/// Copies all deterministic state for persistence, hand-off, or replication.
pub fn QuotaEngine::snapshot(self : QuotaEngine) -> QuotaSnapshot {
{
limits: self.limits.copy(),
buckets: self.buckets.copy(),
windows: self.windows.copy(),
gcra_flows: self.gcra_flows.copy(),
links: self.links.copy(),
subjects: self.subjects.copy(),
}
}
///|
/// Restores a snapshot atomically at the engine boundary. Callers are expected
/// to validate application-level serialization before restoring it.
pub fn QuotaEngine::restore(
self : QuotaEngine,
snapshot : QuotaSnapshot,
) -> Unit {
self.limits.clear()
self.buckets.clear()
self.windows.clear()
self.gcra_flows.clear()
self.links.clear()
self.subjects.clear()
for item in snapshot.limits {
self.limits.push(item)
}
for item in snapshot.buckets {
self.buckets.push(item)
}
for item in snapshot.windows {
self.windows.push(item)
}
for item in snapshot.gcra_flows {
self.gcra_flows.push(item)
}
for item in snapshot.links {
self.links.push(item)
}
for item in snapshot.subjects {
self.subjects.push(item)
}
}
///|
/// Exports a compact, deterministic operational summary for adapter health
/// checks. Full state remains available through `snapshot`.
pub fn QuotaSnapshot::to_json(self : QuotaSnapshot) -> String {
"{\"limits\":\{self.limits.length()},\"token_buckets\":\{self.buckets.length()},\"windows\":\{self.windows.length()},\"gcra_flows\":\{self.gcra_flows.length()},\"links\":\{self.links.length()},\"subjects\":\{self.subjects.length()}}"
}
///|
pub fn QuotaEngine::add_limit(self : QuotaEngine, limit : LimitSpec) -> Bool {
for current in self.limits {
if current.name == limit.name {
return false
}
}
self.limits.push(limit)
true
}
///|
pub fn QuotaEngine::limit_count(self : QuotaEngine) -> Int {
self.limits.length()
}
///|
pub fn QuotaEngine::bucket_count(self : QuotaEngine) -> Int {
self.buckets.length()
}
///|
pub fn QuotaEngine::window_count(self : QuotaEngine) -> Int {
self.windows.length()
}
///|
pub fn QuotaEngine::gcra_count(self : QuotaEngine) -> Int {
self.gcra_flows.length()
}
///|
pub fn QuotaEngine::link_count(self : QuotaEngine) -> Int {
self.links.length()
}
///|
pub fn QuotaEngine::subject_count(self : QuotaEngine) -> Int {
self.subjects.length()
}
///|
pub fn QuotaEngine::add_link(self : QuotaEngine, link : QuotaLink) -> Bool {
for current in self.links {
if current == link {
return false
}
}
self.links.push(link)
true
}
///|
pub fn SubjectBudget::new(subject : String, weight : Int) -> SubjectBudget {
{ subject, weight: max_int(1, weight), used: 0 }
}
///|
fn find_subject_index(subjects : Array[SubjectBudget], subject : String) -> Int {
for i = 0; i < subjects.length(); i = i + 1 {
if subjects[i].subject == subject {
return i
}
}
-1
}
///|
pub fn QuotaEngine::add_subject(
self : QuotaEngine,
subject : String,
weight : Int,
) -> Bool {
if find_subject_index(self.subjects, subject) >= 0 {
return false
}
self.subjects.push(SubjectBudget::new(subject, weight))
true
}
///|
fn find_limit(limits : Array[LimitSpec], name : String) -> LimitSpec? {
for limit in limits {
if limit.name == name {
return Some(limit)
}
}
None
}
///|
fn find_bucket_index(
buckets : Array[TrackedBucket],
key : QuotaKey,
limit_name : String,
) -> Int {
for i = 0; i < buckets.length(); i = i + 1 {
if buckets[i].key == key && buckets[i].limit_name == limit_name {
return i
}
}
-1
}
///|
fn find_window_index(
windows : Array[TrackedWindow],
key : QuotaKey,
limit_name : String,
) -> Int {
for i = 0; i < windows.length(); i = i + 1 {
if windows[i].key == key && windows[i].limit_name == limit_name {
return i
}
}
-1
}
///|
fn find_gcra_index(
flows : Array[TrackedGcra],
key : QuotaKey,
limit_name : String,
) -> Int {
for i = 0; i < flows.length(); i = i + 1 {
if flows[i].key == key && flows[i].limit_name == limit_name {
return i
}
}
-1
}
///|
fn refill_bucket(
limit : LimitSpec,
state : BucketState,
now_ms : Int64,
) -> BucketState {
if now_ms <= state.last_refill_ms || limit.window_ms <= 0L {
return state
}
let elapsed = now_ms - state.last_refill_ms
let periods = elapsed / limit.window_ms
if periods <= 0L {
return state
}
let refill_amount = periods.to_int() * max_int(1, limit.refill)
{
tokens: min_int(limit.capacity, state.tokens + refill_amount),
last_refill_ms: state.last_refill_ms + periods * limit.window_ms,
}
}
///|
pub fn QuotaEngine::check_token_bucket(
self : QuotaEngine,
key : QuotaKey,
limit_name : String,
now_ms : Int64,
cost : Int,
) -> Decision {
let normalized_cost = positive_cost(cost)
let trace : Array[String] = []
match find_limit(self.limits, limit_name) {
None => {
trace.push("missing limit \{limit_name}")
Decision::deny("missing-limit", normalized_cost, 0, 0L, 0L, trace)
}
Some(limit) => {
let index = find_bucket_index(self.buckets, key, limit_name)
let original = if index >= 0 {
self.buckets[index].state
} else {
BucketState::new(limit.capacity, now_ms)
}
let refilled = refill_bucket(limit, original, now_ms)
trace.push(
"bucket \{key.key()} tokens \{original.tokens}->\{refilled.tokens}",
)
if refilled.tokens >= normalized_cost {
let next = {
tokens: refilled.tokens - normalized_cost,
last_refill_ms: refilled.last_refill_ms,
}
if index >= 0 {
self.buckets[index] = { key, limit_name, state: next }
} else {
self.buckets.push({ key, limit_name, state: next })
}
Decision::allow(
"token-bucket-allow",
normalized_cost,
next.tokens,
limit.window_ms,
trace,
)
} else {
if index >= 0 {
self.buckets[index] = { key, limit_name, state: refilled }
} else {
self.buckets.push({ key, limit_name, state: refilled })
}
let missing = normalized_cost - refilled.tokens
let periods = (missing + max_int(1, limit.refill) - 1) /
max_int(1, limit.refill)
Decision::deny(
"token-bucket-deny",
normalized_cost,
refilled.tokens,
periods.to_int64() * limit.window_ms,
limit.window_ms,
trace,
)
}
}
}
}
///|
fn QuotaEngine::clone_engine(self : QuotaEngine) -> QuotaEngine {
{
limits: self.limits.copy(),
buckets: self.buckets.copy(),
windows: self.windows.copy(),
gcra_flows: self.gcra_flows.copy(),
links: self.links.copy(),
subjects: self.subjects.copy(),
}
}
///|
fn replace_buckets(
target : Array[TrackedBucket],
source : Array[TrackedBucket],
) -> Unit {
target.clear()
for bucket in source {
target.push(bucket)
}
}
///|
/// Atomically evaluates token-bucket charges against one logical instant.
/// No bucket is changed unless every charge is allowed.
pub fn QuotaEngine::check_atomic_token_buckets(
self : QuotaEngine,
charges : Array[QuotaCharge],
now_ms : Int64,
) -> AtomicQuotaReport {
let staging = self.clone_engine()
let decisions : Array[Decision] = []
for i = 0; i < charges.length(); i = i + 1 {
let charge = charges[i]
let decision = staging.check_token_bucket(
charge.key,
charge.limit_name,
now_ms,
charge.cost,
)
decisions.push(decision)
if !decision.allowed {
return { allowed: false, committed: false, failed_index: i, decisions }
}
}
replace_buckets(self.buckets, staging.buckets)
{ allowed: true, committed: true, failed_index: -1, decisions }
}
///|
fn window_start(now_ms : Int64, window_ms : Int64) -> Int64 {
if window_ms <= 0L {
0L
} else {
now_ms / window_ms * window_ms
}
}
///|
pub fn QuotaEngine::check_window(
self : QuotaEngine,
key : QuotaKey,
limit_name : String,
now_ms : Int64,
cost : Int,
) -> Decision {
let normalized_cost = positive_cost(cost)
let trace : Array[String] = []
match find_limit(self.limits, limit_name) {
None => {
trace.push("missing limit \{limit_name}")
Decision::deny("missing-limit", normalized_cost, 0, 0L, 0L, trace)
}
Some(limit) => {
let start = window_start(now_ms, limit.window_ms)
let index = find_window_index(self.windows, key, limit_name)
let original = if index >= 0 &&
self.windows[index].state.window_start_ms == start {
self.windows[index].state
} else {
{ window_start_ms: start, used: 0 }
}
trace.push("window \{key.key()} used \{original.used}/\{limit.capacity}")
let next_used = original.used + normalized_cost
if next_used <= limit.capacity {
let next = { window_start_ms: start, used: next_used }
if index >= 0 {
self.windows[index] = { key, limit_name, state: next }
} else {
self.windows.push({ key, limit_name, state: next })
}
Decision::allow(
"window-allow",
normalized_cost,
limit.capacity - next_used,
start + limit.window_ms - now_ms,
trace,
)
} else {
if index >= 0 {
self.windows[index] = { key, limit_name, state: original }
} else {
self.windows.push({ key, limit_name, state: original })
}
Decision::deny(
"window-deny",
normalized_cost,
max_int(0, limit.capacity - original.used),
start + limit.window_ms - now_ms,
start + limit.window_ms - now_ms,
trace,
)
}
}
}
}
///|
fn emission_interval_ms(limit : LimitSpec) -> Int64 {
if limit.capacity <= 0 || limit.window_ms <= 0L {
0L
} else {
limit.window_ms / limit.capacity.to_int64()
}
}
///|
pub fn QuotaEngine::check_gcra(
self : QuotaEngine,
key : QuotaKey,
limit_name : String,
now_ms : Int64,
cost : Int,
) -> Decision {
let normalized_cost = positive_cost(cost)
let trace : Array[String] = []
match find_limit(self.limits, limit_name) {
None => {
trace.push("missing limit \{limit_name}")
Decision::deny("missing-limit", normalized_cost, 0, 0L, 0L, trace)
}
Some(limit) => {
let interval = emission_interval_ms(limit)
if interval <= 0L {
trace.push("invalid gcra interval")
return Decision::deny(
"invalid-limit", normalized_cost, 0, 0L, 0L, trace,
)
}
let tolerance = max_int(0, limit.burst).to_int64() * interval
let index = find_gcra_index(self.gcra_flows, key, limit_name)
let old_tat = if index >= 0 {
self.gcra_flows[index].state.theoretical_arrival_ms
} else {
now_ms
}
let allowed_at = old_tat - tolerance
trace.push("gcra \{key.key()} tat=\{old_tat} allowed_at=\{allowed_at}")
if now_ms >= allowed_at {
let base = if old_tat > now_ms { old_tat } else { now_ms }
let next_tat = base + normalized_cost.to_int64() * interval
let state = { theoretical_arrival_ms: next_tat }
if index >= 0 {
self.gcra_flows[index] = { key, limit_name, state }
} else {
self.gcra_flows.push({ key, limit_name, state })
}
let remaining = max_int(
0,
limit.burst - ((next_tat - now_ms) / interval).to_int(),
)
Decision::allow(
"gcra-allow",
normalized_cost,
remaining,
next_tat - now_ms,
trace,
)
} else {
Decision::deny(
"gcra-deny",
normalized_cost,
0,
allowed_at - now_ms,
old_tat - now_ms,
trace,
)
}
}
}
}
///|
pub fn QuotaLink::new(
child : QuotaKey,
parent : QuotaKey,
limit_name : String,
ratio : Int,
) -> QuotaLink {
{ child, parent, limit_name, ratio: max_int(1, ratio) }
}
///|
fn append_trace(target : Array[String], source : Array[String]) -> Unit {
for item in source {
target.push(item)
}
}
///|
pub fn QuotaEngine::check_hierarchy(
self : QuotaEngine,
key : QuotaKey,
limit_name : String,
now_ms : Int64,
cost : Int,
) -> Decision {
let normalized_cost = positive_cost(cost)
let trace : Array[String] = []
let charges : Array[QuotaCharge] = [
QuotaCharge::new(key, limit_name, normalized_cost),
]
for link in self.links {
if link.child == key && link.limit_name == limit_name {
let parent_cost = normalized_cost * max_int(1, link.ratio)
charges.push(QuotaCharge::new(link.parent, limit_name, parent_cost))
}
}
let report = self.check_atomic_token_buckets(charges, now_ms)
for decision in report.decisions {
append_trace(trace, decision.trace)
}
if !report.allowed {
let failed = report.decisions[report.failed_index]
let reason = if report.failed_index == 0 {
trace.push("child quota blocked")
"hierarchy-child-deny"
} else {
trace.push("parent quota blocked")
"hierarchy-parent-deny"
}
return Decision::deny(
reason,
normalized_cost,
failed.remaining,
failed.retry_after_ms,
failed.reset_after_ms,
trace,
)
}
let child_decision = report.decisions[0]
Decision::allow(
"hierarchy-allow",
normalized_cost,
child_decision.remaining,
child_decision.reset_after_ms,
trace,
)
}
///|
pub fn QuotaEngine::check_many_token_bucket(
self : QuotaEngine,
limit_name : String,
requests : Array[EvaluationInput],
) -> BatchReport {
let decisions : Array[Decision] = []
let mut allowed = 0
let mut denied = 0
for request in requests {
let decision = self.check_token_bucket(
request.key,
limit_name,
request.now_ms,
request.cost,
)
if decision.allowed {
allowed = allowed + 1
} else {
denied = denied + 1
}
decisions.push(decision)
}
{ total: requests.length(), allowed, denied, decisions }
}
///|
fn fair_score(item : SubjectBudget) -> Int {
item.used * 1000 / max_int(1, item.weight)
}
///|
pub fn QuotaEngine::record_usage(
self : QuotaEngine,
subject : String,
cost : Int,
) -> Bool {
let index = find_subject_index(self.subjects, subject)
if index < 0 {
return false
}
let old = self.subjects[index]
self.subjects[index] = {
subject: old.subject,
weight: old.weight,
used: old.used + positive_cost(cost),
}
true
}
///|
pub fn QuotaEngine::next_fair_subject(self : QuotaEngine) -> String {
if self.subjects.length() == 0 {
return ""
}
let mut best = self.subjects[0]
let mut best_score = fair_score(best)
for item in self.subjects {
let score = fair_score(item)
if score < best_score {
best = item
best_score = score
}
}
best.subject
}
///|
pub fn QuotaEngine::fair_snapshot(self : QuotaEngine) -> Array[FairShareItem] {
let result : Array[FairShareItem] = []
for item in self.subjects {
result.push({
subject: item.subject,
weight: item.weight,
used: item.used,
score: fair_score(item),
})
}
result
}
///|
fn push_unique(items : Array[String], value : String) -> Unit {
for item in items {
if item == value {
return
}
}
items.push(value)
}
///|
pub fn QuotaEngine::stats(self : QuotaEngine) -> QuotaStats {
let subjects : Array[String] = []
for bucket in self.buckets {
push_unique(subjects, bucket.key.subject)
}
for window in self.windows {
push_unique(subjects, window.key.subject)
}
for flow in self.gcra_flows {
push_unique(subjects, flow.key.subject)
}
for subject in self.subjects {
push_unique(subjects, subject.subject)
}
{
limits: self.limits.length(),
token_buckets: self.buckets.length(),
windows: self.windows.length(),
gcra_flows: self.gcra_flows.length(),
subjects: subjects.length(),
}
}
///|
pub fn QuotaEngine::validate(self : QuotaEngine) -> Array[ValidationIssue] {
let issues : Array[ValidationIssue] = []
for limit in self.limits {
if limit.name == "" {
issues.push({
code: "empty-limit-name",
message: "limit name must not be empty",
})
}
if limit.capacity <= 0 {
issues.push({
code: "invalid-capacity",
message: "\{limit.name} capacity must be positive",
})
}
if limit.refill <= 0 {
issues.push({
code: "invalid-refill",
message: "\{limit.name} refill must be positive",
})
}
if limit.window_ms <= 0L {
issues.push({
code: "invalid-window",
message: "\{limit.name} window_ms must be positive",
})
}
}
for link in self.links {
if find_limit(self.limits, link.limit_name) is None {
issues.push({
code: "missing-linked-limit",
message: "\{link.limit_name} used by link \{link.child.key()} is missing",
})
}
if link.child == link.parent {
issues.push({
code: "self-link",
message: "\{link.child.key()} cannot link to itself",
})
}
}
for subject in self.subjects {
if subject.subject == "" {
issues.push({
code: "empty-subject",
message: "fair-share subject must not be empty",
})
}
if subject.weight <= 0 {
issues.push({
code: "invalid-weight",
message: "\{subject.subject} weight must be positive",
})
}
}
issues
}
///|
pub fn QuotaEngine::to_json(self : QuotaEngine) -> String {
let buf = StringBuilder()
buf.write_string("{\"limits\":[")
for i = 0; i < self.limits.length(); i = i + 1 {
if i > 0 {
buf.write_char(',')
}
let limit = self.limits[i]
buf.write_string(
"{\"name\":\"\{limit.name}\",\"capacity\":\{limit.capacity},\"refill\":\{limit.refill},\"window_ms\":\{limit.window_ms},\"burst\":\{limit.burst}}",
)
}
buf.write_string("],\"stats\":")
buf.write_string(self.stats().to_json())
buf.write_string("}")
buf.to_string()
}
///|
pub fn FairShareItem::to_json(self : FairShareItem) -> String {
"{ \"subject\": \"\{self.subject}\", \"weight\": \{self.weight}, \"used\": \{self.used}, \"score\": \{self.score} }"
}