YAML出错的方式有很多种。看看两个流行的 JavaScript 库如何处理来自地狱的文档。
JavaScript中的YAML
在 npm 上搜索 JavaScript YAML 解析器会出现 YAML和js-yaml。根据 npm, js-yaml 的每周下载量最多,在 GitHub 上的星数最多,但是,与 js-yaml 的上次发布相比,YAML 似乎处于更积极的开发状态。还有yamljs,已经 6 年没有发布,所以我暂时忽略它。
让我们看看 YAML 和 js-yaml 对来自地狱的 YAML 文档做了什么。
文档本身
server_config: port_mapping: - 22:22 - 80:80 - 443:443 serve: - /robots.txt - /favicon.ico - *.html - *.png - !.git geoblock_regions: - dk - fi - is - no - se flush_cache: on: [push, memory_pressure] priority: background allow_postgres_versions: - 9.5.25 - 9.6.24 - 10.23 - 12.13
那么我们的 JavaScript 库如何处理这个文件呢?
失败
锚点、别名和标签
让我们从失败开始
serve: - /robots.txt - /favicon.ico - *.html - *.png - !.git # Do not expose our Git repository to the entire world.
这会导致我们的两个 JavaScript YAML 库都抛出错误,都引用了一个未定义的别名。这是因为这*
是一种引用先前在文档中使用&
. 在我们的文档中,那个锚从未被创建,所以这是一个解析错误。
如果你想了解更多关于锚点和别名的信息,它似乎在构建管道中很重要。
为了尝试让文件进行解析,我们可以按照预期的方式制作这些别名字符串。
serve: - /robots.txt - /favicon.ico - "*.html" - "*.png" - !.git # Do not expose our Git repository to the entire world.
现在我们从我们的库中得到另一个解析错误;他们都抱怨未知或未解析的标签。开头!
的!.git
是触发此行为的角色。
对我来说,标记似乎是YAML中最复杂的部分。它们取决于您正在使用的解析器,并允许解析器对标记后面的内容进行自定义。我的理解是,您可以在JavaScript中使用它来标记要解析为Map(而不是Object)或Set(而不是Array)的某些内容。
这意味着加载不受信任的 YAML 文档通常是不安全的,因为它可能导致任意代码执行。
PyYaml 显然有一种safe_load
方法可以避免这种情况,但 Go 的 yaml 包没有。似乎 JavaScript 库也缺少此功能,因此针对不受信任的 YAML 文档的警告成立。
如果您确实想利用 yaml 中的标记功能,可以查看yaml 包的自定义数据类型文档或js-yaml 支持的 yaml 类型和不安全类型扩展。
为了解析 YAML 文件,让我们用引号将所有奇怪的 yaml 工件括起来,使它们成为字符串:
serve: - /robots.txt - /favicon.ico - "*.html" - "*.png" - "!.git"
通过serve
上面的代码块,文件现在可以解析了。那么其他潜在的 yaml 问题会怎样呢?
偶然数字
到目前为止,我从这次调查中收集到的一件事是,如果您需要将某些内容作为字符串,请不要对其模棱两可,将其括在引号中。这计入了上面的别名和标签,也计入了意外数字。在 yaml 文件的以下部分中,您会看到版本号列表:
allow_postgres_versions: - 9.5.25 - 9.6.24 - 10.23 - 12.13
版本号是字符串,数字中的小数点不能超过一位。但是当这被任何一个 JavaScript 库解析时,结果如下:
allow_postgres_versions: [ '9.5.25', '9.6.24', 10.23, 12.13 ]
现在我们有一个字符串和数字数组。如果 YAML 解析器认为某些东西看起来像一个数字,它就会这样解析它。当您开始使用这些值时,它们可能不会像您期望的那样起作用。
成功案例
这在 JavaScript 世界中并不全是坏事。在解决了上述问题之后,我们现在可能就清楚了。现在让我们看一下从这个 YAML 文件中正确解析的内容。
六进制数
在 YAML 文件的端口映射部分,我们看到:
port_mapping: # Expose only ssh and http to the public internet. - 22:22 - 80:80 - 443:443
这22:22
在 yaml 版本 1.1 中是危险的,PyYaml 将其解析为六十进制(基数 60)数字,给出1342
. 值得庆幸的是,这两个 JavaScript 库都实现了 YAML 1.2,并且22:22
在这种情况下被正确解析为字符串。
port_mapping: [ '22:22', '80:80', '443:443' ]
挪威问题
在 YAML 1.1 中no
被解析为false
. 这被称为“挪威问题”,因为将国家列为两个字符标识符是相当普遍的,并且具有以下 YAML:
geoblock_regions: - dk - fi - is - no - se
解析成这个 JavaScript:
geoblock_regions: [ 'dk', 'fi', 'is', false, 'se' ]
这只是没有帮助。好消息是,与 Go 的 YAML 库不同,这两个 JavaScript 库都实现了 YAML 1.2,并no
不再作为false
. 这些geoblock_regions
部分被成功解析如下:
geoblock_regions: [ 'dk', 'fi', 'is', 'no', 'se' ]
非字符串键
您可能认为 YAML 中的键会被解析为字符串,如 JSON。但是它们可以是任何值。再一次,有些价值观可能会让你失望。就像挪威问题中yes
andno
可以解析为true
and一样, andfalse
也是如此。这体现在我们的 YAML 文件中:on
off
flush_cache
flush_cache: on: [push, memory_pressure] priority: background
这里的关键是on
,但在某些库中它被解析为布尔值。在 Python 中,更令人困惑的是,布尔值随后被字符串化并显示为 key "True"
。值得庆幸的是,这由 JavaScript 库处理并on
成为关键"on"
。
flush_cache: { on: [ 'push', 'memory_pressure' ], priority: 'background' }
这在 GitHub Actions 中再次受到特别关注,用于on
确定哪些事件应触发 Action。我想知道 GitHub 在实施解析时是否必须解决这个问题。
解析为YAML 版本 1.1
我们的 JavaScript 库回避的许多问题都是来自 YAML 1.1 的问题,并且两个库都完全实现了 YAML 1.2。如果您确实希望不小心,或者您必须使用 YAML 1.1 设置显式解析 yaml 文件,YAML库可以为您完成。您可以将第二个参数传递给该parse
函数以告诉它使用 1.1 版,如下所示:
import { parse } from "yaml"; const yaml = parse(yamlContents, { version: "1.1" }); console.log(yaml);
现在你得到了一个包含上述所有乐趣的结果:
{ server_config: { port_mapping: [ 1342, '80:80', '443:443' ], serve: [ '/robots.txt', '/favicon.ico', '*.html', '*.png', '!.git' ], geoblock_regions: [ 'dk', 'fi', 'is', false, 'se' ], flush_cache: { true: [ 'push', 'memory_pressure' ], priority: 'background' }, allow_postgres_versions: [ '9.5.25', '9.6.24', 10.23, 12.13 ] } }
请注意,在这种情况下,我将别名和标记保留为字符串,以便可以成功解析文件。
坚持使用版本 1.2,这是两个 JavaScript YAML 库中的默认版本,您将获得更合理的结果。
YAML不是很有趣吗?
在这篇文章中,我们已经看到,如果您不知道别名或标签,很容易编写格式错误的 YAML。编写字符串和数字的混合数组也很容易。还有 YAML 1.1 仍在使用的语言和库,以及on
. yes
, off
, 和no
是布尔值,一些数字可以解析为 60 进制。
在经历了所有这些之后,我的建议是在编写 YAML 时谨慎行事。如果您希望键或值是字符串,请将其括在引号中并明确将其设为字符串。
另一方面,如果您正在解析其他人的 yaml,那么您将需要进行防御性编程并尝试处理边缘情况,例如意外数字,这仍然会导致问题。
最后,如果您可以选择,请选择与 YAML 不同的格式。YAML 应该是人性化的,但它可能产生的惊喜和错误肯定对开发人员不友好,最终违背了初衷。
来自 hell post 的原始 YAML 文档的结论提出了许多 YAML 的替代方案,它们会更好地工作。我忍不住认为,在 JavaScript 的世界中,基于 JSON 但对作者更友好的东西应该是解决方案。
有一个包可以简单地从 JSON 中剥离注释,或者有JSON5一种旨在更易于手动编写和维护的 JSON 格式。JSON5 支持注释以及尾随逗号、多行字符串和各种数字格式。如果您想更轻松地编写 JSON 并更一致地解析手工编写的文件,那么这些都是一个好的开始。
如果你能避免 YAML,我推荐它。如果你不能,祝你好运。
推荐阅读