/**
* Message.ets
*
* Protobuf 消息抽象基类 - SwiftProtobuf 架构对齐
*
* 设计理念(基于 SwiftProtobuf):
* 1. ⭐ Visitor 模式为核心 - traverse() 是最重要的方法
* 2. 实例方法为主,API 自然流畅
* 3. 支持消息合并(Protobuf 正确语义)
* 4. 递归深度保护(安全性)
* 5. 保留未知字段(兼容性)
*
* 符合 ArkTS 2025 规范:
* - 不使用 any 类型
* - 对象布局固定,不动态添加属性
* - 支持抽象类和泛型
*
* Version: 1.0.0
* ArkTS 2025 兼容
*/
import { Visitor } from './Visitor'
import { UnknownFields } from './UnknownFields'
import { Reader } from './Reader'
import { BinaryEncodingVisitor } from './BinaryEncodingVisitor'
import { JsonEncodingVisitor } from './JsonEncodingVisitor'
/**
* 所有 Protobuf 消息的抽象基类
*
* 所有生成的消息类都必须继承此类,并实现:
* 1. traverse(visitor: Visitor): void - 遍历所有字段
* 2. decodeFrom(reader: Reader, length?: number): void - 从 Reader 解码
*
* 使用示例:
* ```typescript
* const person = Person.create({ name: 'Alice', age: 30 })
* const binary = person.toBinary()
* const json = person.toJson()
* const cloned = person.clone()
* ```
*/
export abstract class Message {
/**
* 消息类型名称(如 "google.protobuf.Timestamp")
*
* 子类必须实现此属性
*/
abstract readonly $typeName: string
/**
* 未知字段存储(用于前后兼容)
*
* 当解码消息时遇到未知的字段编号,会将原始数据保存在此。
* 重新编码时,这些数据会被完整保留。
*/
unknownFields: UnknownFields = new UnknownFields()
// ========== 抽象方法(子类必须实现) ==========
/**
* ⭐ 核心方法:遍历所有字段(Visitor 模式)
*
* SwiftProtobuf 设计:
* "Conceptually, serializers create visitor objects that are
* then passed recursively to every message and field via generated
* traverse methods."
*
* 这是代码生成器需要生成的主要方法!
* 其他方法(toBinary/toJson等)都是基于这个方法实现的。
*
* 实现示例:
* ```typescript
* traverse(v: Visitor): void {
* if (this.name !== '') v.visitString(this.name, 1)
* if (this.age !== 0) v.visitInt32(this.age, 2)
* if (this.address !== null) v.visitMessage(this.address, 3)
* }
* ```
*
* @param visitor 字段访问者对象
*/
abstract traverse(visitor: Visitor): void
/**
* 从 Reader 解码(子类实现)
*
* @internal 子类实现,外部不直接调用
*
* @param reader 二进制读取器
* @param length 可选的长度限制(用于嵌套消息)
*/
abstract decodeFrom(reader: Reader, length?: number): void
// ========== 序列化方法(使用 Visitor) ==========
/**
* 序列化为二进制(Protobuf wire format)
*
* 使用 BinaryEncodingVisitor 遍历所有字段并编码
*
* @param partial 是否允许部分序列化(跳过 required 字段检查)
* @returns 二进制数据
* @throws 如果 partial=false 且 required 字段未设置
*
* 使用示例:
* ```typescript
* const binary = person.toBinary()
* const partialBinary = person.toBinary(true) // 跳过验证
* ```
*/
toBinary(partial: boolean = false): Uint8Array {
// 验证消息(如果不是 partial 模式)
if (!partial) {
const error = this.validate()
if (error !== null) {
throw new Error(`Message validation failed: ${error}`)
}
}
// ⭐ 使用 BinaryEncodingVisitor 编码
const visitor = new BinaryEncodingVisitor()
this.traverse(visitor)
return visitor.finish()
}
/**
* 从二进制反序列化(修改当前实例)
*
* ⚠️ 注意:这是合并操作,不会清空现有字段
* - 标量字段:覆盖
* - repeated 字段:追加
* - 嵌套消息:递归合并
*
* @param data 二进制数据
* @param partial 是否允许部分解码(跳过 required 字段检查)
* @returns this(支持链式调用)
* @throws 如果 partial=false 且 required 字段未设置
*
* 使用示例:
* ```typescript
* const person = new Person()
* person.fromBinary(binary)
* ```
*/
fromBinary(data: Uint8Array, partial: boolean = false): Message {
const reader = new Reader(data)
this.decodeFrom(reader)
// 验证消息(如果不是 partial 模式)
if (!partial) {
const error = this.validate()
if (error !== null) {
throw new Error(`Message validation failed after decode: ${error}`)
}
}
return this
}
/**
* 转换为 JSON 对象(Protobuf JSON mapping)
*
* 使用 JsonEncodingVisitor 遍历所有字段并转换
*
* ArkTS 注意:返回类型使用 Record<string, Object>
* 而不是 Record<string, any>
*
* @returns JSON 对象
*
* 使用示例:
* ```typescript
* const json = person.toJson()
* console.log(JSON.stringify(json))
* ```
*/
toJson(): Record<string, Object> {
// ⭐ 使用 JsonEncodingVisitor 编码
const visitor = new JsonEncodingVisitor()
this.traverse(visitor)
return visitor.finish()
}
/**
* 从 JSON 对象反序列化(修改当前实例)
*
* ⚠️ 设计说明:
* 本方法故意未实现,这是一个**架构决策**而非疏漏。
* 生成的消息类提供**静态方法** `static fromJson(j: MessageJSON): Message`
* 而非实例方法,以提供更好的类型安全。
*
* 推荐使用方式:
* ```typescript
* // ✅ 使用静态方法(类型安全)
* const person = Person.fromJson(json)
*
* // ❌ 不要使用实例方法(会抛出异常)
* const person = new Person()
* person.fromJson(json) // Error!
* ```
*
* 为什么选择静态方法?
* 1. 类型安全:返回具体类型(Person),而非基类(Message)
* 2. 符合工厂模式:静态方法作为构造器更自然
* 3. 避免先创建实例:不需要 `new Person()` 再填充数据
* 4. ArkTS 兼容:不依赖多态或复杂类型推导
*
* 详见:ARCHITECTURE.md > 关键设计决策 > 1
*
* ArkTS 注意:参数类型使用 Record<string, Object>
*
* @param json JSON 对象
* @returns this(支持链式调用)
*/
fromJson(json: Record<string, Object>): Message {
throw new Error('fromJson() not implemented for ' + this.$typeName +
'. Use static method: ' + this.$typeName + '.fromJson() instead.')
}
// ========== 实用方法 ==========
/**
* 深拷贝消息
*
* 通过序列化和反序列化实现深拷贝
*
* @returns 新的消息对象(深拷贝)
*
* 使用示例:
* ```typescript
* const personCopy = person.clone()
* ```
*/
abstract clone(): Message
/**
* 从另一个消息合并字段(覆盖当前值)
*
* 合并语义(Protobuf 标准):
* - 标量字段:other 覆盖 this
* - repeated 字段:追加(不是替换!)
* - 嵌套消息:递归合并
*
* @param other 要合并的消息
* @returns this(支持链式调用)
*
* 使用示例:
* ```typescript
* const base = Person.create({ name: 'Alice', age: 30 })
* const update = Person.create({ age: 31, emails: ['new@example.com'] })
* base.mergeFrom(update) // age 覆盖,emails 追加
* ```
*/
mergeFrom(other: Message): Message {
try {
return this.fromBinary(other.toBinary(true)) // partial=true
} catch (e) {
throw new Error(`Merge failed: ${e}`)
}
}
/**
* 从二进制数据合并
*
* @param data 二进制数据
* @returns this(支持链式调用)
*/
mergeFromBinary(data: Uint8Array): Message {
try {
return this.fromBinary(data, true) // partial=true
} catch (e) {
throw new Error(`Merge from binary failed: ${e}`)
}
}
/**
* 验证消息(返回错误信息,或 null 表示有效)
*
* 子类可覆盖以实现自定义验证逻辑:
* - Proto2 required 字段检查
* - 业务逻辑验证
*
* @returns 错误信息,或 null 表示有效
*
* 使用示例:
* ```typescript
* const error = person.validate()
* if (error !== null) {
* console.error('Validation failed:', error)
* }
* ```
*/
validate(): string | null {
// 默认实现:无验证
// 子类可覆盖此方法
return null
}
/**
* 判断消息是否有效
*
* @returns true 如果消息有效
*/
isValid(): boolean {
return this.validate() === null
}
/**
* 检查所有 required 字段是否已设置(递归检查)
*
* 用于 proto2 语法
*
* @returns true 如果所有 required 字段都已设置
*/
isInitialized(): boolean {
return this.isValid()
}
/**
* 清空所有字段为默认值
*
* @returns this(支持链式调用)
*
* 注意:子类应覆盖此方法以清空所有字段
*/
clear(): Message {
// 默认实现:只清空未知字段
this.unknownFields.clear()
return this
}
/**
* 判断消息是否为空(所有字段都是默认值)
*
* @returns true 如果所有字段都是默认值
*
* 注意:子类应覆盖此方法以检查所有字段
*/
isEmpty(): boolean {
// 默认实现:只检查未知字段
return this.unknownFields.isEmpty()
}
/**
* 判断两个消息是否相等(二进制比较)
*
* @param other 要比较的消息
* @returns true 如果内容完全相同
*
* 使用示例:
* ```typescript
* if (person1.equals(person2)) {
* console.log('Messages are equal')
* }
* ```
*/
equals(other: Message): boolean {
// 类型检查
if (this.$typeName !== other.$typeName) {
return false
}
try {
// 二进制比较
const a = this.toBinary(true)
const b = other.toBinary(true)
if (a.length !== b.length) {
return false
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false
}
}
return true
} catch (e) {
return false
}
}
/**
* 转换为可读字符串(用于调试)
*
* @returns JSON 格式的字符串
*/
toString(): string {
try {
return JSON.stringify(this.toJson(), null, 2)
} catch (e) {
return `[${this.$typeName}]`
}
}
}
/**
* 消息构造器接口,用于泛型约束
*
* ArkTS 注意:使用 Object 而不是 any
*
* 使用示例:
* ```typescript
* function deserialize<T extends Message>(
* ctor: MessageConstructor<T>,
* data: Uint8Array
* ): T {
* return ctor.fromBinary(data)
* }
* ```
*/
export interface MessageConstructor<T extends Message> {
readonly typeName: string
// 静态工厂方法 - 不使用 Partial<T>(ArkTS 不支持)
create(init?: Record<string, Object>): T
// 从二进制创建(静态方法)
fromBinary(data: Uint8Array): T
// 从 JSON 创建(静态方法)
fromJson(json: Record<string, Object>): T
}