|
| 1 | +package parameter |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + "log/slog" |
| 6 | + |
| 7 | + "gopkg.in/yaml.v3" |
| 8 | + |
| 9 | + "github.com/paloaltonetworks/pan-os-codegen/pkg/errors" |
| 10 | + "github.com/paloaltonetworks/pan-os-codegen/pkg/schema/profile" |
| 11 | + "github.com/paloaltonetworks/pan-os-codegen/pkg/schema/validator" |
| 12 | +) |
| 13 | + |
| 14 | +// Parameter describes a single parameter for the given object spec. |
| 15 | +// |
| 16 | +// Parameter can describe both a top-level parameter of the object |
| 17 | +// or nested parameter of another object parameter. |
| 18 | +// |
| 19 | +// Spec type is any, as its unmarshalling is done in a custom |
| 20 | +// UnmarshalYAML function. |
| 21 | +type Parameter struct { |
| 22 | + Name string `yaml:"name"` |
| 23 | + Description string `yaml:"description"` |
| 24 | + Type string `yaml:"type"` |
| 25 | + Profiles []profile.Profile `yaml:"profiles"` |
| 26 | + Validators []validator.Validator `yaml:"validators"` |
| 27 | + Spec any `yaml:"-"` |
| 28 | +} |
| 29 | + |
| 30 | +// SimpleSpec describes a parameter of a simple type. |
| 31 | +type SimpleSpec struct { |
| 32 | + Default any `yaml:"default"` |
| 33 | + Required bool `yaml:"required"` |
| 34 | +} |
| 35 | + |
| 36 | +// EnumSpecValue describes a single enum value. |
| 37 | +type EnumSpecValue struct { |
| 38 | + Value string `yaml:"value"` |
| 39 | + Const string `yaml:"const"` |
| 40 | +} |
| 41 | + |
| 42 | +// EnumSpec describes a parameter of type enum |
| 43 | +// |
| 44 | +// Values is a list of ParameterEnumSpecValue, where each one consisting of the PAN-OS Value |
| 45 | +// and its optional Const representation. This allows to generate a more meaningful |
| 46 | +// types, for example when spec value is "color1", and its const is "red", the following |
| 47 | +// type will be marshalled for pan-os-go SDK: |
| 48 | +// |
| 49 | +// ParameterRed ParameterType = "color1" |
| 50 | +// |
| 51 | +// when spec value is "up" and spec const is empty, the following type will be marshalled |
| 52 | +// instead: |
| 53 | +// |
| 54 | +// ParameterUp ParameterType = "up" |
| 55 | +type EnumSpec struct { |
| 56 | + Required bool `yaml:"required"` |
| 57 | + Default string `yaml:"default"` |
| 58 | + Values []EnumSpecValue `yaml:"values"` |
| 59 | +} |
| 60 | + |
| 61 | +type StructSpec struct { |
| 62 | + Required bool `yaml:"required"` |
| 63 | + Parameters []*Parameter `yaml:"params"` |
| 64 | + Variants []*Parameter `yaml:"variants"` |
| 65 | +} |
| 66 | + |
| 67 | +type ListSpecElement struct { |
| 68 | + Type string `yaml:"type"` |
| 69 | + Required bool `yaml:"required"` |
| 70 | + Spec StructSpec `yaml:"spec"` |
| 71 | +} |
| 72 | + |
| 73 | +type ListSpec struct { |
| 74 | + Required bool `yaml:"required"` |
| 75 | + Items ListSpecElement `yaml:"items"` |
| 76 | +} |
| 77 | + |
| 78 | +type NilSpec struct{} |
| 79 | + |
| 80 | +// UnmarshalYAML implements custom unmarshalling logic for parameters |
| 81 | +// |
| 82 | +// When unmarshalling yaml objects into parameters, their spec is unmarshalled |
| 83 | +// into different structures based on the spec type. This custom UnmarshalYAML |
| 84 | +// functions handles this logic. |
| 85 | +func (p *Parameter) UnmarshalYAML(n *yaml.Node) error { |
| 86 | + // Create an empty Parameter value and a new temporary structure |
| 87 | + // that is based on the parameter, but overrides Spec field to be |
| 88 | + // of a generic yaml.Node type. |
| 89 | + // This new type, and the casting below is needed so that yaml |
| 90 | + // unmarshaller doesn't recursively call UnmarshalYAML. |
| 91 | + type P Parameter |
| 92 | + type S struct { |
| 93 | + *P `yaml:",inline"` |
| 94 | + Spec yaml.Node `yaml:"spec"` |
| 95 | + } |
| 96 | + |
| 97 | + // Cast "this" parameter pointer to a new S type and then decode |
| 98 | + // entire parameter yaml object into this new object. This will |
| 99 | + // unmarshal spec field from yaml into yaml.Node Spec field in the |
| 100 | + // temporary structure. |
| 101 | + obj := &S{P: (*P)(p)} |
| 102 | + if err := n.Decode(obj); err != nil { |
| 103 | + return err |
| 104 | + } |
| 105 | + |
| 106 | + // Now that we have unmarshalled entire parameter object into a temporary |
| 107 | + // structure, we can assign proper structure to the parameter Spec field. |
| 108 | + switch p.Type { |
| 109 | + case "object": |
| 110 | + p.Spec = new(StructSpec) |
| 111 | + case "list": |
| 112 | + p.Spec = new(ListSpec) |
| 113 | + case "enum": |
| 114 | + p.Spec = new(EnumSpec) |
| 115 | + case "nil": |
| 116 | + p.Spec = new(NilSpec) |
| 117 | + case "string", "bool", "int64": |
| 118 | + p.Spec = new(SimpleSpec) |
| 119 | + default: |
| 120 | + return errors.NewSchemaError(fmt.Sprintf("unsupported parameter type: '%s'", p.Type)) |
| 121 | + } |
| 122 | + |
| 123 | + // Finally, decode obj.Spec (which is yaml.Node type) into the parameter |
| 124 | + // spec structure |
| 125 | + return obj.Spec.Decode(p.Spec) |
| 126 | +} |
| 127 | + |
| 128 | +// Required returns whether given parameter is required, by checking its spec. |
| 129 | +func (p *Parameter) Required() bool { |
| 130 | + switch spec := p.Spec.(type) { |
| 131 | + case *SimpleSpec: |
| 132 | + return spec.Required |
| 133 | + case *ListSpec: |
| 134 | + return spec.Required |
| 135 | + case *StructSpec: |
| 136 | + return spec.Required |
| 137 | + case *EnumSpec: |
| 138 | + return spec.Required |
| 139 | + case *NilSpec: |
| 140 | + return false |
| 141 | + } |
| 142 | + |
| 143 | + return false |
| 144 | +} |
| 145 | + |
| 146 | +// SingularName returns a singular name for parameter. |
| 147 | +// |
| 148 | +// When Parameter type is list, and list profile type is either |
| 149 | +// "entry" or "member", we us first path of an profile xpath array |
| 150 | +// to determine a singular name for the given parameter. |
| 151 | +// |
| 152 | +// When called for non-list parameters, parameter name is returned |
| 153 | +// instead. |
| 154 | +func (p *Parameter) SingularName() string { |
| 155 | + switch p.Spec.(type) { |
| 156 | + case *ListSpec: |
| 157 | + var singularName string |
| 158 | + for _, profile := range p.Profiles { |
| 159 | + switch profile.Type { |
| 160 | + case "entry", "member": |
| 161 | + if len(profile.Xpath) >= 1 { |
| 162 | + singularName = profile.Xpath[0] |
| 163 | + } |
| 164 | + } |
| 165 | + } |
| 166 | + |
| 167 | + if singularName == "" { |
| 168 | + slog.Warn("Couldn't generate singular name for list parameter", "parameter", p.Name) |
| 169 | + } |
| 170 | + |
| 171 | + return singularName |
| 172 | + } |
| 173 | + |
| 174 | + return p.Name |
| 175 | +} |
| 176 | + |
| 177 | +// SpecItemsType is a shorthand accessor to list items type |
| 178 | +// |
| 179 | +// When checking parameter list item type, parameter's spec has to |
| 180 | +// be first cast to the ParameterListSpec type. This function gives |
| 181 | +// a quick access to the type without having to do casting manually. |
| 182 | +// |
| 183 | +// When called on any other parameter type, it returns an empty |
| 184 | +// string, so the caller (mostly templates) must ensure that it's |
| 185 | +// being called on list parameters. |
| 186 | +func (p *Parameter) SpecItemsType() string { |
| 187 | + switch spec := p.Spec.(type) { |
| 188 | + case *ListSpec: |
| 189 | + return spec.Items.Type |
| 190 | + default: |
| 191 | + return "" |
| 192 | + } |
| 193 | +} |
0 commit comments