十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

KubeSphere 依赖链中的 JSON Schema 校验引擎:gojsonschema 的 Loader、Draft 控制与错误体系详解

KubeSphere 依赖链中的 JSON Schema 校验引擎:gojsonschema 的 Loader、Draft 控制与错误体系详解 KubeSphere 依赖链中的 JSON Schema 校验引擎gojsonschema 的 Loader、Draft 控制与错误体系详解【免费下载链接】kubesphereThe container platform tailored for Kubernetes multi-cloud, datacenter, and edge management ⎈ ☁️项目地址: https://gitcode.com/GitHub_Trending/ku/kubesphere本文以 OPA 内部 fork 的 JSON Schema 校验库gojsonschema位于 KubeSphere vendor 目录的 README 为主体完整梳理其加载器Loader、多 Schema 离线引用、Draft 自动检测、元模式校验、错误模型与自定义 Format 检查器的用法并结合 vendor 中的源码与 OPA 的jsonschema内建函数实现说明这套库如何被真正调用、以及 KubeSphere 为何会间接依赖它。读完本文你可以直接使用或阅读该库完成 JSON Schema 校验并能从源码层面理解其递归校验、错误打分与 Rego 内建函数的映射关系。一、gojsonschema 是什么OPA 的 internal fork该库的 READMEREADME.md开宗明义xeipuuv/gojsonschema被复制进了 OPA 的internal/gojsonschema目录并做了三类修改字段导出gojsonschema中schema与subSchema结构体的一部分私有字段被导出为公开字段使 OPA 的类型检查代码能够访问并操纵Compile方法的返回值代码风格适配满足 OPA 的 lint 与 format 检查脚本使其符合 OPA 的代码风格Go 用法现代化例如用类型切换type switching和语言内建的 map 访问方式替代原有的辅助方法。换句话说你在 KubeSphere 仓库中看到的vendor/github.com/open-policy-agent/opa/internal/gojsonschema/不是原始上游库而是经过 OPA 改造的内部版本。README 同时保留了原上游 README 的完整内容包括功能说明、依赖与全部用法示例——这也是本文的主体依据。二、功能定位与依赖README 对库本身的描述An implementation of JSON Schema for the Go programming language. Supports draft-04, draft-06 and draft-07.即一个 Go 语言实现的 JSON Schema 校验库支持 draft-04、draft-06、draft-07 三个草案版本参考 JSON Schema 核心规范与校验规范。其依赖关系github.com/xeipuuv/gojsonpointer—— JSON Pointer 解析github.com/xeipuuv/gojsonreference—— JSON Reference 解析源码中确实直接 import见 subSchema.gogithub.com/stretchr/testify/assert—— 测试断言库README 特别注明该依赖在 OPA 内部版本中已被移除以减少依赖数量。上游库的官方安装方式为go get github.com/xeipuuv/gojsonschema。需要注意KubeSphere 仓库是只读的 vendor 形态这里只是说明该库的来历实际 KubeSphere 通过go.mod中的github.com/open-policy-agent/opa v1.4.2间接引入这份代码无需也不应单独安装。三、基本用法Loader 与 Validate3.1 一个完整示例README 给出的最小可运行示例package main import ( fmt github.com/xeipuuv/gojsonschema ) func main() { schemaLoader : gojsonschema.NewReferenceLoader(file:///home/me/schema.json) documentLoader : gojsonschema.NewReferenceLoader(file:///home/me/document.json) result, err : gojsonschema.Validate(schemaLoader, documentLoader) if err ! nil { panic(err.Error()) } if result.Valid() { fmt.Printf(The document is valid\n) } else { fmt.Printf(The document is not valid. see errors :\n) for _, desc : range result.Errors() { fmt.Printf(- %s\n, desc) } } }调用链非常短gojsonschema.Validate只是NewSchema(ls)编译模式后再调用schema.Validate(ld)。这一点在源码 validation.go 中可以确认// Validate loads and validates a JSON schema func Validate(ls JSONLoader, ld JSONLoader) (*Result, error) { schema, err : NewSchema(ls) if err ! nil { return nil, err } return schema.Validate(ld) } // Validate loads and validates a JSON document func (v *Schema) Validate(l JSONLoader) (*Result, error) { root, err : l.LoadJSON() if err ! nil { return nil, err } return v.validateDocument(root), nil }validateDocument以根JSONContext为起点调用RootSchema.validateRecursive(...)做全树递归校验。3.2 四种 LoaderREADME 明确加载 JSON 数据的方式是先声明合适的 loader。共有四类1Web/HTTP 引用加载loader : gojsonschema.NewReferenceLoader(http://www.some_host.com/schema.json)2本地文件引用加载loader : gojsonschema.NewReferenceLoader(file:///home/me/schema.json)注意 README 的强调引用方式使用 URI schemefile://前缀和完整文件路径都是必须的。3JSON 字符串加载loader : gojsonschema.NewStringLoader({type: string})4自定义 Go 类型加载m : map[string]interface{}{type: string} loader : gojsonschema.NewGoLoader(m)也可以直接传入结构化 struct会被 JSON 化type Root struct { Users []User json:users } type User struct { Name string json:name } data : Root{} data.Users append(data.Users, User{John}) data.Users append(data.Users, User{Sophia}) data.Users append(data.Users, User{Bill}) loader : gojsonschema.NewGoLoader(data)3.3 一次编译、多次校验README 指出如果希望只加载一次 schema 并对多份文档校验可以编译后复用schema, err : gojsonschema.NewSchema(schemaLoader) ... result1, err : schema.Validate(documentLoader1) ... result2, err : schema.Validate(documentLoader2) ... // etc ...结果检查统一走result.Valid()与result.Errors()if result.Valid() { fmt.Printf(The document is valid\n) } else { fmt.Printf(The document is not valid. see errors :\n) for _, err : range result.Errors() { // Err implements the ResultError interface fmt.Printf(- %s\n, err) } }四、离线加载与引用多个 SchemaSchemaLoader默认情况下file与http(s)的外部 schema 引用会自动经文件系统或 HTTP 下载加载。若希望把引用 schema 预置进校验器、避免运行时下载可用SchemaLoadersl : gojsonschema.NewSchemaLoader() loader1 : gojsonschema.NewStringLoader({ type : string }) err : sl.AddSchema(http://some_host.com/string.json, loader1)如果 schema 自身带$id可以直接用AddSchemasloader2 : gojsonschema.NewStringLoader({ $id : http://some_host.com/maxlength.json, maxLength : 5 }) err sl.AddSchemas(loader2)然后把主 schema 交给Compile。主 schema 可以直接引用这些已加载的 schema无需下载loader3 : gojsonschema.NewStringLoader({ $id : http://some_host.com/main.json, allOf : [ { $ref : http://some_host.com/string.json }, { $ref : http://some_host.com/maxlength.json } ] }) schema, err : sl.Compile(loader3) documentLoader : gojsonschema.NewStringLoader(hello world) result, err : schema.Validate(documentLoader)也可以把ReferenceLoader指向一个已加载 schema 的引用直接传给Compileerr sl.AddSchemas(loader3) schema, err : sl.Compile(gojsonschema.NewReferenceLoader(http://some_host.com/main.json))README 的补充提醒通过AddSchema/AddSchemas添加的 schema只有在整个 schema 被编译时才会被校验除非启用了元模式校验见第六节。从源码看这一机制由 schemaLoader.go 中的AddSchemas实现它把每个 loader 的文档解析后写入schemaPool按$id注册引用sl.Validate打开时还会顺带做元模式校验sl.validateMetaschema(doc)。五、Draft 控制AutoDetect、Draft 与 Hybrid 混合模式README 说明默认情况下gojsonschema通过$schema关键字自动检测schema 的 draft并以严格的 draft-04、draft-06 或 draft-07 模式解析若缺少$schema或未显式指定版本则进入Hybrid 混合模式把三个 draft 的特性合并为一种模式自动检测可用AutoDetect属性关闭具体版本可用Draft属性指定sl : gojsonschema.NewSchemaLoader() sl.Draft gojsonschema.Draft7 sl.AutoDetect falseREADME 还给出一个重要结论当自动检测开启默认时draft-07 的 schema 可以安全地引用 draft-04 的 schema反之亦然——前提是所有 schema 都指定了$schema。这些默认值在源码 schemaLoader.go 中可以直接核对type SchemaLoader struct { pool *schemaPool AutoDetect bool Validate bool Draft Draft } func NewSchemaLoader() *SchemaLoader { ps : SchemaLoader{ ... AutoDetect: true, Validate: false, Draft: Hybrid, } ... }即默认AutoDetecttrue、Validatefalse元模式校验关闭、DraftHybrid。六、元模式Meta-schema校验通过AddSchema、AddSchemas、Compile加入的 schema可以通过设置Validate属性使其在被添加时就对照自己的元模式进行校验sl : gojsonschema.NewSchemaLoader() sl.Validate true err : sl.AddSchemas(gojsonschema.NewStringLoader({ $id : http://some_host.com/invalid.json, $schema: http://json-schema.org/draft-07/schema#, multipleOf : true }))README 用这个例子说明multipleOf必须是数字这里传true会在添加阶段直接报错若Validate关闭默认同样的错误只会在Compile步骤才暴露。要点元模式校验返回的错误更可读、信息量更大对正在编写 schema 的开发者帮助显著自定义$schema也支持元模式校验当$schema缺失或AutoDetect为false时会使用当前所用 draft 对应的元模式。源码 schemaLoader.go 中validateMetaschema的实现印证了这一点它先解析 schema 的元模式 URL或按当前 draft 取默认元模式 URL在编译元模式前临时把sl.Validate置false以避免无限递归再编译元模式并validateDocument最后恢复Validate。七、错误体系Type、Context、Field、Details 与自定义模板这是 README 篇幅最大的部分值得逐条继承。7.1 本地化locale与错误码库内部使用字符串错误码可以通过自定义 locale 覆盖gojsonschema.Locale YourCustomLocale{}注意新版gojsonschema可能引入新的错误类型因此使用自定义 locale 的代码需要随库升级而同步更新。7.2err.Type()返回值全集err.Type()返回错误类型的字符串。完整映射如下RequiredType的err.Type()返回值为required错误码错误类型requiredRequiredErrorinvalid_typeInvalidTypeErrornumber_any_ofNumberAnyOfErrornumber_one_ofNumberOneOfErrornumber_all_ofNumberAllOfErrornumber_notNumberNotErrormissing_dependencyMissingDependencyErrorinternalInternalErrorconstConstErorREADME 原文拼写enumEnumErrorarray_no_additional_itemsArrayNoAdditionalItemsErrorarray_min_itemsArrayMinItemsErrorarray_max_itemsArrayMaxItemsErroruniqueItemsMustBeUniqueErrorcontainsArrayContainsErrorarray_min_propertiesArrayMinPropertiesErrorarray_max_propertiesArrayMaxPropertiesErroradditional_property_not_allowedAdditionalPropertyNotAllowedErrorinvalid_property_patternInvalidPropertyPatternErrorinvalid_property_nameInvalidPropertyNameErrorstring_gteStringLengthGTEErrorstring_lteStringLengthLTEErrorpatternDoesNotMatchPatternErrormultiple_ofMultipleOfErrornumber_gteNumberGTEErrornumber_gtNumberGTErrornumber_lteNumberLTEErrornumber_ltNumberLTErrorcondition_thenConditionThenErrorcondition_elseConditionElseError7.3ResultError接口方法err.Value()interface{}返回出错位置给出的值err.Context()*gojsonschema.JsonContext带String()方法打印形如(root).firstName的路径err.Field()string字段名如firstName嵌套属性为person.firstName。与Context().String()相同只是去掉了(root).前缀err.Description()string错误描述基于当前 localeerr.DescriptionFormat()string描述所用的格式串在你需要向结果追加自定义校验错误时相关err.Details()gojsonschema.ErrorDetails即map[string]interface{}错误专属的附加细节。例如 GTE 错误带min值、LTE 错误带max值每个错误都始终包含field键值为err.Field()。7.4 模板函数ErrorTemplateFuncserr.Details()大多用于在 locale 模板中做替换遵循 Gotext/template语法{{.field}} must be greater than or equal to {{.min}}需要更复杂的错误消息处理时可以注册自定义模板函数gojsonschema.ErrorTemplateFuncs map[string]interface{}{ allcaps: func(s string) string { return strings.ToUpper(s), }, }注册后即可在本地化模板中使用{{allcaps .field}} must be greater than or equal to {{.min}}效果示例PASSWORD must be greater than or equal to 8。可用的函数类型可参考 Go 标准库text/template的FuncMap。7.5 源码印证错误从哪来validation.go 中的validateSchema完整实现了anyOf/oneOf/allOf/not/dependencies/if-then-else的组合语义错误构造方式统一为result.addInternalError(new(XXError), context, value, ErrorDetails{...})。两个值得注意的实现细节anyOf与oneOf的最接近匹配策略两者都会在所有子 schema 都失败时记录一个bestValidationResult按score最高的结果失败时把最接近匹配的子 schema 的错误合并进主结果——那个很可能就是用户本想匹配的模式score机制validateRecursive/validateSchema/validateCommon/validateArray/validateObject等每通过一步都result.incrementScore()required属性命中也会加分这个分数就是最接近匹配的度量。multipleOf、minimum/maximum等数值校验则基于big.Rat精确有理数运算见 validation.go避免了浮点误差。八、Format 校验与自定义 FormatCheckerJSON Schema 允许用可选的format属性按知名格式校验实例例如{type: string, format: email}README 列出了已实现的 formatdraft-07 中定义的部分格式并未全部实现date、time、date-timehostname也支持以数字开头的子域名因此不严格遵循 RFC1034副作用是 IPv4 地址也会被识别为合法主机名emailGo 的 email 解析器与 RFC5322 略有偏差包含 Unicode 支持idn-email同email的注意事项ipv4、ipv6uri、uri-reference含 Unicode 支持iri、iri-reference、uri-templateuuidregexGo 使用 RE2 引擎不兼容 ECMA262json-pointer、relative-json-pointerREADME 提醒email、uri、uri-reference与其 Unicode 版本idn-email、iri、iri-reference共用同一套校验代码如果你依赖 Unicode 支持为了与可能不支持常规格式中的 Unicode的其他实现互操作应当使用显式的 Unicode 格式名。uri与idn-email等校验代码大多基于标准库。8.1 注册自定义格式检查器对重复性或更复杂的格式可以实现FormatChecker接口并注册// Define the format checker type RoleFormatChecker struct{} // Ensure it meets the gojsonschema.FormatChecker interface func (f RoleFormatChecker) IsFormat(input interface{}) bool { asString, ok : input.(string) if !ok { return false } return strings.HasPrefix(ROLE_, asString) } // Add it to the library gojsonschema.FormatCheckers.Add(role, RoleFormatChecker{})然后在 JSON Schema 中使用{type: string, format: role}另一个例子是校验整数是否为数据库中存在的用户 ID{type: integer, format: ValidUserId}type ValidUserIdFormatChecker struct{} func (f ValidUserIdFormatChecker) IsFormat(input interface{}) bool { asFloat64, ok : input.(float64) // Numbers are always float64 here if !ok { return false } // XXX // do the magic on the database looking for the int(asFloat64) return true } gojsonschema.FormatCheckers.Add(ValidUserId, ValidUserIdFormatChecker{})注意数值在 Go 侧以float64出现。格式检查器也可以删除例如为了覆盖某个默认格式gojsonschema.FormatCheckers.Remove(hostname)FormatCheckers的注册/查询入口见 format_checkers.go而format关键字在validateCommon中的触发点见 validation.go只要currentSubSchema.format ! 且FormatCheckers.IsFormat(...)返回false即产生DoesNotMatchFormatError细节中带format键。九、追加自定义校验错误Result.AddError校验跑完后可以通过Result.AddError追加额外错误以保持整个结果集的格式统一不必为自己的错误开特殊分支。README 示例校验终极答案的业务逻辑type AnswerInvalidError struct { gojsonschema.ResultErrorFields } func newAnswerInvalidError(context *gojsonschema.JsonContext, value interface{}, details gojsonschema.ErrorDetails) *AnswerInvalidError { err : AnswerInvalidError{} err.SetContext(context) err.SetType(custom_invalid_error) // 必须用 SetDescriptionFormat()它会触发 SetDescription() 基于格式串重新渲染 // 直接 set 的描述会被覆盖。 err.SetDescriptionFormat(Answer to the Ultimate Question of Life, the Universe, and Everything is {{.answer}}) err.SetValue(value) err.SetDetails(details) return err } func main() { // ... schema, err : gojsonschema.NewSchema(schemaLoader) result, err : gojsonschema.Validate(schemaLoader, documentLoader) if true { // some validation jsonContext : gojsonschema.NewJsonContext(question, nil) errDetail : gojsonschema.ErrorDetails{ answer: 42, } result.AddError( newAnswerInvalidError( gojsonschema.NewJsonContext(answer, jsonContext), 52, errDetail, ), errDetail, ) } return result, err }README 总结当需要超越 JSON Schema 各草案能表达的范围例如业务特定逻辑时这一机制特别有用。十、在 OPA 与 KubeSphere 中的真实调用链README 第一部分提到 fork 的目的——让 OPA 可以把Compile产物设置为 Rego 类型。在 vendor 源码中这条调用链非常清晰OPA 的 Rego 内建函数注册jsonschema.go 的init()注册了两个内建函数func init() { RegisterBuiltinFunc(ast.JSONSchemaVerify.Name, builtinJSONSchemaVerify) RegisterBuiltinFunc(ast.JSONMatchSchema.Name, builtinJSONMatchSchema) }jsonschema_verify(document)把 Rego 的字符串/对象参数转成gojsonschema.JSONLoader字符串先做json.Valid预检对象走ast.JSON序列化后用NewGoLoader再调用gojsonschema.NewSchema(loader)检查 schema 本身是否可解析返回[true, null]或[false, jsonschema: err]json.match_schema(document, schema)同样把两个参数转成 loader 后gojsonschema.NewSchema编译 schema 并schema.Validate(documentLoader)把result.Errors()中每个错误的Type()、Field()、Description()打包成 Rego 对象数组返回。值得注意的是它使用bctx.InterQueryBuiltinValueCache按 schema 值缓存编译好的*gojsonschema.Schema避免同一查询内重复编译——这正是 README 中一次编译、多次校验模式在 OPA 内部的应用ast编译路径的引用compile.go 也 import 了internal/gojsonschema与 README 所述导出字段供 OPA 类型检查代码访问Compile返回值的修改目的一致。KubeSphere 侧的间接依赖KubeSphere 的 go.mod 声明了github.com/open-policy-agent/opa v1.4.2其 RBAC 授权路径如 pkg/apiserver/authorization/rbac/rbac.go 引入opa/rego、pkg/componenthelper/auth/rbac/helper.go 解析 RoleTemplate 上的 Rego 策略都构建在这份 OPA 依赖之上。也就是说gojsonschema之于 KubeSphere 是一条OPA 依赖树里的 JSON Schema 校验基础设施Rego 规则里一旦使用jsonschema_verify/json.match_schema底层执行的就是本文所讲的这套 Loader、递归校验与错误模型。十一、测试基线README 末尾说明该库使用官方 JSON Schema 测试套件验证自身行为JSON-Schema-Test-Suitejson-schema 组织的标准测试集覆盖 draft-04/06/07 的校验语义。十二、小结与使用建议选 Loader纯字符串场景用NewStringLoader已有 Go 值含 struct用NewGoLoader本地/远程文件用NewReferenceLoader必须带file:///http://前缀与完整路径多 schema 引用用SchemaLoader.AddSchema/AddSchemas Compile预置引用运行时零下载Draft缺省即 Hybrid 混合模式 自动检测要严格模式需显式设sl.Draft gojsonschema.Draft7且sl.AutoDetect false并保证所有 schema 声明$schema开发 schema 时打开sl.Validate true把元模式错误挡在AddSchemas阶段自定义规则格式级用FormatCheckers.Add/Remove业务级用Result.AddError追加符合ResultError接口的自定义错误务必用SetDescriptionFormat。本文所有用法均出自 vendor 内 README.md实现细节均以vendor/github.com/open-policy-agent/opa/internal/gojsonschema/下的 validation.go、schemaLoader.go、subSchema.go、types.go 及 OPA 的 topdown/jsonschema.go 为据可按路径逐一核对。【免费下载链接】kubesphereThe container platform tailored for Kubernetes multi-cloud, datacenter, and edge management ⎈ ☁️项目地址: https://gitcode.com/GitHub_Trending/ku/kubesphere创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表