有没有办法根据 groovy 地图验证架构?

Is there a way to validate a schema against a groovy map?

我的用例是 Jenkins 特定的,但这是一个一般性的 Groovy 问题

我有一张地图,我想对其应用架构。如果此地图缺少字段或某些字段类型错误 (string/int/list/map/etc),我想抛出一条明确的错误消息,解释地图与架构不匹配的位置。

我像这样将 Jenkinsfile 中的映射传递给共享库中的函数,因此 mypipeline 需要包含架构验证部分:

#Jenkinsfile
@Library('my-shared-lib') _

mypipeline ([

    'validate-this-map': [

        // manditory-param-one must exist and be a list
        'manditory-param-one': ["aaa","bbb","ccc"] 

        // manditory-param-two must exist and be a map
        'manditory-param-two': [
            "manditory-one": "111", // must include this field and be a string
            "manditory-two": "222", // must include this field and be a string
            "optional": "33",
        ]

        // manditory-param-three must exist and be a list of maps that have specific fields
        'manditory-param-three': [
            "manditory-one": [
                // all maps in this list must have these two fields
                "aaa": true // validate bool
                "bbb": "sdfsfdsd" //validate string
            ]
        ]

        'optional-param': "sdfsdfs" // ignore this. dont fail if this is here
    ]

])

我过去所做的是将特定于语言的构造(在本例中为 groovy 映射)翻译为 json 并应用支持高级的 json 模式递归等功能。虽然这可能很不稳定,但我想知道这是否可以用惯用的 groovy 方式实现。

我不想将 validate-this-map 设为 class,因为这会使我 认为 的 Jenkinsfile 配置复杂化。有没有办法让 groovy 将地图转换为 class 并让 groovy 验证 class 属性或其他东西?

您可以在构造函数中使用 Groovy 的命名参数并自动创建对象层次结构。

然后你可以在上面添加简单的验证,为了简单起见,我在下面使用了Groovy Truth

代码(在对 JSON 键进行一些清理后)可能如下所示:

import groovy.transform.*

// your classes

@ToString
class A {
  List<String> manditoryParamOne
  ManditoryParamTwo manditoryParamTwo
  ManditoryParamThree manditoryParamThree

  String optionalParam

  boolean asBoolean() {
    manditoryParamOne && manditoryParamTwo && manditoryParamThree
  }
}

@ToString
class ManditoryParamTwo {
  String manditoryOne, manditoryTwo, optional

  boolean asBoolean() {
    manditoryOne && manditoryTwo
  }
}
@ToString
class ManditoryParamThree {
  ManditoryOne manditoryOne

  boolean asBoolean() {
    manditoryOne
  }
}

@ToString
class ManditoryOne{
  Boolean aaa
  String bbb

  boolean asBoolean() {
    null != aaa && bbb
  }
}

// some test code, pay attention to map keys

def mapValid = [
        // manditory-param-one must exist and be a list
        'manditoryParamOne': ["aaa","bbb","ccc"],

        // manditory-param-two must exist and be a map
        'manditoryParamTwo': [
            "manditoryOne": "111", // must include this field and be a string
            "manditoryTwo": "222", // must include this field and be a string
            "optional": "33",
        ],

        // manditory-param-three must exist and be a list of maps that have specific fields
        'manditoryParamThree': [
            "manditoryOne": [
                // all maps in this list must have these two fields
                "aaa": true, // validate bool
                "bbb": "sdfsfdsd" //validate string
            ]
        ],

        'optionalParam': "sdfsdfs" // ignore this. dont fail if this is here
]
A a = new A( mapValid )
assert a.toString() == 'A([aaa, bbb, ccc], ManditoryParamTwo(111, 222, 33), ManditoryParamThree(ManditoryOne(true, sdfsfdsd)), sdfsdfs)'
assert true == !!a
assert true == !!a.manditoryParamOne
assert true == !!a.manditoryParamTwo

def mapInvalid = [
        'manditoryParamTwo': [
            "manditoryOne": "111", // must include this field and be a string
            "manditoryTwo": "222", // must include this field and be a string
            "optional": "33",
        ],

        // manditory-param-three must exist and be a list of maps that have specific fields
        'manditoryParamThree': [
            "manditoryOne": [
                "bbb": "sdfsfdsd" //validate string
            ]
        ]
]
A aa = new A( mapInvalid )
assert false == !!aa
assert true == !!aa.manditoryParamTwo
assert false == !!aa.manditoryParamThree