// 协议相关的公共小类型:Depth 头、条件请求、OPTIONS 能力探测结果、
// 属性级读取结果与部分失败条目。与具体操作实现解耦,单独成文件以遵守
// 单文件行数上限(AGENTS.md RL-04)。
///|
/// `Depth` 头取值(RFC 4918 §10.2):PROPFIND 的作用深度与 COPY 的
/// 是否浅拷贝都由它表达。
pub(all) enum Depth {
/// 只作用于目标资源自身
Zero
/// 作用于目标资源及其直接子项
One
/// 递归作用于整棵子树
Infinity
}
///|
/// 渲染为 HTTP 头文本:`"0"` / `"1"` / `"infinity"`。
pub fn Depth::to_header(self : Depth) -> String {
match self {
Zero => "0"
One => "1"
Infinity => "infinity"
}
}
///|
/// `If` 头里一条条件的作用对象(RFC 4918 §10.4.2):
/// 状态令牌(`<令牌>`,锁令牌属于此类)或 ETag(`[etag]`)。
pub(all) enum IfConditionKind {
/// 状态令牌:``,取值不含外层 `<>`
StateToken
/// 实体标签:`["abc"]`,取值是含引号的 ETag 原文
EntityTag
}
///|
/// `If` 头里的一条条件,可带 `Not` 取反。
pub(all) struct IfCondition {
/// 条件的作用对象种类
kind : IfConditionKind
/// 取值:状态令牌不含外层 `<>`;ETag 含引号(或 `*`),原样透传
value : String
/// 是否带 `Not` 前缀(RFC 4918 §10.4.3:只作用于紧随其后的那一条条件)
negated : Bool
}
///|
/// 构造状态令牌条件;`value` 可带或不带外层 `<>`,渲染时统一只包一层,
/// 归一化后为空串视为无效(渲染成空列表的请求会被本地拦下)。
pub fn IfCondition::token(value : String, negated? : Bool) -> IfCondition {
{ kind: StateToken, value, negated: negated.unwrap_or(false), }
}
///|
/// 构造 ETag 条件;`value` 是含引号的 ETag 原文(如 `"\"abc\""`)。
pub fn IfCondition::etag(value : String, negated? : Bool) -> IfCondition {
{ kind: EntityTag, value, negated: negated.unwrap_or(false), }
}
///|
/// `If` 头里的一个列表:`(<条件> …)`,可选带资源标签(` (<条件>)`)。
/// 同一列表内的条件是合取;多个列表之间是析取(RFC 4918 §10.4.3)。
pub(all) struct IfList {
/// 资源标签(Simple-ref,不含外层 `<>`);`None` = 无标签列表,作用于目标 URL
resource_tag : String?
/// 列表内的条件,至少要有一条
conditions : Array[IfCondition]
}
///|
/// 按需构造 `If` 列表:`resource_tag` 省略时渲染成作用于目标 URL 的
/// 无标签列表。
pub fn IfList::new(
conditions : Array[IfCondition],
resource_tag? : String,
) -> IfList {
{ resource_tag, conditions, }
}
///|
/// 条件请求:`If-Match` / `If-None-Match` / `If`,前者取值是服务端给出的
/// ETag(含引号,原样透传)或通配符 `*`,用于乐观并发控制;
/// `lock_token` 是要随请求提交的锁令牌,渲染成 `If: (<令牌>)`
/// (RFC 4918 §10.4.1 的无标签列表形态);`if_lists` 是显式的 `If` 列表,
/// 覆盖多令牌析取、`Not` / ETag 形态与带标签(` (<令牌>)`)的条件。
pub(all) struct Conditions {
if_match : String?
if_none_match : String?
lock_token : String?
if_lists : Array[IfList]
}
///|
/// 按需构造条件:字段都可省略,省略即不发送对应请求头。
/// `lock_token` 可带或不带外层 `<>`,由 `normalize_lock_token` 统一形状;
/// 归一化后为空串时视为未设置(不会发出 `If: ()` 这种坏头)。
/// `if_lists` 省略时不追加显式 `If` 列表。
pub fn Conditions::new(
if_match? : String,
if_none_match? : String,
lock_token? : String,
if_lists? : Array[IfList],
) -> Conditions {
{ if_match, if_none_match, lock_token, if_lists: if_lists.unwrap_or([]), }
}
///|
/// 换一个锁令牌(其余字段不变):按 423 自动重试时用它把覆盖锁的令牌
/// 补进 `If` 头。
pub fn Conditions::with_lock_token(
self : Conditions,
token : String,
) -> Conditions {
{ ..self, lock_token: Some(token), }
}
///|
/// OPTIONS 能力探测结果(`DAV` / `Allow` / `Server` 头)。
pub(all) struct ServerCapabilities {
/// `DAV` 头解析出的合规等级(1 / 2 / 3);
/// 没有该头时按 RFC 4918 §10.1 视为 `[1]`
dav_class : Array[Int32]
/// `Allow` 头解析出的方法名(ASCII 大写,保持服务端给出的顺序)
allow : Array[String]
/// `Server` 头原文
server : String?
}
///|
/// 服务端是否声明支持某方法(比较 `Allow` 列表,ASCII 大小写不敏感)。
pub fn ServerCapabilities::supports(
self : ServerCapabilities,
http_method : String,
) -> Bool {
let target = ascii_upper(http_method)
for item in self.allow {
if ascii_upper(item) == target {
return true
}
}
false
}
///|
/// 是否具备 Class 2 作品锁能力(`DAV` 等级含 2,或 `Allow` 含 `LOCK`)。
pub fn ServerCapabilities::has_lock(self : ServerCapabilities) -> Bool {
self.dav_class.contains(2) || self.supports("LOCK")
}
///|
/// 属性级读取结果:`status` 是该属性所在 `propstat` 的状态码,
/// 404 表示「服务端没有这个属性」,与「属性值为空串」区分开。
pub(all) struct PropResult {
name : String
value : String
status : Int32
}
///|
/// 部分失败条目:207 Multi-Status 中非 2xx 的那个资源。
pub(all) struct ResourceFailure {
/// 服务端返回的原始 href
href : String
/// 该资源的 HTTP 状态码
status : Int32
/// 状态码的中文注解
message : String
}
///|
/// ASCII 大写化:HTTP 方法名与 `DAV` 等级都是 ASCII 文本,够用且不引入依赖。
pub fn ascii_upper(text : String) -> String {
let buf = StringBuilder()
for ch in text {
let code = ch.to_int()
if code >= 0x61 && code <= 0x7A {
buf.write_char((code - 0x20).unsafe_to_char())
} else {
buf.write_char(ch)
}
}
buf.to_string()
}