tommwq.work/aip

AIP-210 Unicode

· [tommwq@126.com]
编号 210
原文链接 https://google.aip.dev/210
状态 批准
创建日期 2018-06-28
更新日期 2018-06-28

API在处理(解释、限制长度、计费)字符串值和字符串编码时,应当保持一致,范围从理解上的歧义(如域“限制为1024个字符”)到账单上的困惑(Datastore中属性的名字和值的计费单元是字符还是字节?)。

一般来说,如果我们以字节为单位限制长度,这是在区别对待非ASCII文本,因为它占用更多空间。另一方面,如果我们谈论“字符”,我们就不清楚它们是Unicode“码点”、特定编码(如UTF-8或UTF-16)的“编码单元”、“字素”还是“字素簇”。

Unicode入门

字符编码是我们经常忽略的领域,下面是一个快速入门:

  • 字符串是一列字节,字节按照某种编码格式表示数字。
  • 我们谈论 字符 时,通常指的是Unicode 码点(code point) ,它们是Unicode规范中的数字(最多21位)。
  • 其他时候可能指的是 字素(grapheme)字素簇(grapheme cluster) ,它们可能有多个数字表示,也可能由多个码点表示。例如 á 可以表示为 U+0061 + U+0301a + 重音组合标记)的组合,或者作为单个码点 U+00E1
  • protocol buffer使用 UTF-8 (Unicode转换格式),这是一种变长度编码方案,每个码点最多使用4个 编码单元(code unit) (8位)。

指南

字符定义

省流 在我们的API中,“字符”指的是“Unicode码点”。

在API文档(例如API参考文档、博客文章、营销文档、计费说明等)中,“字符” 必须 定义为Unicode码点。

长度单位

省流 以“字符”(如上定义)为单位设置长度限制。

API注释中定义的所有字符串长度限制 必须 以上述定义的字符为单位进行测量和执行。这意味着存在潜在的最大限制( 4倍字符 )字节数。当然这个限制只有在字符全部占4个UTF-8代码单元(32位)时才成立。

如果你使用的数据库系统(如Spanner)允许以字符为单位定义长度限制,可以放心的假定底层存储系统会处理好字节层面的需求。

计费单位

API 可以 使用码点或字节(使用UTF-8编码)作为计费或配额测量单位(例如Cloud Translation选择使用字符)。如果API没有明确定义,则假定计费单位是字符(如 $0.01 每字符 ,而非 $0.01 每字节 )。

唯一标识符

省流 唯一标识符 应当 仅使用ASCII,通常仅限字母、数字、连字符和下划线,并且 不应 以数字开头。

作为唯一标识符的字符串 应当 将输入限制为ASCII字符,通常是字母、数字、连字符和下划线( [a-zA-Z][a-zA-Z0-9_-]* )。这样确保了不会因规范化导致意外的冲突。如果API决定允许在唯一标识符中使用任意合法Unicode字符,那么API 必须 拒绝任何不符合规范化形式C的输入。通常,唯一标识符 不应 以数字开头,这些前缀保留给Google生成的标识符,并为我们提供了一种简单的方法,检查标识符是服务器生成的数字,还是由用户选择的。

唯一标识符最大长度 应当 是64个字符,当然这个限制可以根据需要进行扩展。64个字符应该足以满足大多数目的,即使是UUID也只需要36个字符。

注意 有关资源标识段的建议,请参考AIP-122

规范化

省流 Unicode值 应当 使用规范化形式C存储。

应当 始终规范化为规范化形式C。唯一标识符 必须 始终使用规范化形式C存储(参考下一节)。

想象我们正在处理西班牙语输入“estar é ”(粗体是重音部分)。这段文本有6个“字符”(此时它们是字素簇)。它有两种有效的Unicode表示:

  • 使用6个码点: U+0065 U+0073 U+0074 U+0061 U+0072 U+00E9
  • 使用7个码点: U+0065 U+0073 U+0074 U+0061 U+0072 U+0065 U+0301

此外,使用UTF-8编码时,这些码点有两种不同的序列化表示:

  • 使用7个代码单元(7字节): 0x65 0x73 0x74 0x61 0x72 0xC3 0xA9
  • 使用8个代码单元(8字节): 0x65 0x73 0x74 0x61 0x72 0x65 0xCC 0x81

为了避免这种(代码单元和码点)尺寸上的差异,使用规范化形式C,它提供了字符串的规范表示。

唯一性

省流 Unicode值 必须 在唯一性检查之前规范化为规范化形式C

为了唯一标识的目的(例如 nameidparent ),值 必须 规范化为规范化形式C(恰好是最紧凑的)。否则,我们可能把本质上“相同的字符串”用于标识两个完全不同的资源。

在上面的例子中,有两种方式表示本质上相同的文本。这就提出一个问题,即这两种表示是否应被视为等效。换句话说,如果有人在唯一标识符字符串域中,使用这两种字节序列,会违反唯一性约束吗?

W3C建议对所有在互联网上传输的内容使用规范化形式C。它是Unicode文本中最紧凑的规范化形式,避免了大多数互操作性问题。如果我们将规范化形式C表示一样的两个不同的Unicode字节序列视为不同的字符串,我们可能需要返回包含 规范化的内容的“Get”请求。这是不可行的,我们 必须 将传入的字符串转换为规范化形式C,或者拒绝不符合规范化形式的标识符,将两个字节序列视为相同。

关于是否应该将字符串视为表示为字节的码点序列(导致基于该字符串的字节表示确定唯一性),还是将字符串解释为具有许多不同字节表示的高级抽象,存在一些争议。这里采取的立场是,我们已经有一个域类型来处理这个问题: bytesstring 类型域已经表达了对输入有效性的意见(必须是有效的UTF-8)。因此,根据底层字节表示,将具有相同规范化形式的两个字符串区别对待,似乎违背了 string 类型的初衷。对于服务不会直接处理的字符串(如 descriptiondisplay_name ),这种区别通常无关紧要。但是当我们使用字符串唯一标识资源时,我们需要采取上述立场。

换句话说,我们的目标是允许使用任何编码(ASCII、UTF-16、UTF-32等)的文本与我们的API交互,不会遇到很多“陷阱”。

参考资料