From fbbe7fb2565e93e401b5bb554f592e24abf7449f Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 24 Jun 2026 09:13:37 +0200 Subject: [PATCH 1/4] Add min/maxItems annotation support --- CHANGELOG.md | 1 + lib/compile/csdl.js | 2 +- lib/compile/csdl2openapi.js | 4 ++ test/lib/compile/data/annotations.json | 9 ++++ .../compile/data/annotations.openapi3.json | 41 +++++++++++++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d34de98..733e901 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added +- Support `@Validation.MinItems` and `@Validation.MaxItems` annotations on collection-typed properties and action parameters to emit `minItems`/`maxItems` in the generated OpenAPI schema ### Changed ### Deprecated ### Removed diff --git a/lib/compile/csdl.js b/lib/compile/csdl.js index 0b113d5..cb02fcf 100644 --- a/lib/compile/csdl.js +++ b/lib/compile/csdl.js @@ -14,7 +14,7 @@ const CDS_TERMS = Object.freeze({ Core: ['AcceptableMediaTypes', 'Computed', 'ComputedDefaultValue', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription', 'MediaType', 'OptionalParameter', 'Permissions', 'SchemaVersion'], JSON: ['Schema'], - Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern'] + Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'MaxItems', 'Minimum', 'MinItems', 'Pattern'] }) /** diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 6c1ae31..1b0e91f 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -2599,6 +2599,10 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot type: 'array', items: s }; + if (element[meta.voc.Validation.MinItems] != undefined) + s.minItems = element[meta.voc.Validation.MinItems]; + if (element[meta.voc.Validation.MaxItems] != undefined) + s.maxItems = element[meta.voc.Validation.MaxItems]; } const description = forParameter ? undefined : (element[meta.voc.Core.LongDescription] || element[meta.voc.Core.Description]); diff --git a/test/lib/compile/data/annotations.json b/test/lib/compile/data/annotations.json index 684dc2c..2dcdc41 100644 --- a/test/lib/compile/data/annotations.json +++ b/test/lib/compile/data/annotations.json @@ -90,6 +90,15 @@ "$Type": "Edm.Decimal", "$Scale": 5 }, + "MinMaxItems": { + "$Collection": true, + "@Validation.MinItems": 1, + "@Validation.MaxItems": 5 + }, + "MinItems": { + "$Collection": true, + "@Validation.MinItems": 2 + }, "Example": { "$Nullable": true, "@Core.Example": { diff --git a/test/lib/compile/data/annotations.openapi3.json b/test/lib/compile/data/annotations.openapi3.json index 9077d79..ac36152 100644 --- a/test/lib/compile/data/annotations.openapi3.json +++ b/test/lib/compile/data/annotations.openapi3.json @@ -113,6 +113,10 @@ "Minimum desc", "MinimumExt", "MinimumExt desc", + "MinMaxItems", + "MinMaxItems desc", + "MinItems", + "MinItems desc", "ExampleExt", "ExampleExt desc", "Computed", @@ -149,6 +153,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -250,6 +256,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -566,6 +574,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -867,6 +877,10 @@ "Minimum desc", "MinimumExt", "MinimumExt desc", + "MinMaxItems", + "MinMaxItems desc", + "MinItems", + "MinItems desc", "Example", "Example desc", "ExampleExt", @@ -905,6 +919,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -1006,6 +1022,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -1113,6 +1131,10 @@ "Minimum desc", "MinimumExt", "MinimumExt desc", + "MinMaxItems", + "MinMaxItems desc", + "MinItems", + "MinItems desc", "Example", "Example desc", "ExampleExt", @@ -1151,6 +1173,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -1252,6 +1276,8 @@ "MaximumExt", "Minimum", "MinimumExt", + "MinMaxItems", + "MinItems", "Example", "ExampleExt", "Computed", @@ -2351,6 +2377,21 @@ "x-sap-scale": 5, "exclusiveMinimum": true }, + "MinMaxItems": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "maxItems": 5 + }, + "MinItems": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 2 + }, "Example": { "type": "string", "nullable": true, From 6eb6b2e6839670697a05ccb8061d0e408ad6bfa0 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 24 Jun 2026 09:40:45 +0200 Subject: [PATCH 2/4] Strict undefined check --- lib/compile/csdl2openapi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 1b0e91f..ef47361 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -2574,7 +2574,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot } } - if (element[meta.voc.Validation.Maximum] != undefined) { + if (element[meta.voc.Validation.Maximum] !== undefined) { if (s.$ref) s = { allOf: [s] }; if (isAnyOfSchema(s) && isNumberSchema(s.anyOf[0])) { s.anyOf[0].maximum = element[meta.voc.Validation.Maximum]; @@ -2584,7 +2584,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot if (element[meta.voc.Validation.Maximum + meta.voc.Validation.Exclusive]) s.exclusiveMaximum = true; } - if (element[meta.voc.Validation.Minimum] != undefined) { + if (element[meta.voc.Validation.Minimum] !== undefined) { if (s.$ref) s = { allOf: [s] }; if (isAnyOfSchema(s) && isNumberSchema(s.anyOf[0])) { s.anyOf[0].minimum = element[meta.voc.Validation.Minimum]; From bfd2deb63faf880e7597b2af6e3a59015d4a9877 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 24 Jun 2026 09:46:33 +0200 Subject: [PATCH 3/4] Fix types --- lib/compile/types.d.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compile/types.d.ts b/lib/compile/types.d.ts index b774ade..4f70f7e 100644 --- a/lib/compile/types.d.ts +++ b/lib/compile/types.d.ts @@ -23,7 +23,9 @@ type BooleanSchema = { type ArraySchema = { type: 'array', - items: Schema + items: Schema, + minItems?: number, + maxItems?: number } type ObjectSchema = { From 57707afcc072d8fa7644edecb97e49f35369e042 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 24 Jun 2026 09:47:15 +0200 Subject: [PATCH 4/4] Strict undef check --- lib/compile/csdl2openapi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index ef47361..f57dd08 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -2599,9 +2599,9 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot type: 'array', items: s }; - if (element[meta.voc.Validation.MinItems] != undefined) + if (element[meta.voc.Validation.MinItems] !== undefined) s.minItems = element[meta.voc.Validation.MinItems]; - if (element[meta.voc.Validation.MaxItems] != undefined) + if (element[meta.voc.Validation.MaxItems] !== undefined) s.maxItems = element[meta.voc.Validation.MaxItems]; }