Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,14 @@ func mkRegistry(customMarkers []config.Marker) (*markers.Registry, error) {
}
}

// Register k8s:* markers - sig apimachinery plans to unify CRD and native types on these.
if err := registry.Define("k8s:required", markers.DescribesField, struct{}{}); err != nil {
return nil, err
}
if err := registry.Define("k8s:optional", markers.DescribesField, struct{}{}); err != nil {
return nil, err
}

for _, marker := range customMarkers {
t := markers.DescribesField
switch marker.Target {
Expand Down Expand Up @@ -554,6 +562,17 @@ func parseMarkers(markers markers.MarkerValues) (string, []string) {
validation = append(validation, fmt.Sprintf("%s: %v", name, value))
}

// Handle standalone +required and +k8s:required marker
// This is equivalent to +kubebuilder:validation:Required
if name == "required" || name == "k8s:required" {
validation = append(validation, "Required: {}")
}
// Handle standalone +optional and +k8s:optional marker
// This is equivalent to +kubebuilder:validation:Optional
if name == "optional" || name == "k8s:optional" {
validation = append(validation, "Optional: {}")
}

if name == "kubebuilder:default" {
if value, ok := value.(crdmarkers.Default); ok {
defaultValue = fmt.Sprintf("%v", value.Value)
Expand Down
11 changes: 9 additions & 2 deletions renderer/asciidoctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (adr *AsciidoctorRenderer) TemplateValue(key string) string {
func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string {
// Escape the pipe character, which has special meaning for asciidoc as a way to format tables,
// so that including | in a comment does not result in wonky tables.
out := strings.ReplaceAll(text, "|", "\\|")
out := escapePipe(text)

// Trim any leading and trailing whitespace from each line.
lines := strings.Split(out, "\n")
Expand All @@ -169,6 +169,7 @@ func (adr *AsciidoctorRenderer) RenderFieldDoc(text string) string {

func (adr *AsciidoctorRenderer) RenderValidation(text string) string {
renderedText := escapeFirstAsterixInEachPair(text)
renderedText = escapePipe(renderedText)
return escapeCurlyBraces(renderedText)
}

Expand All @@ -190,9 +191,15 @@ func escapeFirstAsterixInEachPair(text string) string {
return text
}

// escapePipe ensures sufficient escapes are added to pipe characters, so they are not mistaken
// for asciidoctor table formatting.
func escapePipe(text string) string {
return strings.ReplaceAll(text, "|", "\\|")
}

// escapeCurlyBraces ensures sufficient escapes are added to curly braces, so they are not mistaken
// for asciidoctor id attributes.
func escapeCurlyBraces(text string) string {
// Per asciidoctor docs, only the leading curly brace needs to be escaped.
return strings.Replace(text, "{", "\\{", -1)
return strings.ReplaceAll(text, "{", "\\{")
}
5 changes: 5 additions & 0 deletions renderer/asciidoctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@ func Test_escapeCurlyBraces(t *testing.T) {
assert.Equal(t, "[a-fA-F0-9]\\{64}", escapeCurlyBraces("[a-fA-F0-9]{64}"))
assert.Equal(t, "[a-fA-F0-9]\\\\{64\\}", escapeCurlyBraces("[a-fA-F0-9]\\{64\\}"))
}

func Test_escapePipe(t *testing.T) {
assert.Equal(t, "[0-9]", escapePipe("[0-9]"))
assert.Equal(t, `[0-9]*\|\s`, escapePipe(`[0-9]*|\s`))
}
14 changes: 14 additions & 0 deletions test/api/v1/guestbook_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ type PositiveInt int
// GuestbookEntry defines an entry in a guest book.
type GuestbookEntry struct {
// Name of the guest (pipe | should be escaped)
// +kubebuilder:validation:Required
// +kubebuilder:validation:MaxLength=80
// +kubebuilder:validation:Pattern=`0*[a-z0-9]*[a-z]*[0-9]`
Name string `json:"name,omitempty"`
Expand All @@ -146,6 +147,19 @@ type GuestbookEntry struct {
Comment string `json:"comment,omitempty"`
// Rating provided by the guest
Rating Rating `json:"rating,omitempty"`

// Email is the email address of the guest (required field using +required marker)
// +required
Email string `json:"email"`
// Location is the location of the guest (required field using +k8s:required marker)
// +k8s:required
Location string `json:"location"`
// Phone is the phone number of the guest (optional field using +optional marker)
// +optional
Phone string `json:"phone"`
// Company is the company of the guest (optional field using +k8s:optional marker)
// +k8s:optional
Company string `json:"company"`
}

// GuestbookStatus defines the observed state of Guestbook.
Expand Down
11 changes: 10 additions & 1 deletion test/expected.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ GuestbookEntry defines an entry in a guest book.
| Field | Description | Default | Validation
| *`name`* __string__ | Name of the guest (pipe \| should be escaped) + | | MaxLength: 80 +
Pattern: `0\*[a-z0-9]*[a-z]*[0-9]` +
Required: \{} +

| *`tags`* __string array__ | Tags of the entry. + | | items:Pattern: `[a-z]*` +

Expand All @@ -174,11 +175,19 @@ Now let's test a list: +

Another isolated comment. +

Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*|\s` +
Looks good? + | | Pattern: `0\*[a-z0-9]*[a-z]\*[0-9]*\|\s` +

| *`rating`* __xref:{anchor_prefix}-github-com-elastic-crd-ref-docs-api-v1-rating[$$Rating$$]__ | Rating provided by the guest + | | Maximum: 5 +
Minimum: 1 +

| *`email`* __string__ | Email is the email address of the guest (required field using +required marker) + | | Required: \{} +

| *`location`* __string__ | Location is the location of the guest (required field using +k8s:required marker) + | | Required: \{} +

| *`phone`* __string__ | Phone is the phone number of the guest (optional field using +optional marker) + | | Optional: \{} +

| *`company`* __string__ | Company is the company of the guest (optional field using +k8s:optional marker) + | | Optional: \{} +

|===


Expand Down
6 changes: 5 additions & 1 deletion test/expected.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,15 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br /> |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
| `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5 <br />Minimum: 1 <br /> |
| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\} <br /> |
| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\} <br /> |
| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\} <br /> |
| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\} <br /> |


#### GuestbookHeader
Expand Down
6 changes: 5 additions & 1 deletion test/hide.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,15 @@ _Appears in:_

| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br /> |
| `name` _string_ | Name of the guest (pipe \| should be escaped) | | MaxLength: 80 <br />Pattern: `0*[a-z0-9]*[a-z]*[0-9]` <br />Required: \{\} <br /> |
| `tags` _string array_ | Tags of the entry. | | items:Pattern: `[a-z]*` <br /> |
| `time` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#time-v1-meta)_ | Time of entry | | |
| `comment` _string_ | Comment by guest. This can be a multi-line comment.<br />Like this one.<br />Now let's test a list:<br />* a<br />* b<br />Another isolated comment.<br />Looks good? | | Pattern: `0*[a-z0-9]*[a-z]*[0-9]*\|\s` <br /> |
| `rating` _[Rating](#rating)_ | Rating provided by the guest | | Maximum: 5 <br />Minimum: 1 <br /> |
| `email` _string_ | Email is the email address of the guest (required field using +required marker) | | Required: \{\} <br /> |
| `location` _string_ | Location is the location of the guest (required field using +k8s:required marker) | | Required: \{\} <br /> |
| `phone` _string_ | Phone is the phone number of the guest (optional field using +optional marker) | | Optional: \{\} <br /> |
| `company` _string_ | Company is the company of the guest (optional field using +k8s:optional marker) | | Optional: \{\} <br /> |


#### GuestbookHeader
Expand Down