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).
366 lines
9.6 KiB
Ruby
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
|