///|
/// SDK instances are independent. They do not consult process-global configuration.
pub struct Sdk {
priv bindings : Map[String, Provider]
priv known_domains : Map[String, Bool]
priv watched : Array[(Provider, Int32)]
priv bus : EventBus
priv api_bus : EventBus
priv hooks : Array[Hook]
priv mut context : EvaluationContext
priv diagnostics : Array[SdkError]
priv mut mutating : Bool
priv mut closed : Bool
}
///|
pub fn Sdk::new() -> Sdk {
let sdk : Sdk = {
bindings: Map([]),
known_domains: Map([]),
watched: [],
bus: EventBus::new(),
api_bus: EventBus::new(),
hooks: [],
context: EvaluationContext::empty(),
diagnostics: [],
mutating: false,
closed: false,
}
let noop = Provider::new("No-op", (_, _, default, _) => {
Resolution::success(default, reason="DEFAULT")
})
noop.bind_domain("") |> ignore
sdk.bindings[""] = noop
sdk.known_domains[""] = true
sdk.watch(noop)
noop.initialize(sdk.context) |> ignore
sdk
}
///|
pub fn Sdk::is_closed(self : Sdk) -> Bool {
self.closed
}
///|
pub fn Sdk::provider(self : Sdk, domain~ : String = "") -> Provider {
match self.bindings.get(domain) {
Some(provider) => provider
None => self.bindings[""]
}
}
///|
pub fn Sdk::provider_status(
self : Sdk,
domain~ : String = "",
) -> ProviderStatus {
self.provider(domain~).status()
}
///|
pub fn Sdk::registered_domains(self : Sdk) -> Array[String] {
let result = self.bindings.keys().to_array()
result.sort()
result
}
///|
fn Sdk::watch(self : Sdk, provider : Provider) -> Unit {
for pair in self.watched {
if physical_equal(pair.0, provider) {
return
}
}
let id = provider
.events()
.on_internal(event => {
// Freeze domain ownership before observers can mutate bindings. API
// callbacks may replace the provider, but cannot erase this event's route.
let domains = self.known_domains.keys().to_array()
domains.sort()
let recipients : Array[String] = []
for domain in domains {
if physical_equal(self.provider(domain~), provider) {
recipients.push(domain)
}
}
self.api_bus.emit(event.copy())
for domain in recipients {
self.bus.emit({ ..event.copy(), domain: Some(domain), })
}
Ok(())
})
self.watched.push((provider, id))
}
///|
fn Sdk::is_bound(self : Sdk, provider : Provider) -> Bool {
for _, candidate in self.bindings {
if physical_equal(candidate, provider) {
return true
}
}
false
}
///|
fn Sdk::retire(self : Sdk, provider : Provider) -> Unit {
if self.is_bound(provider) {
return
}
let mut index = 0
while index < self.watched.length() {
let (candidate, id) = self.watched[index]
if physical_equal(candidate, provider) {
provider.events().off(id) |> ignore
self.watched.remove(index) |> ignore
break
}
index += 1
}
match provider.shutdown() {
Ok(_) => ()
Err(error) => self.record_diagnostic(error)
}
}
///|
/// Replacing a domain never shuts down a provider still used by another domain.
/// Failed initialization leaves the new binding's error/not-ready state observable.
pub fn Sdk::set_provider(
self : Sdk,
provider : Provider,
domain~ : String = "",
) -> Result[Unit, SdkError] {
if self.closed || self.mutating {
return Err(
SdkError::new(
General,
"SDK is closed or a lifecycle mutation is in progress",
),
)
}
if provider.status() == Shutdown {
return Err(
SdkError::new(ProviderNotReady, "a shut down provider cannot be rebound"),
)
}
match provider.bind_domain(domain) {
Err(error) => return Err(error)
Ok(_) => ()
}
let previous = self.bindings.get(domain)
if previous is Some(old) && physical_equal(old, provider) {
return Ok(())
}
self.mutating = true
self.bindings[domain] = provider
self.known_domains[domain] = true
self.watch(provider)
let initialized = provider.initialize(self.context.copy())
match previous {
Some(old) => self.retire(old)
None => ()
}
self.mutating = false
initialized
}
///|
pub fn Sdk::clear_domain(self : Sdk, domain : String) -> Result[Unit, SdkError] {
if domain == "" {
return Err(SdkError::new(General, "the default domain cannot be removed"))
}
if self.closed || self.mutating {
return Err(
SdkError::new(
General,
"SDK is closed or a lifecycle mutation is in progress",
),
)
}
self.mutating = true
match self.bindings.get(domain) {
Some(old) => {
self.bindings.remove(domain)
self.retire(old)
}
None => ()
}
self.mutating = false
Ok(())
}
///|
pub fn Sdk::context(self : Sdk) -> EvaluationContext {
self.context.copy()
}
///|
pub fn Sdk::set_context(
self : Sdk,
context : EvaluationContext,
) -> Result[Unit, SdkError] {
if self.closed || self.mutating {
return Err(
SdkError::new(
General,
"SDK is closed or a lifecycle mutation is in progress",
),
)
}
self.mutating = true
let old = self.context.copy()
self.context = context.copy()
let providers = self.watched.copy()
let mut first_error = None
for pair in providers {
match pair.0.on_context_changed(old.copy(), self.context.copy()) {
Ok(_) => ()
Err(error) => {
if first_error == None {
first_error = Some(error)
}
self.record_diagnostic(error)
}
}
}
self.mutating = false
match first_error {
None => Ok(())
Some(error) => Err(error)
}
}
///|
pub fn Sdk::add_hook(self : Sdk, hook : Hook) -> Unit {
self.hooks.push(hook)
}
///|
pub fn Sdk::clear_hooks(self : Sdk) -> Unit {
self.hooks.clear()
}
///|
pub fn Sdk::hooks(self : Sdk) -> Array[Hook] {
self.hooks.copy()
}
///|
pub fn Sdk::events(self : Sdk) -> EventBus {
self.bus
}
///|
fn Sdk::record_diagnostic(self : Sdk, error : SdkError) -> Unit {
if self.diagnostics.length() == 128 {
self.diagnostics.remove(0) |> ignore
}
self.diagnostics.push(error)
}
///|
pub fn Sdk::take_diagnostics(self : Sdk) -> Array[SdkError] {
let result = self.diagnostics.copy()
self.diagnostics.clear()
for error in self.api_bus.take_diagnostics() {
result.push(error)
}
for error in self.bus.take_diagnostics() {
result.push(error)
}
for pair in self.watched {
for error in pair.0.events().take_diagnostics() {
result.push(error)
}
}
result
}
///|
pub fn Sdk::shutdown(self : Sdk) -> Result[Unit, SdkError] {
if self.closed {
return Ok(())
}
if self.mutating {
return Err(SdkError::new(General, "a lifecycle mutation is in progress"))
}
self.closed = true
self.mutating = true
let watched = self.watched.copy()
self.watched.clear()
let mut first_error = None
for pair in watched {
pair.0.events().off(pair.1) |> ignore
match pair.0.shutdown() {
Ok(_) => ()
Err(error) => {
if first_error == None {
first_error = Some(error)
}
self.record_diagnostic(error)
}
}
}
self.hooks.clear()
self.bus.clear()
self.api_bus.clear()
self.context = EvaluationContext::empty()
self.known_domains.clear()
// Keep bindings to terminal providers so existing clients fail closed.
// No resolver can run after the closed flag has been set.
self.mutating = false
match first_error {
None => Ok(())
Some(error) => Err(error)
}
}
///|
/// API-wide observers receive each provider event once, unlike the detailed
/// domain fan-out bus returned by events(). Late state replay uses the default
/// provider; configuration notifications are never replayed.
pub fn Sdk::on(
self : Sdk,
event_type : EventType,
callback : (ProviderEvent) -> Result[Unit, SdkError],
) -> Int32 {
let id = self.api_bus.on(callback, event_type=Some(event_type))
let provider = self.provider()
if !self.closed && event_type.matches_status(provider.status()) {
match callback(ProviderEvent::new(event_type, provider.name())) {
Ok(_) => ()
Err(error) => self.record_diagnostic(error)
}
}
id
}
///|
pub fn Sdk::off(self : Sdk, id : Int32) -> Bool {
self.api_bus.off(id)
}