// ============================================================================
// 拦截器的注册、同名替换与按名移除。
//
// 名字唯一性与查找规则都收在本包内:包外只需要「注册」与「按名移除」两个动作,
// 不需要认识下标(下标在条件注册下会漂,这正是寻址走名字的原因)。
// ============================================================================
///|
/// 按名字找请求处理器在链上的下标;没有这个名字返回 `None`。
///
/// 名字按原样精确匹配——不做 trim、不折叠大小写:拦截器的名字永不上线,
/// 规范化只会带来「两个写法指向同一个键」这种看不见的行为。
fn request_index_of(requests : Array[RequestPair], name : String) -> Int32? {
for i = 0; i < requests.length(); i = i + 1 {
match requests[i].name {
Some(existing) => if existing == name { return Some(i) }
None => ()
}
}
None
}
///|
/// 按名字找响应处理器在链上的下标;没有这个名字返回 `None`。与请求侧同规则,
/// 两侧各扫各的数组(命名空间独立)。
fn response_index_of(responses : Array[ResponsePair], name : String) -> Int32? {
for i = 0; i < responses.length(); i = i + 1 {
match responses[i].name {
Some(existing) => if existing == name { return Some(i) }
None => ()
}
}
None
}
///|
/// 注册一对请求处理器:正常处理器**后注册先跑**(axios 的请求拦截器是 LIFO),
/// 错误处理器与它同向(请求侧的两条链只有一个方向,见 `docs/11-interceptors.md`)。
///
/// `on_rejected` 省略表示「这个拦截器不管错误」,错误继续交给更早注册的错误处理器
/// (与 axios 里不写第二个参数同义)。
///
/// `name` 省略 = 无名注册:链尾追加,事后撤不下来(要能撤就得现在起名)。给了名字
/// 则这条**可寻址**:同名再注册**替换原位**——位置不变,LIFO 顺序照旧,所以同一侧
/// 内不存在同名两条;`remove_request(name)` 按名撤下。名字只在请求侧内唯一,与响应侧
/// 互不相干。
///
/// 两个可选参数调用时都得写标签(可选参数不能按位置传,实测):
/// `.use_request(f, on_rejected=r)` / `.use_request(f, name="auth")`。
pub fn Interceptors::use_request(
self : Interceptors,
on_request : RequestInterceptor,
on_rejected? : RequestErrorHandler,
name? : StringView,
) -> Interceptors {
let on_rejected = match on_rejected {
Some(handler) => handler
None => error => raise error
}
// copy 后再改:数组可能属于别的集合(比如一份共享的基础链),就地改会让它跟着变。
let requests = self.requests.copy()
match name {
None =>
requests.push({ on_fulfilled: on_request, on_rejected, name: None, })
Some(value) => {
let key = value.to_owned()
// 显式标注:两个 pair 的字段名同形,字面量单看无法判断是哪一种。
let pair : RequestPair = {
on_fulfilled: on_request,
on_rejected,
name: Some(key),
}
match request_index_of(requests, key) {
Some(index) => requests[index] = pair
None => requests.push(pair)
}
}
}
{ requests, responses: self.responses, }
}
///|
/// 注册一对响应处理器:**先注册先跑**(axios 的响应拦截器是 FIFO)。
///
/// `on_rejected` 省略表示「这个拦截器不管错误」,错误继续交给更晚注册的错误
/// 处理器(与 axios 里不写第二个参数同义)。
///
/// `name` 与请求侧同规则:省略 = 无名(撤不下来,FIFO 顺序里照常跑),给了名字则
/// 同名替换原位、可用 `remove_response(name)` 撤下;名字只在响应侧内唯一。
pub fn Interceptors::use_response(
self : Interceptors,
on_fulfilled : ResponseInterceptor,
on_rejected? : ResponseErrorHandler,
name? : StringView,
) -> Interceptors {
let on_rejected = match on_rejected {
Some(handler) => handler
None => error => raise error
}
// copy 后再改:数组可能属于别的集合,就地改会让它跟着变。
let responses = self.responses.copy()
match name {
None => responses.push({ on_fulfilled, on_rejected, name: None, })
Some(value) => {
let key = value.to_owned()
// 显式标注:两个 pair 的字段名同形,字面量单看无法判断是哪一种。
let pair : ResponsePair = { on_fulfilled, on_rejected, name: Some(key), }
match response_index_of(responses, key) {
Some(index) => responses[index] = pair
None => responses.push(pair)
}
}
}
{ requests: self.requests, responses, }
}
///|
/// 按名撤下一个请求拦截器,返回**新的集合**;这个名字没注册过时返回 `None`。
///
/// 名字没对上就返回 `None`,不 panic,也不静默当成撤成功:拼错的名字必须立刻可见,
/// 否则「拦截器其实还在跑」是查不出来的那类 bug。`None` 时一个元素都不动,
/// 原集合可以照用。
///
/// 对应 axios 的 `eject(id)`,差别在寻址方式:axios 用 `use` 返回的数字 id,这里用注册
/// 时给的名字——下标在条件注册下会漂,名字不会。无名注册的那几条撤不下来,要撤就得在
/// 注册时起名;撤下只影响这份值,已经交给 `Client::new` 的实例不受影响。
pub fn Interceptors::remove_request(
self : Interceptors,
name : StringView,
) -> Interceptors? {
let key = name.to_owned()
match request_index_of(self.requests, key) {
None => None
Some(index) => {
let requests = self.requests.copy()
ignore(requests.remove(index))
Some({ requests, responses: self.responses, })
}
}
}
///|
/// 按名撤下一个响应拦截器,返回**新的集合**;这个名字没注册过时返回 `None`。
/// 语义与 `remove_request` 相同,只是落在响应侧(FIFO 顺序里剩下的相对位置不变)。
pub fn Interceptors::remove_response(
self : Interceptors,
name : StringView,
) -> Interceptors? {
let key = name.to_owned()
match response_index_of(self.responses, key) {
None => None
Some(index) => {
let responses = self.responses.copy()
ignore(responses.remove(index))
Some({ requests: self.requests, responses, })
}
}
}