From a4e475219f639e495ab8f3f3464be09bb81b9523 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Fri, 14 Aug 2026 10:43:32 +0200 Subject: [PATCH 1/6] Voeg regel toe voor het teruggeven van objecten voor collection resources Deze suggestie kwam naar voren tijdens de brainstorm sessie over pagination. De regel is breder toepasbaar dan enkel pagination en bij meerdere API's is deze verandering toegepast nadat bleek dat er behoefte was aan metadata. Ook is dit voor linked data belangrijk zodat ook API's beter integreren met linked data. --- sections/designRules.md | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/sections/designRules.md b/sections/designRules.md index abddf71..385f1c8 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -192,6 +192,49 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f +
+

Return an object when retrieving items from a collection resource

+
+
Statement
+
+

An API MUST return an object which contains a field (its key MAY be named items) with its value an array of items from a collection resource. +

+
Rationale
+
+

Items in a collection resources can be retrieved all at once, or a subset thereof. + In both cases, wrapping the returned items in an object allows metadata to be attached to the response. + Examples of metadata are structured data to implement linked data concepts, or pagination data for performance purposes. +

If a collection resource directly returns an array of object information from the collection, such metadata can only be provided using HTTP headers. + While that is feasible, HTTP headers are generally more difficult to work with than content from a response body. +

Even if currently no metadata is attached to a response, that could be the case in the future. + Changing the response from an array to an object is a breaking change, hence it is future-proof to always return an object. +

+
+
How to test
+
+ Analyse all responses and check that the type is not an array. +
+
+
+

Hide irrelevant implementation details

From 4b4f9053089ca096cca836055740e080eaa4a77e Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Fri, 14 Aug 2026 10:53:12 +0200 Subject: [PATCH 2/6] Update designRules.md --- sections/designRules.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sections/designRules.md b/sections/designRules.md index 385f1c8..0456dd2 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -197,7 +197,8 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f
Statement
-

An API MUST return an object which contains a field (its key MAY be named items) with its value an array of items from a collection resource. +

An API MUST return an object for a GET request on a collection resource. + The object MUST contain a field (its key MAY be named items) with its value an array of items from a collection resource.

Rationale
@@ -210,8 +211,11 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f The following example shows a response from a collection resource containing all items -
HTTP/1.1 200 OK
-Content-Type: application/json{
+            
GET /organisations HTTP/1.1
+
HTTP/1.1 200 OK
+Content-Type: application/json
+   
+{
    "metadata": {
    },
   "items": [
@@ -224,13 +228,12 @@ Content-Type: application/json{
        "name": "Geonovum"
      }
   ]
-}
-
+}
How to test
- Analyse all responses and check that the type is not an array. + Analyse all responses for paths supporting a GET method and check that the type of response is an object.
From ca55863bd223dd81db23fd622a2cedb576d871b7 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Fri, 14 Aug 2026 10:55:09 +0200 Subject: [PATCH 3/6] Update designRules.md --- sections/designRules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/designRules.md b/sections/designRules.md index 0456dd2..b9de791 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -214,7 +214,7 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66fGET /organisations HTTP/1.1
HTTP/1.1 200 OK
 Content-Type: application/json
-   
+
 {
    "metadata": {
    },

From 81ae087cecd9b72de1542b56487f7a61c42bed83 Mon Sep 17 00:00:00 2001
From: Tim van der Lippe 
Date: Mon, 17 Aug 2026 15:20:56 +0200
Subject: [PATCH 4/6] Voeg linter regel toe

---
 .../return-objects/expected-output.txt        |   7 +
 linter/testcases/return-objects/openapi.json  | 147 ++++++++++++++++++
 media/linter.yaml                             |  11 ++
 sections/designRules.md                       |  13 +-
 4 files changed, 172 insertions(+), 6 deletions(-)
 create mode 100644 linter/testcases/return-objects/expected-output.txt
 create mode 100644 linter/testcases/return-objects/openapi.json

diff --git a/linter/testcases/return-objects/expected-output.txt b/linter/testcases/return-objects/expected-output.txt
new file mode 100644
index 0000000..d116466
--- /dev/null
+++ b/linter/testcases/return-objects/expected-output.txt
@@ -0,0 +1,7 @@
+
+/testcases/return-objects/openapi.json
+ 108:44  error  nlgov:always-return-objects  The content of all responses should be wrapped in an object  paths./incorrect.get.responses[200].content.application/json.schema.type
+ 113:44  error  nlgov:always-return-objects  The content of all responses should be wrapped in an object  paths./incorrect.get.responses[200].content.application/hal+json.schema.type
+ 118:44  error  nlgov:always-return-objects  The content of all responses should be wrapped in an object  paths./incorrect.get.responses[200].content.application/xml.schema.type
+
+✖ 3 problems (3 errors, 0 warnings, 0 infos, 0 hints)
diff --git a/linter/testcases/return-objects/openapi.json b/linter/testcases/return-objects/openapi.json
new file mode 100644
index 0000000..c22350d
--- /dev/null
+++ b/linter/testcases/return-objects/openapi.json
@@ -0,0 +1,147 @@
+{
+    "openapi": "3.0.3",
+    "info": {
+        "title": "Baseline",
+        "description": "Deze OpenAPI specification bevat het minimale om aan alle regels te voldoen.",
+        "contact": {
+            "name": "Beheerder",
+            "url": "https://www.example.com",
+            "email": "mail@example.com"
+        },
+        "version": "1.0.0"
+    },
+    "servers": [
+        {
+            "url": "https://example.com/api/v1"
+        }
+    ],
+    "security": [
+        {
+            "default": []
+        }
+    ],
+    "tags": [
+        {
+            "name": "openapi"
+        }
+    ],
+    "paths": {
+        "/correct": {
+            "get": {
+                "tags": [
+                    "openapi"
+                ],
+                "description": "OpenAPI document",
+                "operationId": "getCorrect",
+                "parameters": [],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "headers": {
+                            "API-Version": {
+                                "description": "De huidige versie van de applicatie",
+                                "style": "simple",
+                                "schema": {
+                                    "type": "string"
+                                }
+                            }
+                        },
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                   "type": "object"
+                                }
+                            },
+                            "application/hal+json": {
+                                "schema": {
+                                   "type": "object"
+                                }
+                            },
+                            "application/xml": {
+                                "schema": {
+                                   "type": "object"
+                                }
+                            },
+                            "plain/text": {
+                                "schema": {
+                                   "type": "string"
+                                }
+                            },
+                            "text/html": {
+                                "schema": {
+                                   "type": "string"
+                                }
+                            }
+                        }
+                    }
+                },
+                "security": [
+                    {
+                        "default": []
+                    }
+                ]
+            }
+        },
+        "/incorrect": {
+            "get": {
+                "tags": [
+                    "openapi"
+                ],
+                "description": "OpenAPI document",
+                "operationId": "getIncorrect",
+                "parameters": [],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "headers": {
+                            "API-Version": {
+                                "description": "De huidige versie van de applicatie",
+                                "style": "simple",
+                                "schema": {
+                                    "type": "string"
+                                }
+                            }
+                        },
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                   "type": "array"
+                                }
+                            },
+                            "application/hal+json": {
+                                "schema": {
+                                   "type": "array"
+                                }
+                            },
+                            "application/xml": {
+                                "schema": {
+                                   "type": "array"
+                                }
+                            }
+                        }
+                    }
+                },
+                "security": [
+                    {
+                        "default": []
+                    }
+                ]
+            }
+        }
+    },
+    "components": {
+        "schemas": {
+        },
+        "securitySchemes": {
+            "default": {
+                "type": "oauth2",
+                "flows": {
+                    "implicit": {
+                        "authorizationUrl": "https://test.com",
+                        "scopes": {}
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/media/linter.yaml b/media/linter.yaml
index 679a5aa..1723a5d 100644
--- a/media/linter.yaml
+++ b/media/linter.yaml
@@ -290,3 +290,14 @@ rules:
       function: pattern
       functionOptions:
         match: ^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
+
+  #/core/always-return-objects
+  nlgov:always-return-objects:
+    severity: error
+    message: "The content of all responses should be wrapped in an object"
+    given: $..[responses][*][content][?(@property.toString().match(/(json)|(xml)/))][schema]
+    then:
+      field: type
+      function: pattern
+      functionOptions:
+        match: "object"
diff --git a/sections/designRules.md b/sections/designRules.md
index b9de791..c259e49 100644
--- a/sections/designRules.md
+++ b/sections/designRules.md
@@ -192,17 +192,18 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f
 
 
-
-

Return an object when retrieving items from a collection resource

+
+

Always return a response with an object

Statement
-

An API MUST return an object for a GET request on a collection resource. - The object MUST contain a field (its key MAY be named items) with its value an array of items from a collection resource. +

An API MUST return a response with a top-level object, regardless of request method. + For collection resources, the object MUST contain a field (its key MAY be named items) with its value an array of items from that collection.

Rationale
-

Items in a collection resources can be retrieved all at once, or a subset thereof. +

For singular resources, objects are returned as part of RESTful design. +

For collection resources, items can be retrieved all at once, or a subset thereof. In both cases, wrapping the returned items in an object allows metadata to be attached to the response. Examples of metadata are structured data to implement linked data concepts, or pagination data for performance purposes.

If a collection resource directly returns an array of object information from the collection, such metadata can only be provided using HTTP headers. @@ -233,7 +234,7 @@ Content-Type: application/json

How to test
- Analyse all responses for paths supporting a GET method and check that the type of response is an object. + Analyse all responses for paths and check that the response contains a top-level object.
From 3e78e7397b0b89f330621db9376f4e167c447fe9 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Tue, 18 Aug 2026 11:00:39 +0200 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: Alexander Green Co-authored-by: Tim van der Lippe --- linter/testcases/return-objects/openapi.json | 2 +- sections/designRules.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/linter/testcases/return-objects/openapi.json b/linter/testcases/return-objects/openapi.json index c22350d..fb13933 100644 --- a/linter/testcases/return-objects/openapi.json +++ b/linter/testcases/return-objects/openapi.json @@ -62,7 +62,7 @@ "type": "object" } }, - "plain/text": { + "text/plain": { "schema": { "type": "string" } diff --git a/sections/designRules.md b/sections/designRules.md index c259e49..53d0d3f 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -197,7 +197,7 @@ https://api.example.org/v1/vergunningen/d285e05c-6b01-45c3-92d8-5e19a946b66f
Statement
-

An API MUST return a response with a top-level object, regardless of request method. +

A JSON or XML response MUST have a top-level object, regardless of request method. For collection resources, the object MUST contain a field (its key MAY be named items) with its value an array of items from that collection.

Rationale
From aa644491703bc57d39415e96ac23d83151cc6018 Mon Sep 17 00:00:00 2001 From: Tim van der Lippe Date: Thu, 20 Aug 2026 11:01:07 +0200 Subject: [PATCH 6/6] Update designRules.md --- sections/designRules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sections/designRules.md b/sections/designRules.md index 53d0d3f..b60bc3b 100644 --- a/sections/designRules.md +++ b/sections/designRules.md @@ -234,7 +234,7 @@ Content-Type: application/json
How to test
- Analyse all responses for paths and check that the response contains a top-level object. + Analyse all JSON and XML responses for paths and check that the response contains a top-level object.