Skip to content

Commit cab4728

Browse files
feat: add immutable marker (#660)
* feat: add immutable marker * refactor: changed switch to if/else * add: immutable marker documentation
1 parent 7070c6a commit cab4728

File tree

5 files changed

+96
-2
lines changed

5 files changed

+96
-2
lines changed

pkg/simpleschema/markers.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@ const (
5555
MarkerTypeValidation MarkerType = "validation"
5656
// MarkerTypeEnum represents the `enum` marker.
5757
MarkerTypeEnum MarkerType = "enum"
58+
// MarkerTypeImmutable represents the `immutable` marker.
59+
MarkerTypeImmutable MarkerType = "immutable"
5860
)
5961

6062
func markerTypeFromString(s string) (MarkerType, error) {
6163
switch MarkerType(s) {
6264
case MarkerTypeRequired, MarkerTypeDefault, MarkerTypeDescription,
63-
MarkerTypeMinimum, MarkerTypeMaximum, MarkerTypeValidation, MarkerTypeEnum:
65+
MarkerTypeMinimum, MarkerTypeMaximum, MarkerTypeValidation, MarkerTypeEnum, MarkerTypeImmutable:
6466
return MarkerType(s), nil
6567
default:
6668
return "", fmt.Errorf("unknown marker type: %s", s)

pkg/simpleschema/markers_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,15 @@ func TestParseMarkers(t *testing.T) {
125125
},
126126
wantErr: false,
127127
},
128+
{
129+
name: "immutable marker with other markers",
130+
input: "immutable=true required=false",
131+
want: []*Marker{
132+
{MarkerType: MarkerTypeImmutable, Key: "immutable", Value: "true"},
133+
{MarkerType: MarkerTypeRequired, Key: "required", Value: "false"},
134+
},
135+
wantErr: false,
136+
},
128137
}
129138

130139
for _, tt := range tests {

pkg/simpleschema/transform.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,20 @@ func (tf *transformer) applyMarkers(schema *extv1.JSONSchemaProps, markers []*Ma
275275
},
276276
}
277277
schema.XValidations = validation
278+
case MarkerTypeImmutable:
279+
isImmutable, err := strconv.ParseBool(marker.Value)
280+
if err != nil {
281+
return fmt.Errorf("failed to parse immutable marker value: %w", err)
282+
}
283+
if isImmutable {
284+
immutableValidation := []extv1.ValidationRule{
285+
{
286+
Rule: "self == oldSelf",
287+
Message: "field is immutable",
288+
},
289+
}
290+
schema.XValidations = append(schema.XValidations, immutableValidation...)
291+
}
278292
case MarkerTypeEnum:
279293
var enumJSONValues []extv1.JSON
280294

pkg/simpleschema/transform_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,73 @@ func TestBuildOpenAPISchema(t *testing.T) {
485485
want: nil,
486486
wantErr: true,
487487
},
488+
{
489+
name: "Simple immutable field",
490+
obj: map[string]interface{}{
491+
"id": "string | immutable=true",
492+
},
493+
want: &extv1.JSONSchemaProps{
494+
Type: "object",
495+
Properties: map[string]extv1.JSONSchemaProps{
496+
"id": {
497+
Type: "string",
498+
XValidations: []extv1.ValidationRule{
499+
{
500+
Rule: "self == oldSelf",
501+
Message: "field is immutable",
502+
},
503+
},
504+
},
505+
},
506+
},
507+
wantErr: false,
508+
},
509+
{
510+
name: "Simple immutable field with false value",
511+
obj: map[string]interface{}{
512+
"name": "string | immutable=false",
513+
},
514+
want: &extv1.JSONSchemaProps{
515+
Type: "object",
516+
Properties: map[string]extv1.JSONSchemaProps{
517+
"name": {
518+
Type: "string",
519+
},
520+
},
521+
},
522+
wantErr: false,
523+
},
524+
{
525+
name: "Immutable with other markers",
526+
obj: map[string]interface{}{
527+
"resourceId": `string | required=true immutable=true description="Unique resource identifier"`,
528+
},
529+
want: &extv1.JSONSchemaProps{
530+
Type: "object",
531+
Required: []string{"resourceId"},
532+
Properties: map[string]extv1.JSONSchemaProps{
533+
"resourceId": {
534+
Type: "string",
535+
Description: "Unique resource identifier",
536+
XValidations: []extv1.ValidationRule{
537+
{
538+
Rule: "self == oldSelf",
539+
Message: "field is immutable",
540+
},
541+
},
542+
},
543+
},
544+
},
545+
wantErr: false,
546+
},
547+
{
548+
name: "Invalid immutable value",
549+
obj: map[string]interface{}{
550+
"id": "string | immutable=invalid",
551+
},
552+
want: nil,
553+
wantErr: true,
554+
},
488555
{
489556
name: "Custom simple type (required)",
490557
obj: map[string]interface{}{

website/docs/docs/concepts/10-simple-schema.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ spec:
1919
kind: WebApplication
2020
spec:
2121
# Basic types
22-
name: string | required=true description="My Name"
22+
name: string | required=true immutable=true description="My Name"
2323
replicas: integer | default=1 minimum=1 maximum=100
2424
image: string | required=true
2525

@@ -193,13 +193,15 @@ mode: string | enum="debug,info,warn,error" default="info"
193193
- `enum="value1,value2"`: Allowed values
194194
- `minimum=value`: Minimum value for numbers
195195
- `maximum=value`: Maximum value for numbers
196+
- `immutable=true`: Field cannot be changed after creation
196197

197198
Multiple markers can be combined using the `|` separator.
198199

199200
For example:
200201

201202
```yaml
202203
name: string | required=true default="app" description="Application name"
204+
id: string | required=true immutable=true description="Unique identifier"
203205
replicas: integer | default=3 minimum=1 maximum=10
204206
price: float | minimum=0.01 maximum=999.99
205207
mode: string | enum="debug,info,warn,error" default="info"

0 commit comments

Comments
 (0)