import Foundation
/// How the gate finds the domain a gated mutation touches.
enum GateSource: Sendable {
/// A field of the JSON body (`domain`, `hostname`, `zone`).
case bodyField(String)
/// A path parameter holding the domain.
case pathParam(String)
/// A path parameter holding a registration id, resolved through the index.
case registrationId(pathParam: String)
/// One read with the same path parameters, then a field of its answer.
case resolve(GateResolve)
}
struct GateResolve: Sendable {
let read: IrdOperation
let extract: Extract
enum Extract: Sendable {
/// A domain at this key path of the read's body.
case domain(path: [String])
/// A registration id at this key path.
case registrationId(path: [String])
/// The row of `array` whose `id` equals the `idParam` path parameter; then `field`.
case domainInArray(array: String, idParam: String, field: String)
}
}
/// Every gated mutation, keyed by `"METHOD /path/{template}"`. Ported from the
/// sibling TypeScript client; reads and everything not touching a domain are absent.
enum GatedOperations {
static let table: [String: GateSource] = {
var t: [String: GateSource] = [:]
for k in ["POST /domains/registrations", "POST /domains/transfers-in", "POST /domains/trades-in",
"POST /domains/adoptions", "POST /dns/claims", "POST /ipfs/domains"] {
t[k] = .bodyField("domain")
}
for k in ["POST /tunnels", "POST /p2p/apps/{id}/domains"] { t[k] = .bodyField("hostname") }
t["POST /vps/instances/{id}/domains"] = .bodyField("zone")
for k in ["PUT /dns/domains/{domain}/records", "DELETE /dns/domains/{domain}/records"] { t[k] = .pathParam("domain") }
for k in ["POST /domains/registrations/{id}/renew", "PUT /domains/registrations/{id}/auto-renew",
"PUT /domains/registrations/{id}/sensitive", "PUT /domains/registrations/{id}/nameservers",
"POST /domains/registrations/{id}/hosts", "PUT /domains/registrations/{id}/hosts/{host}",
"DELETE /domains/registrations/{id}/hosts/{host}", "POST /domains/registrations/{id}/dsdata",
"DELETE /domains/registrations/{id}/dsdata", "POST /domains/registrations/{id}/transfer-out",
"PUT /domains/registrations/{id}/registrant", "POST /domains/registrations/{id}/registrant-email/start",
"POST /domains/registrations/{id}/registrant-email/confirm", "POST /domains/registrations/{id}/registrant-email/adopt",
"POST /domains/registrations/{id}/trade", "POST /domains/registrations/{id}/dns",
"DELETE /domains/registrations/{id}/dns", "POST /domains/registrations/{id}/dns/dnssec",
"DELETE /domains/registrations/{id}/dns/dnssec", "POST /domains/registrations/{id}/dns/nameservers",
"POST /domains/registrations/{id}/grants", "DELETE /domains/registrations/{id}/grants/{grant-id}"] {
t[k] = .registrationId(pathParam: "id")
}
func read(_ path: String, _ module: String, _ name: String) -> IrdOperation {
IrdOperation(method: .get, pathTemplate: path, module: module, name: name, security: .keyOrJwt)
}
t["POST /domains/actions/{id}/cancel"] = .resolve(GateResolve(
read: read("/domains/actions/{id}", "registrations", "getAction"), extract: .registrationId(path: ["action", "registration_id"])))
let claim = GateResolve(read: read("/dns/claims/{id}", "dns", "getClaim"), extract: .domain(path: ["claim", "domain"]))
for k in ["DELETE /dns/claims/{id}", "PUT /dns/claims/{id}/seed", "DELETE /dns/claims/{id}/seed"] { t[k] = .resolve(claim) }
let tunnel = GateResolve(read: read("/tunnels/{id}", "tunnels", "get"), extract: .domain(path: ["tunnel", "hostname"]))
for k in ["DELETE /tunnels/{id}", "PATCH /tunnels/{id}"] { t[k] = .resolve(tunnel) }
let gateway = GateResolve(read: read("/ipfs/domains/{id}", "ipfs", "getDomain"), extract: .domain(path: ["domain", "domain"]))
for k in ["POST /ipfs/domains/{id}/pins", "DELETE /ipfs/domains/{id}"] { t[k] = .resolve(gateway) }
t["DELETE /p2p/apps/{id}/domains/{domain-id}"] = .resolve(GateResolve(
read: read("/p2p/apps/{id}/domains", "p2p", "listAppDomains"),
extract: .domainInArray(array: "domains", idParam: "domain-id", field: "hostname")))
t["DELETE /vps/instances/{id}/domains/{connection-id}"] = .resolve(GateResolve(
read: read("/vps/instances/{id}/domains", "vps", "listInstanceDomains"),
extract: .domainInArray(array: "connections", idParam: "connection-id", field: "zone_origin")))
return t
}()
}
extension IrdClient {
/// Turns a gate source into the target to check, issuing the resolve read when needed.
func gateTarget(for source: GateSource, params: [String: String], body: JSONValue?) async throws -> GateTarget {
switch source {
case .bodyField(let field):
return .domain(body?[field]?.stringValue)
case .pathParam(let name):
return .domain(params[name])
case .registrationId(let name):
return .registrationId(params[name])
case .resolve(let r):
let json = try await perform(r.read, params: params, query: [], body: nil, gated: false).json
switch r.extract {
case .domain(let path):
return .domain(json.at(path)?.stringValue)
case .registrationId(let path):
return .registrationId(json.at(path)?.stringValue)
case .domainInArray(let array, let idParam, let field):
let row = json[array]?.arrayValue?.first { $0["id"]?.stringValue == params[idParam] }
return .domain(row?[field]?.stringValue)
}
}
}
}