100 Commits
Author SHA1 Message Date
Tim Hockin 95ebc83857 Fix flagged lint 2026-10-12 17:44:53 -07:00
Tim Hockin 46ab02f04d Add optional string payload to required/forbidden
Now we can do `+k8s:forbidden="reason"`

Gemini helped me write this.
2026-10-12 17:44:51 -07:00
Tim Hockin dd22535156 Add an OptionGetter interface to Operation
This allows us to keep a single map with all the values without fear of
it being mutated.

I'd like to use this in external projects.
2026-09-28 16:42:42 -07:00
Tim Hockin 933188d024 DV: Add a ptr field in subfield tests 2026-09-12 04:34:00 +00:00
Tim Hockin 689b8d6505 DV: Add a testcase for uninitialized struct with subfield 2026-09-12 04:34:00 +00:00
Tim Hockin cdc13253f6 Revert "code-generator/validation-gen: Disable nested +k8s:subfield validations"
This reverts commit e5dc7167a5.
2026-09-12 04:13:02 +00:00
Tim Hockin 0bf6b54bf6 Fix lint 2026-09-03 18:00:54 +00:00
Tim Hockin bb954fb651 Add validate.UpdateValue, deprecate the By* forms
This is the last place that calls semantic DeepEqual directly.
2026-09-03 18:00:54 +00:00
Tim Hockin b8d76348fa validation-gen: Let deep-equal func be set by tag
This makes validation gen more generally usable outside the confines of
k8s APIs.  This does not eliminate 100% of direct calls but fixes all
the generated code.
2026-09-03 18:00:54 +00:00
Tim Hockin 4a393d2350 validation-gen: Support maps of pointers
We don't support this in k8s but it seems that validation-gen can be
used to generate code for other environments, like protobuf, which do
support maps of pointers (to structs).

Gemini helped with this one.
2026-08-17 17:41:23 -07:00
Tim Hockin 5b35b7ed6c DV: comment erroneous docs for k8s-ip format 2026-08-03 17:21:29 -07:00
Tim Hockin 67efa8b845 Add a DV format for prefixed label key 2026-07-19 16:57:09 -07:00
Tim Hockin 3bee4b8248 Refactor where no-nil-ptrs check lives 2026-07-08 09:59:37 -07:00
Tim Hockin 045d301647 Fix lint for ptr.To() 2026-07-08 09:59:36 -07:00
Tim Hockin 181177004b validation-gen: allow other major versions in tag levels 2026-07-08 09:59:35 -07:00
Tim Hockin f160772efd validation-gen: Move functions around, no changes 2026-07-08 09:59:34 -07:00
Tim Hockin 35644aff3e validation-gen: Rename and comment functions 2026-07-08 09:59:23 -07:00
Tim Hockin 4449df4be7 validation-gen: Support lists of pointers
We don't support this in k8s but it seems that validation-gen can be
used to generate code for other environments, like protobuf, which do
support lists of pointers.

Gemini helped with this one.
2026-07-08 09:58:29 -07:00
Tim Hockin 237f89612c validation-gen: Remove 1-use func, rename another 2026-07-08 09:58:28 -07:00
Tim Hockin 60db4d99f3 validation-gen: Add comments 2026-07-08 09:58:27 -07:00
Tim Hockin 78fa807216 validation-gen: Change MatchFunc signature 2026-07-08 09:58:26 -07:00
Tim Hockin b60e6b211d validation-gen: Trivial rename for consistency 2026-07-08 09:58:25 -07:00
Tim Hockin 0a9a6cff45 validation-gen: Remove now unused DirectEqualPtr 2026-07-08 09:58:25 -07:00
Tim Hockin 167d04287e validation-gen: Change MatchFunc to take pointers
This clarifies code for upcoming changes but is also a small perf win.

Given this benchmark:

./bm/doc.go:
```
// +k8s:validation-gen=TypesWithField=TypeMeta
// +k8s:validation-gen-scheme-registry=k8s.io/code-generator/cmd/validation-gen/testscheme.Scheme

// This is a test package.
// +k8s:validation-gen-nolint
package bm

import "k8s.io/code-generator/cmd/validation-gen/testscheme"

var localSchemeBuilder = testscheme.New()

type Struct struct {
	 TypeMeta int

	// +k8s:listType=map
	// +k8s:listMapKey=keyField
	ListField []OtherStruct `json:"listField"`
}

type OtherStruct struct {
	KeyField string `json:"keyField"`
	// +k8s:maxLength=1024
	DataField string `json:"dataField"`
}
```

./bm/doc_test.go:
```
package bm

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"k8s.io/apimachinery/pkg/api/operation"
	"k8s.io/apimachinery/pkg/util/validation/field"
)

func BenchmarkSmall(b *testing.B) {
	b.ReportAllocs()
	s := Struct{}
	for i := range 5 {
		s.ListField = append(s.ListField, OtherStruct{
			KeyField:  fmt.Sprintf("key%d", i),
			DataField: strings.Repeat(fmt.Sprintf("data%d", i), 3),
		})
	}
	for i := 0; i < b.N; i++ {
		Validate_Struct(context.Background(), operation.Operation{}, field.NewPath("obj"), &s, nil)
	}
}
func BenchmarkLarge(b *testing.B) {
	b.ReportAllocs()
	s := Struct{}
	for i := range 100 {
		s.ListField = append(s.ListField, OtherStruct{
			KeyField:  fmt.Sprintf("key%d", i),
			DataField: strings.Repeat(fmt.Sprintf("data%d", i), 100),
		})
	}
	for i := 0; i < b.N; i++ {
		Validate_Struct(context.Background(), operation.Operation{}, field.NewPath("obj"), &s, nil)
	}
}
```

The new version is both faster (10-20%) and has less allocs per op (~30%).

before:
BenchmarkSmall-12       43848441               424.5 ns/op           736 B/op         17 allocs/op
BenchmarkLarge-12         750921             24098 ns/op           12896 B/op        302 allocs/op

after:
BenchmarkSmall-12       52705480               336.3 ns/op           576 B/op         12 allocs/op
BenchmarkLarge-12         852565             21278 ns/op            9696 B/op        202 allocs/op
2026-07-08 09:58:24 -07:00
Tim Hockin 4ff724d943 validation-gen: Add file tag for TypesWithSuffix
This lets me use it more directly in protobuf code where I really only
need to generate validation for Requests, not Responses.
2026-07-08 09:58:23 -07:00
Tim Hockin 84001df0a3 validation-gen: make file-tag more expressive
What used to be:
    `// +k8s:validation-gen=TypeMeta`
...is now:
    `// +k8s:validation-gen=TypesWithField=TypeMeta`
...which leaves room for other criteria
2026-07-08 09:58:06 -07:00
Tim Hockin fa09e37814 validation-gen: Nix emit-register-funcs, use a tag
Get rid of flag `--emit-register-funcs` and use a per-pkg tag.

Set `+k8s:validation-gen-scheme-registry=nil` to disable register
function generation for a pkg.
2026-07-08 09:55:51 -07:00
Tim Hockin 9b239b99ab validation-gen: Add a flag to skip register funcs 2026-07-08 09:55:50 -07:00
Tim Hockin eaf1fa198c validation-gen: better check explicit opt-in/out
Rather than ONLY checking the SecondClosest comments, we can check the
type's real comments, too.
2026-07-08 09:55:49 -07:00
Tim Hockin 6d20d043cc Add whitespace in DV func calls 2026-04-28 12:29:16 -07:00
Tim Hockin 685199b229 Add whitespace in DV ratcheting 2026-04-28 12:08:35 -07:00
Tim Hockin a557aa7e80 Add whitespace in DV func bodies 2026-04-28 09:19:24 -07:00
Tim Hockin edf080f056 Add whitespace in DV func signatures 2026-04-28 09:12:24 -07:00
Tim Hockin 8950aac006 Add whitespace in DV register func 2026-04-28 09:12:19 -07:00
Tim Hockin 58bb17adcd Use sets.List() instead of UnsortedList() 2026-03-11 10:21:38 -07:00
Tim Hockin f2d8accbbb Fix maxItems test to match minItems with ErrorMatcher 2026-03-11 10:21:37 -07:00
Tim Hockin 91ddc8ff78 Add DV tag '+k8s:minItems' 2026-03-11 10:21:37 -07:00
Tim Hockin 383d74ba4f Fail to generate unsigned min and max for negative values 2026-03-11 10:21:36 -07:00
Tim Hockin 7f90ee0043 Fix minimum test to match maximum with ErrorMatcher 2026-03-11 10:21:35 -07:00
Tim Hockin c53d59c41d Add DV tag '+k8s:maximum' 2026-03-11 10:21:34 -07:00
Tim Hockin 4c936747d3 Add and use util.ParseBool in validation-gen 2026-03-11 10:21:33 -07:00
Tim Hockin 1cdd727b26 Use util.ParseInt in all validators 2026-03-11 10:21:32 -07:00
Tim Hockin 8f2be40b50 Rename an argument in validation stack 2026-01-23 17:40:38 -08:00
Tim Hockin ea02c4bfb9 Add validation-gen 'warning' to tag docs 2025-12-15 09:41:28 -08:00
Tim Hockin b1c85ef2fe golangci-lint: no need to enable deprecatedComment 2025-10-21 12:47:38 -07:00
Tim Hockin 8443187688 Warn when quota request > limit 2025-10-03 10:58:35 -07:00
Tim Hockin 0a26ff5cb4 Update CSR DV test to match RC style
Once we go broad, people will copy these.  Let's make them easy to debug
:)
2025-10-01 19:45:37 +00:00
Tim Hockin 2d48dae391 Eliminate public ValidateReplicationControllerName
Everyone who referenced it now uses the underlying function.  Clearer
and frees me up to change objectMeta validation without impacting anyone
else.
2025-10-01 19:45:37 +00:00
Tim HockinandYongrui Lin 229c6b13ca Validate ReplicationController.metadata.name
This relies on `+k8s:subfield` and validation cohorts.  The
`k8s:optional` ensures that we don't run the name validation if name is
empty, because core apimachinery will already flag it as Required().

This demonstrates some of the DV value - docs and clients are now (in
theory) able to see what RC's name format is.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-10-01 19:45:37 +00:00
Tim Hockin 7cf9989222 Add ValidateObjectMetaWithOpts() to apimachinery
This new function does 1 main thing (changes how name validation works),
and the rest is future-proofing.  Specifically this changes the
signature of the name validation function.

Before: Name validation functions return `[]string` and apimachinery
logic wraps those in `field.Invalid()`.

Problem: This makes it hard to incrementally add declarative validation,
which needs access to potential name and generateName errors to set
origins properly.

After: New name validation functions return `field.ErrorList`, which can
be wrapped in trivial functions to do things like "mark as covered by
declarative" in just one place rather than for all types.

The "WithOpts" part of this gives us a new function name which isn't
horrible and is plausibly useful in the future.

Another notable change is that this no longer directly validates the
`generateName` field (but only on the new path -- existing code still
behaves the same).  From the code:

```
+   // generateName is not directly validated here. Types can have
+   // different rules for name generation, and the nameFn is for validating
+   // the post-generation data, not the input. In the past we assumed that
+   // name generation was always "append 5 random characters", but that's not
+   // NECESSARILY true. Also, the nameFn should always be considering the max
+   // length of the name, and it doesn't know enough about the name generation
+   // to do that. Also, given a bad generateName, the user will get errors
+   // for both the generateName and name fields. We will focus validation on
+   // the name field, which should give a better UX overall.
```

This also beefs up tests a bit.
2025-10-01 19:45:37 +00:00
Tim Hockin b00e0f3020 Add cohort support to +k8s:item
Given:

```
   // +k8s:item(stringKey: "target", intKey: 42, boolKey: true)=+k8s:validateFalse="item Items[stringKey=target,intKey=42,boolKey=true] 1"
   // +k8s:item(stringKey: "target", intKey: 42, boolKey: true)=+k8s:validateFalse="item Items[stringKey=target,intKey=42,boolKey=true] 2"
   Items []Item `json:"items"`
```

...we get:

```
   func() { // cohort {"stringKey": "target", "intKey": 42, "boolKey": true}
       errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
           return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[stringKey=target,intKey=42,boolKey=true] 1")
       })...)
       errs = append(errs, validate.SliceItem(ctx, op, fldPath, obj, oldObj, func(item *Item) bool { return item.StringKey == "target" && item.IntKey == 42 && item.BoolKey == true }, validate.DirectEqual, func(ctx context.Context, op operation.Operation, fldPath *field.Path, obj, oldObj *Item) field.ErrorList {
           return validate.FixedResult(ctx, op, fldPath, obj, oldObj, false, "item Items[stringKey=target,intKey=42,boolKey=true] 2")
       })...)
   }()
```
2025-10-01 19:45:37 +00:00
Tim Hockin 975df59fcd Add cohort support to +k8s:subfield
Given:

```
    // +k8s:subfield(name)=+k8s:optional
    // +k8s:subfield(name)=+k8s:format=dns-label
    // +k8s:subfield(generateName)=+k8s:optional
    // +k8s:subfield(generateName)=+k8s:format=dns-label
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
```

...we get:

```
  errs = append(errs,
    func(fldPath *field.Path, obj, oldObj *metav1.ObjectMeta) (errs field.ErrorList) {
      func() { // cohort name
        if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.OptionalValue); len (e) != 0 {
          return // do not proceed
        }
        errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "name", func(o *metav1.ObjectMeta) *string { return &o.Name }, validate.DNSLabel)...)
      }()
      func() { // cohort generateName
        if e := validate.Subfield(ctx, op, fldPath, obj, oldObj, "generateName", func(o *metav1.ObjectMeta) *string { return &o.GenerateName }, validate.OptionalValue); len(e) != 0
 {
          return // do not proceed
        }
        errs = append(errs, validate.Subfield(ctx, op, fldPath, obj, oldObj, "generateName", func(o *metav1.ObjectMeta) *string { return &o.GenerateName }, validate.DNSLabel)...)
      }()
      return
    }(fldPath.Child("metadata"), &obj.ObjectMeta, safe.Field(oldObj, func(oldObj *corev1.ReplicationController) *metav1.ObjectMeta { return &oldObj.ObjectMeta }))...)
```

It is worth noting that this only works one level deep:

```
    // +k8s:subfield(foo)=+k8s:subfield(bar)=+k8s:optional
    // +k8s:subfield(foo)=+k8s:subfield(bar)=+k8s:format=dns-label
    Field StructType
```

...will generate one cohort ("foo") with two distinct calls to Subfield,
so the optional will not short-circuit format.  If we need deeper
cohorting we need to either make subfield take multiple field names
(e.g. `subfield(foo.bar)` or `subbfield(foo, bar)`) or we need to
accumulate "structure" in addition to validator calls.  This is similar
to how we handle multiple `eachVal` tags - each one iterates the list.
2025-10-01 19:45:37 +00:00
Tim HockinandYongrui Lin efe4d17d15 Add support for validation cohorts
On a given field or type, there may be multiple "cohorts" of validation
which need to be processed together.  For example, a short-circuit
validation and a non-short-circuit for the same subfield.

The unnamed cohort (aka the default cohort) is first, followed by named
cohorts in order they are created.  Named cohorts are emitted as inline
functions, so early-return can be used.

This allows tags like k8s:subfield or k8s:item to generate cohorts
(named after the field's JSON name or the selector string,
respectively).

Subsequent commits will add support to those tags.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-10-01 19:45:21 +00:00
Tim Hockin 5d067af5e1 Prefactor: Fix some bad tests 2025-10-01 19:00:30 +00:00
Tim Hockin 7b93851395 Fix ReplicationControl double validation 2025-10-01 18:37:13 +00:00
Tim Hockin 89b975c6d3 Add comments 2025-10-01 06:42:41 +00:00
Tim HockinandYongrui Lin 199c9ac77a Remove ExpectInvalid()
All use cases are converted.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-10-01 06:42:41 +00:00
Tim HockinandYongrui Lin 8b08c8e59c Remove ExpectRegexpsByPath()
All use cases are converted.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-10-01 06:42:39 +00:00
Tim Hockin b922fd9db4 Make ErrorMatcher more strict about multi-match
As discussed in a proposed PR, multi-match makes sense when origin is
specified and matched, but otherwise it feels "loose" to multi-match.
And in fact it was hiding bugs in declarative validation tests and real
bugs (in subsequent commits) in implementation.

Now we allow multi-match if and only if:
  1) ErrorMatcher.ByOrigin() was specified
  2) The origin string is not empty
  3) The multiple "got" errors are not exactly identical

This last part is key -- if some change to DV codegens logic which
(errantly) calls the same function twice, we want to know about it.
2025-10-01 06:26:26 +00:00
Tim Hockin 054ab36c91 Fix field path for embedded fields in root types
Given the following:

```
type Struct type {
    TypeMeta int

    // +k8s:validateFalse="embedded"
    OtherStruct
}
```

What should the field-path be for the error?

or:

```
type Struct type {
    TypeMeta int

    OtherStruct // embedded
}

// +k8s:validateFalse="otherstruct"
type OtherStruct {
  I int
}
```

Conclusion: If we find an embedded field, we look for a nil fldPath, and
if so we set it to the type name.  Embedded values in root types (e.g.
above `spec`) should not be a things we actually do.

Implementation: Add `safe.Value(*T, func() *T)`
2025-10-01 06:22:06 +00:00
Tim Hockin c4d8c9b544 Re-disable listmap uniqueness (for now) 2025-09-09 22:12:25 +00:00
Tim Hockin 6fb020b194 Temporary: Re-enable listmap uniqueness checks
Testing this exposed a bug -- we do this check for both the field AND
the type when the field is a typedef.  Now fixed to only emit a
uniqueness check at the site where listmap keys are defined.
2025-09-09 22:11:47 +00:00
Tim HockinandYongrui Lin 3e15dded75 Pass equiv func to subfield, like item and eachVal
Update comments to be similar.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-09-09 22:07:49 +00:00
Tim HockinandYongrui Lin 0d5e374da9 Don't ratchet-check inside type functions
The caller has to do that.  Add a big comment.

Co-Authored-by: Yongrui Lin <yongrlin@outlook.com>
2025-09-09 22:06:18 +00:00
Tim HockinandYongrui Lin b6fdbe4a6a Emit ratchet check for fields with a type func
This could go inside the type func but then we end up with a double
ratchet sometimes.

Co-Authored-By: Yongrui Lin <yongrlin@outlook.com>
2025-09-09 21:58:55 +00:00
Tim Hockin 2d71a0d72b Refactor ItemTagValidator.GetValidations a bit 2025-09-09 16:55:12 +00:00
Tim Hockin 8f679c7402 Make item validation just use a TagValidator
No need for Field/TypeValidators.
2025-09-09 16:55:12 +00:00
Tim Hockin c047bdf58f Sort item criteria to match listmap key order 2025-09-09 16:55:12 +00:00
Tim Hockin 26283acfb1 Clarify that union has field- or item-members 2025-09-09 16:55:12 +00:00
Tim Hockin 3f068c7c64 Parse path early, clean up getDisplayFields()
The path part of a list-item's fieldName is redundant with the path key
in the map.  I don't love parsing paths, but it seems less awful that
overloading Context.Member.
2025-09-09 16:55:12 +00:00
Tim Hockin 77c1ac6320 Improve error reporting in item tag 2025-09-09 16:55:12 +00:00
Tim Hockin 1f61a8b814 Rename "fields" to "members"
It is more inclusive of list-item unions.
2025-09-09 16:55:11 +00:00
Tim Hockin 1417e56dff Break processFieldMemberValidations into 2 funcs
It's a little repetitive but I think it is easier to follow.
2025-09-09 16:55:11 +00:00
Tim Hockin 023c026d6f Add ListSelector in validation Context
We already have "Member" which is a way to "forward" information from
stem validators (each, subfield, item) to leaf validators.  That works
for fields in a struct but not items in a list.

Now we have the list selection criteria.

This simplifies union a bit and gets rid of the need to regex parse the
field path.
2025-09-09 16:55:11 +00:00
Tim Hockin 3e2e2195ed Unions: replace [2]string with a type 2025-09-09 16:55:11 +00:00
Tim Hockin 348d576c9f validation: Use JSON names in paths
This ensures that things that parse paths (e.g. to extract the leaf
part) get API-style names.
2025-09-09 16:15:17 +00:00
Tim Hockin f51d57042e Update Context comments and fix some usage
Audit all Context creation sites and update the comments to better
describe.

Pass the raw type in Context, not the `NativeType()`.
2025-09-09 16:14:06 +00:00
Tim Hockin 9e71a07abf Minor validator name-string fix 2025-09-09 16:13:25 +00:00
Tim Hockin fd3fcfe121 Add comments to generated code
This makes the output longer, but I think it might have helped catch
recent bugs that SHOULD have been caught by inspection.
2025-09-09 16:12:59 +00:00
Tim Hockin 73cf278883 Mark some aliases as deprecated 2025-09-03 08:24:48 -07:00
Tim Hockin 5f4b4a6824 Bump gengo 2025-09-03 08:24:17 -07:00
Tim Hockin f971ad76af Only pass type args when needed in enum init 2025-09-02 16:22:06 -04:00
Tim Hockin 5af2b732be Change KYAML gate to on-by-default 2025-08-27 17:36:02 -07:00
Tim Hockin d176808d3a Rename hack/*-yamlfmt to -owners-fmt 2025-07-24 12:12:59 -07:00
Tim Hockin 7adcd21148 Replace cmd/yamlfmt with k-sigs/yaml/yamlfmt
And run it.
2025-07-24 12:11:04 -07:00
Tim Hockin 2cb955d8cc Add KYAML support to kubectl
KYAML is a strict subset of YAML, which is sort of halfway between YAML
and JSON.  It has the following properties:
* Does not depend on whitespace (easier to text-patch and template).
* Always quotes value strings (no ambiguity aroud things like "no").
* Allows quoted keys, but does not require them, and only quotes them if
  they are not obviously safe (e.g. "no" would always be quoted).
* Always uses {} for structs and maps (no more obscure errors about
  mapping values).
* Always uses [] for lists (no more trying to figure out if a dash
  changes the meaning).
* When printing, it includes a header which makes it clear this is YAML
  and not ill-formed JSON.
* Allows trailing commas
* Allows comments,
* Tries to economize on vertical space by "cuddling" some kinds of
  brackets together.
* Retains comments.

Examples:

A struct:

```yaml
metadata: {
  creationTimestamp: "2024-12-11T00:10:11Z",
  labels: {
    app: "hostnames",
  },
  name: "hostnames",
  namespace: "default",
  resourceVersion: "15231643",
  uid: "f64dbcba-9c58-40b0-bbe7-70495efb5202",
}
```

A list of primitves:

```yaml
ipFamilies: [
  "IPv4",
  "IPv6",
]
```

A list of structs:

```yaml
ports: [{
  port: 80,
  protocol: "TCP",
  targetPort: 80,
}, {
  port: 443,
  protocol: "TCP",
  targetPort: 443,
}]
```

A multi-document stream:

```yaml
---
{
  foo: "bar",
}
---
{
  qux: "zrb",
}
```
2025-07-24 11:47:03 -07:00
Tim Hockin 8182a27f3b Re-vendor sigs.k8s.io/yaml @ v1.6.0 2025-07-24 11:46:03 -07:00
Tim Hockin e68d601344 Don't panic in case of an unknown API error code 2025-06-19 10:11:17 +09:00
Tim Hockin 4ca91a0305 WIP: Fix tests
Notes:
* For types that define String() - should we prefer that or JSON?
* metav1.Time has a MarshalJSON() and inhereits a String() and they are
  different
* Since validation runs on internal types, we still get some GoNames
  instead of goNames.
2025-06-19 10:11:17 +09:00
Tim Hockin 2b2c9adef3 Nicer value rendering in API errors
Today, if the value passed is a struct, map, or list, we get Go's vative
rendering which is clunky.

This uses JSON (could be kyaml when that is ready) instead.

I hear it already: "But JSON is slow!".  I benchmarked it -- for an
simple int or string field, JSON is only a little slower (~20%) than a
type assertion, but it IS slower, so I left the type assertion in.
Remember that this is only called when an API error has occurred.

The type assertions do not handle typedefs-to{string, int64, etc} so
those will fall back on JSON.  Almost all of our errors go thru standard
functions which demand string or int64 anyway, so mostly pointless.

I also benchmarked using reflect to check `CanInt()` and that is almost
exactly as fast as type-switch but handles more cases, so we COULD
switch to that instead, if we wanted. I thought it wasn't worth the
complexity.

JSON is really there to handle composite types.
2025-06-19 10:11:16 +09:00
Tim Hockin 384240b4d9 Use "Invalid" errors rather than "TypeInvalid" 2025-06-14 17:50:38 -07:00
Tim Hockin 769d71b304 Remove unused Expect methods in testscheme 2025-06-10 20:15:07 +00:00
Tim Hockin 28e973c044 Better formatting of matcher errors 2025-06-10 18:08:45 +00:00
Tim Hockin 2c4c3037b6 Fix field path for embedded fields in root types
Given the following:

```
type Struct type {
    TypeMeta int

    // +k8s:validateFalse="embedded"
    OtherStruct
}
```

What should the field-path be for the error?

or:

```
type Struct type {
    TypeMeta int

    OtherStruct // embedded
}

// +k8s:validateFalse="otherstruct"
type OtherStruct {
  I int
}
```

Conclusion: If we find an embedded field, we look for a nil fldPath, and
if so we set it to the type name.  Embedded values in root types (e.g.
above `spec`) should not be a things we actually do.

Implementation: Add `safe.Value(*T, func() *T)`
2025-06-05 11:17:47 -04:00
Tim Hockin cbbfabe7fe Support embedded fields properly 2025-06-05 10:50:14 -04:00
Tim Hockin cfe7d04243 Account consumed newlines properly in YAML decoder
This isn't a significant functional bug in that the StreamDecoder is
still offset to account for it (which is why we never saw it before).
But it's a trap for future use-cases.

Also move one LOC to match a parallel-shaped block above.
2025-05-11 16:22:52 -07:00
Tim Hockin 641834d030 Fix broken recursion
Test case:

```
type T1 struct {
    T2 T2 `json:"t2"`
    T3 T3 `json:"t3"`
}

type T2 struct {
    ST1 []T1 `json:"st1"`
}

// +k8s:validateFalse
type T3 struct {
    T4 T4 `json:"t4"`
}

type T4 struct {
    ST3 []T3 `json:"st3"`
}
```

Today, we do not detect T2 as having validations because T1 was
incomplete because T3 had not been finished.  This is hard to resolve in
the current model where we look at `hasValidations()` during discovery.
We don't have the information we need at that point.

This commit changes the approach.

Instead of saying:
```
if hasValidation(slice-element-type) {
    save the eachVal(type-function)
}
```

...we ALWAYS track the `eachVal(type-function)`, but in a different
field.  Then at emit time we can check `hasValidations` and emit the
code to call the eachVal only if actually needed.

Additionally, `hasValidations` is changed to not cache answers. This was
the source of part of the original problem and, on measuring, the cache
added little performance, so I nixed it.  As we add coverage across all
of k/k we might want to bring it back, but now we have good tests.

I converted `hasValidations` back to a method which is only accessible
in emit phase, not discovery. Not more temptation.

Lastly, I added test cases.
2025-05-08 17:22:38 -07:00
Tim Hockin 97e64e80c3 Fix typo in comment 2025-05-06 12:43:17 -07:00
Tim Hockin 68f0204e3e Better logs and debugging 2025-05-06 12:40:58 -07:00
Tim Hockin 8a571f7fb8 Rename discover() to discoverType() 2025-05-06 12:40:57 -07:00