Skip to content

Commit

Permalink
Update default templates with default and validation markers (#49)
Browse files Browse the repository at this point in the history
This updates the default asciidoc and markdown templates
with `Default` and `Validation` markers.

Not supported in this change:
- merging enum markers: when both a type and the alias type has enum validation or a field and its type has enum validation the current code does not merge them as in the generated CRD
- XValidation marker: removed due to being long and difficult to read

---------
Co-authored-by: Thibault Richard <[email protected]>
  • Loading branch information
tenstad authored Feb 14, 2024
1 parent 9a3105b commit cf959ab
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 107 deletions.
53 changes: 53 additions & 0 deletions processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ func Process(config *config.Config) ([]types.GroupVersionDetails, error) {
}

p.types.InlineTypes(p.propagateReference)
p.types.PropagateMarkers()
p.parseMarkers()

// collect references between types
for typeName, refs := range p.references {
Expand Down Expand Up @@ -494,3 +496,54 @@ func mkRegistry() (*markers.Registry, error) {

return registry, nil
}

func parseMarkers(markers markers.MarkerValues) (string, []string) {
defaultValue := ""
validation := []string{}

markerNames := make([]string, 0, len(markers))
for name := range markers {
markerNames = append(markerNames, name)
}
sort.Strings(markerNames)

for _, name := range markerNames {
value := markers[name][len(markers[name])-1]

if strings.HasPrefix(name, "kubebuilder:validation:") {
name := strings.TrimPrefix(name, "kubebuilder:validation:")

switch name {
case "Pattern":
value = fmt.Sprintf("`%s`", value)
// FIXME: XValidation currently removed due to being long and difficult to read.
// E.g. "XValidation: {self.page < 200 Please start a new book.}"
case "XValidation":
continue
}
validation = append(validation, fmt.Sprintf("%s: %v", name, value))
}

if name == "kubebuilder:default" {
if value, ok := value.(crdmarkers.Default); ok {
defaultValue = fmt.Sprintf("%v", value.Value)
if strings.HasPrefix(defaultValue, "map[") {
defaultValue = strings.TrimPrefix(defaultValue, "map[")
defaultValue = strings.TrimSuffix(defaultValue, "]")
defaultValue = fmt.Sprintf("{ %s }", defaultValue)
}
}
}
}

return defaultValue, validation
}

func (p *processor) parseMarkers() {
for _, t := range p.types {
t.Default, t.Validation = parseMarkers(t.Markers)
for _, f := range t.Fields {
f.Default, f.Validation = parseMarkers(f.Markers)
}
}
}
8 changes: 8 additions & 0 deletions renderer/markdown.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ func (m *MarkdownRenderer) ToFuncMap() template.FuncMap {
"ShouldRenderType": m.ShouldRenderType,
"TypeID": m.TypeID,
"RenderFieldDoc": m.RenderFieldDoc,
"RenderDefault": m.RenderDefault,
}
}

Expand Down Expand Up @@ -138,6 +139,13 @@ func (m *MarkdownRenderer) RenderExternalLink(link, text string) string {
return fmt.Sprintf("[%s](%s)", text, link)
}

func (m *MarkdownRenderer) RenderDefault(text string) string {
return strings.NewReplacer(
"{", "\\{",
"}", "\\}",
).Replace(text)
}

func (m *MarkdownRenderer) RenderGVLink(gv types.GroupVersionDetails) string {
return m.RenderLocalLink(gv.GroupVersionString())
}
Expand Down
18 changes: 13 additions & 5 deletions templates/asciidoctor/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@

{{ $type.Doc }}

{{ if $type.Validation -}}
.Validation:
{{- range $type.Validation }}
- {{ . }}
{{- end }}
{{- end }}

{{ if $type.References -}}
.Appears In:
****
Expand All @@ -19,16 +26,17 @@
{{- end }}

{{ if $type.Members -}}
[cols="25a,75a", options="header"]
[cols="20a,50a,15a,15a", options="header"]
|===
| Field | Description
| Field | Description | Default | Validation
{{ if $type.GVK -}}
| *`apiVersion`* __string__ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}`
| *`kind`* __string__ | `{{ $type.GVK.Kind }}`
| *`apiVersion`* __string__ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | |
| *`kind`* __string__ | `{{ $type.GVK.Kind }}` | |
{{ end -}}

{{ range $type.Members -}}
| *`{{ .Name }}`* __{{ asciidocRenderType .Type }}__ | {{ template "type_members" . }}
| *`{{ .Name }}`* __{{ asciidocRenderType .Type }}__ | {{ template "type_members" . }} | {{ .Default }} | {{ range .Validation -}} {{ . }} +
{{ end }}
{{ end -}}
|===
{{ end -}}
Expand Down
17 changes: 12 additions & 5 deletions templates/markdown/type.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

{{ $type.Doc }}

{{ if $type.Validation -}}
_Validation:_
{{- range $type.Validation }}
- {{ . }}
{{- end }}
{{- end }}

{{ if $type.References -}}
_Appears in:_
{{- range $type.SortedReferences }}
Expand All @@ -16,15 +23,15 @@ _Appears in:_
{{- end }}

{{ if $type.Members -}}
| Field | Description |
| --- | --- |
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
{{ if $type.GVK -}}
| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}`
| `kind` _string_ | `{{ $type.GVK.Kind }}`
| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | | |
| `kind` _string_ | `{{ $type.GVK.Kind }}` | | |
{{ end -}}

{{ range $type.Members -}}
| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} |
| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} | {{ markdownRenderDefault .Default }} | {{ range .Validation -}} {{ . }} <br />{{ end }} |
{{ end -}}

{{ end -}}
Expand Down
23 changes: 21 additions & 2 deletions test/api/v1/guestbook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type EmbeddedX struct {
// Underlying tests that Underlying1's underlying type is Underlying2 instead of string.
// +kubebuilder:object:root=true
type Underlying struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:default="b"
A Underlying1 `json:"a,omitempty"`
}
Expand All @@ -62,50 +65,65 @@ type Underlying struct {
type Underlying1 Underlying2

// Underlying2 is a string alias
// +kubebuilder:validation:MaxLength=10
type Underlying2 string

// NOTE: Rating is placed here to ensure that it is parsed as a standalone type
// before it is parsed as a struct field.

// Rating is the rating provided by a guest.
// +kubebuilder:validation:Maximum=4
// +kubebuilder:validation:Minimum=1
// +kubebuilder:validation:Maximum=5
type Rating int

// GuestbookSpec defines the desired state of Guestbook.
// +kubebuilder:validation:XValidation:rule="self.page < 200", message="Please start a new book."
type GuestbookSpec struct {
// Page indicates the page number
// +kubebuilder:default=1
Page *int `json:"page,omitempty"`
// +kubebuilder:example=3
Page *PositiveInt `json:"page,omitempty"`
// Entries contain guest book entries for the page
Entries []GuestbookEntry `json:"entries,omitempty"`
// Selector selects something
Selector metav1.LabelSelector `json:"selector,omitempty"`
// Headers contains a list of header items to include in the page
// +kubebuilder:validation:MaxItems=10
// +kubebuilder:validation:UniqueItems=true
Headers []GuestbookHeader `json:"headers,omitempty"`
// CertificateRef is a reference to a secret containing a certificate
CertificateRef gwapiv1b1.SecretObjectReference `json:"certificateRef"`
}

// +kubebuilder:validation:Minimum=1
type PositiveInt int

// GuestbookEntry defines an entry in a guest book.
type GuestbookEntry struct {
// Name of the guest (pipe | should be escaped)
// +kubebuilder:validation:MaxLength=80
Name string `json:"name,omitempty"`
// Time of entry
Time metav1.Time `json:"time,omitempty"`
// Comment by guest. This can be a multi-line comment.
//
// Just like this one.
// +kubebuilder:validation:Pattern=`[a-z0-9]`
Comment string `json:"comment,omitempty"`
// Rating provided by the guest
Rating Rating `json:"rating,omitempty"`
}

// GuestbookStatus defines the observed state of Guestbook.
type GuestbookStatus struct {
Status string `json:"status"`
// +kubebuilder:validation:Enum={OK, Error}
Status Status `json:"status"`
}

// +kubebuilder:validation:Enum={OK, Unknown, Error}
type Status string

// GuestbookHeaders are strings to include at the top of a page.
type GuestbookHeader string

Expand All @@ -117,6 +135,7 @@ type Guestbook struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// +kubebuilder:default={page: 1}
Spec GuestbookSpec `json:"spec,omitempty"`
Status GuestbookStatus `json:"status,omitempty"`
}
Expand Down
27 changes: 26 additions & 1 deletion test/api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cf959ab

Please sign in to comment.