synor/sdk/ruby/lib/synor_economics/client.rb
Gulshan Yadav 6607223c9e feat(sdk): complete Phase 4 SDKs for all remaining languages
Add Economics, Governance, and Mining SDKs for:
- Java: Full SDK with CompletableFuture async operations
- Kotlin: Coroutine-based SDK with suspend functions
- Swift: Modern Swift SDK with async/await
- Flutter/Dart: Complete Dart SDK with Future-based API
- C: Header files and implementations with opaque handles
- C++: Modern C++17 with std::future and PIMPL pattern
- C#: Records, async/await Tasks, and IDisposable
- Ruby: Struct-based types with Faraday HTTP client

Also includes minor Dart lint fixes (const exceptions).
2026-01-28 08:33:20 +05:30

366 lines
9.6 KiB
Ruby

# frozen_string_literal: true
require "faraday"
require "json"
require "uri"
module SynorEconomics
# Synor Economics SDK client for Ruby.
# Pricing, billing, staking, and discount management.
class Client
attr_reader :closed
def initialize(config)
@config = config
@closed = false
@conn = Faraday.new(url: config.endpoint) do |f|
f.request :json
f.response :json
f.options.timeout = config.timeout
f.headers["Authorization"] = "Bearer #{config.api_key}"
f.headers["Content-Type"] = "application/json"
f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
end
end
# ==================== Pricing Operations ====================
def get_pricing(service: nil)
path = service ? "/pricing?service=#{service}" : "/pricing"
response = get(path)
(response["pricing"] || []).map { |p| parse_service_pricing(p) }
end
def get_price(service:, usage:)
body = { service: service, usage: usage_to_hash(usage) }
response = post("/pricing/calculate", body)
parse_price_result(response)
end
def estimate_cost(plan:)
body = { plan: plan.map { |p| usage_plan_item_to_hash(p) } }
response = post("/pricing/estimate", body)
parse_cost_estimate(response)
end
# ==================== Billing Operations ====================
def get_usage(period: nil)
path = period ? "/billing/usage?period=#{period}" : "/billing/usage"
response = get(path)
parse_usage(response)
end
def get_invoices
response = get("/billing/invoices")
(response["invoices"] || []).map { |i| parse_invoice(i) }
end
def get_invoice(invoice_id)
response = get("/billing/invoices/#{encode(invoice_id)}")
parse_invoice(response)
end
def get_balance
response = get("/billing/balance")
parse_account_balance(response)
end
# ==================== Staking Operations ====================
def stake(amount:, duration_days: nil)
body = { amount: amount }
body[:duration_days] = duration_days if duration_days
response = post("/staking/stake", body)
parse_stake_receipt(response)
end
def unstake(stake_id)
response = post("/staking/#{encode(stake_id)}/unstake", {})
parse_unstake_receipt(response)
end
def get_stakes
response = get("/staking")
(response["stakes"] || []).map { |s| parse_stake(s) }
end
def get_stake(stake_id)
response = get("/staking/#{encode(stake_id)}")
parse_stake(response)
end
def get_staking_rewards
response = get("/staking/rewards")
parse_staking_rewards(response)
end
def claim_rewards
response = post("/staking/rewards/claim", {})
parse_stake_receipt(response)
end
# ==================== Discount Operations ====================
def apply_discount(code)
response = post("/discounts/apply", { code: code })
parse_discount_result(response)
end
def remove_discount(code)
delete("/discounts/#{encode(code)}")
end
def get_discounts
response = get("/discounts")
(response["discounts"] || []).map { |d| parse_discount(d) }
end
# ==================== Lifecycle ====================
def health_check
response = get("/health")
response["status"] == "healthy"
rescue StandardError
false
end
def close
@closed = true
@conn.close if @conn.respond_to?(:close)
end
private
def get(path, params = {})
execute { @conn.get(path, params).body }
end
def post(path, body)
execute { @conn.post(path, body).body }
end
def delete(path)
execute { @conn.delete(path).body }
end
def execute
raise ClientClosedError, "Client has been closed" if @closed
last_error = nil
@config.retries.times do |attempt|
begin
response = yield
check_error(response) if response.is_a?(Hash)
return response
rescue StandardError => e
last_error = e
sleep(2**attempt) if attempt < @config.retries - 1
end
end
raise last_error
end
def check_error(response)
return unless response["error"] || (response["code"] && response["message"])
message = response["message"] || response["error"] || "Unknown error"
code = response["code"]
status = response["status_code"] || 0
raise HttpError.new(message, status_code: status, code: code)
end
def encode(str)
URI.encode_www_form_component(str)
end
# Parsing methods
def parse_service_pricing(data)
ServicePricing.new(
service: data["service"],
unit: data["unit"],
price_per_unit: data["price_per_unit"],
currency: data["currency"],
minimum: data["minimum"],
maximum: data["maximum"]
)
end
def parse_price_result(data)
PriceResult.new(
service: data["service"],
amount: data["amount"],
currency: data["currency"],
usage: data["usage"] ? parse_usage_metrics(data["usage"]) : nil
)
end
def parse_usage_metrics(data)
UsageMetrics.new(
service: data["service"],
compute_units: data["compute_units"],
storage_bytes: data["storage_bytes"],
bandwidth_bytes: data["bandwidth_bytes"],
duration_seconds: data["duration_seconds"],
requests: data["requests"]
)
end
def parse_cost_estimate(data)
CostEstimate.new(
total: data["total"],
currency: data["currency"],
breakdown: (data["breakdown"] || []).map { |b| CostItem.new(service: b["service"], amount: b["amount"]) },
discount_applied: data["discount_applied"],
period: data["period"]
)
end
def parse_usage(data)
Usage.new(
period: data["period"],
start_date: data["start_date"],
end_date: data["end_date"],
items: (data["items"] || []).map { |i| parse_usage_item(i) },
total_cost: data["total_cost"],
currency: data["currency"]
)
end
def parse_usage_item(data)
UsageItem.new(
service: data["service"],
compute_units: data["compute_units"],
storage_bytes: data["storage_bytes"],
bandwidth_bytes: data["bandwidth_bytes"],
requests: data["requests"],
cost: data["cost"]
)
end
def parse_invoice(data)
Invoice.new(
id: data["id"],
date: data["date"],
due_date: data["due_date"],
status: data["status"],
lines: (data["lines"] || []).map { |l| parse_invoice_line(l) },
subtotal: data["subtotal"],
discount: data["discount"],
tax: data["tax"],
total: data["total"],
currency: data["currency"],
pdf_url: data["pdf_url"]
)
end
def parse_invoice_line(data)
InvoiceLine.new(
service: data["service"],
description: data["description"],
quantity: data["quantity"],
unit_price: data["unit_price"],
amount: data["amount"]
)
end
def parse_account_balance(data)
AccountBalance.new(
available: data["available"],
pending: data["pending"],
staked: data["staked"],
total: data["total"],
currency: data["currency"]
)
end
def parse_stake(data)
Stake.new(
id: data["id"],
amount: data["amount"],
staked_at: data["staked_at"],
unlock_at: data["unlock_at"],
status: data["status"],
rewards_earned: data["rewards_earned"],
apy: data["apy"]
)
end
def parse_stake_receipt(data)
StakeReceipt.new(
id: data["id"],
amount: data["amount"],
tx_hash: data["tx_hash"],
staked_at: data["staked_at"],
unlock_at: data["unlock_at"],
apy: data["apy"]
)
end
def parse_unstake_receipt(data)
UnstakeReceipt.new(
id: data["id"],
amount: data["amount"],
tx_hash: data["tx_hash"],
unstaked_at: data["unstaked_at"],
available_at: data["available_at"]
)
end
def parse_staking_rewards(data)
StakingRewards.new(
pending: data["pending"],
claimed: data["claimed"],
total: data["total"],
current_apy: data["current_apy"],
last_claim: data["last_claim"],
next_distribution: data["next_distribution"]
)
end
def parse_discount(data)
Discount.new(
code: data["code"],
type: data["type"],
value: data["value"],
description: data["description"],
applicable_services: data["applicable_services"],
valid_from: data["valid_from"],
valid_until: data["valid_until"],
max_uses: data["max_uses"],
current_uses: data["current_uses"],
active: data["active"]
)
end
def parse_discount_result(data)
DiscountResult.new(
discount: parse_discount(data["discount"]),
savings_estimate: data["savings_estimate"],
applied_at: data["applied_at"]
)
end
# Conversion methods
def usage_to_hash(usage)
{
service: usage.service,
compute_units: usage.compute_units,
storage_bytes: usage.storage_bytes,
bandwidth_bytes: usage.bandwidth_bytes,
duration_seconds: usage.duration_seconds,
requests: usage.requests
}
end
def usage_plan_item_to_hash(item)
{
service: item.service,
projected_usage: usage_to_hash(item.projected_usage),
period: item.period
}
end
end
end