Repository: scimmyjs/scimmy
Pull request #72 (2025-02-23 03:51:28)

Add schema ID static get accessor properties for all `SCIMMY.Messages` classes
PR #69 added a static get accessor and correct typings for the `SCIMMY.Messages.PatchOp` class.

This change adds the same static get accessor and property declarations to all classes in the `SCIMMY.Messages` namespace. Test fixtures have also been updated to reflect this change.


Patch:
diff --git a/src/lib/messages/bulkrequest.js b/src/lib/messages/bulkrequest.js
index 97458d5..2f83666 100644
--- a/src/lib/messages/bulkrequest.js
+++ b/src/lib/messages/bulkrequest.js
@@ -19,12 +19,17 @@ const validMethods = ["POST", "PUT", "PATCH", "DELETE"];
  * *   Provides a method to apply BulkRequest operations and return the results as a BulkResponse.
  */
 export class BulkRequest {
+    /** @private */
+    static #id = "urn:ietf:params:scim:api:messages:2.0:BulkRequest";
+    
     /**
      * SCIM BulkRequest Message Schema ID
-     * @type {String}
-     * @private
+     * @type {"urn:ietf:params:scim:api:messages:2.0:BulkRequest"}
      */
-    static #id = "urn:ietf:params:scim:api:messages:2.0:BulkRequest";
+    static get id() {
+        return this.#id;
+    }
+    
     
     /**
      * Whether the incoming BulkRequest has been applied 
@@ -47,9 +52,11 @@ export class BulkRequest {
     /**
      * Instantiate a new SCIM BulkRequest message from the supplied operations
      * @param {Object} request - contents of the BulkRequest operation being performed
+     * @param {typeof SCIMMY.Messages.BulkRequest.id[]} request.schemas - list exclusively containing the SCIM BulkRequest message schema ID
      * @param {SCIMMY.Messages.BulkRequest~BulkOpOperation[]} request.Operations - list of SCIM-compliant bulk operations to apply
      * @param {Number} [request.failOnErrors] - number of error results to encounter before aborting any following operations
      * @param {Number} [maxOperations] - maximum number of operations supported in the request, as specified by the service provider
+     * @property {typeof SCIMMY.Messages.BulkRequest.id[]} schemas - list exclusively containing the SCIM BulkRequest message schema ID
      * @property {SCIMMY.Messages.BulkRequest~BulkOpOperation[]} Operations - list of operations in this BulkRequest instance
      * @property {Number} [failOnErrors] - number of error results a service provider should tolerate before aborting any following operations
      */
@@ -101,7 +108,7 @@ export class BulkRequest {
         
         // Get a map of POST ops with bulkIds for direct and circular reference resolution
         const bulkIds = new Map(this.Operations
-            .filter(o => o.method === "POST" && !!o.bulkId && typeof o.bulkId === "string")
+            .filter(o => String(o.method) === "POST" && !!o.bulkId && typeof o.bulkId === "string")
             .map(({bulkId}, index, postOps) => {
                 // Establish who waits on what, and provide a way for that to happen
                 const handlers = {referencedBy: postOps.filter(({data}) => JSON.stringify(data ?? {}).includes(`bulkId:${bulkId}`)).map(({bulkId}) => bulkId)};
diff --git a/src/lib/messages/bulkresponse.js b/src/lib/messages/bulkresponse.js
index f99d283..e2f7b1e 100644
--- a/src/lib/messages/bulkresponse.js
+++ b/src/lib/messages/bulkresponse.js
@@ -7,12 +7,16 @@
  * *   Provides a method to unwrap BulkResponse results into operation success status, and map newly created resource IDs to their BulkRequest bulkIds.
  */
 export class BulkResponse {
+    /** @private */
+    static #id = "urn:ietf:params:scim:api:messages:2.0:BulkResponse";
+    
     /**
      * SCIM BulkResponse Message Schema ID
-     * @type {String}
-     * @private
+     * @type {"urn:ietf:params:scim:api:messages:2.0:BulkResponse"}
      */
-    static #id = "urn:ietf:params:scim:api:messages:2.0:BulkResponse";
+    static get id() {
+        return this.#id;
+    }
     
     /**
      * BulkResponse operation response status codes
@@ -48,6 +52,7 @@ export class BulkResponse {
      * @param {SCIMMY.Messages.BulkResponse~BulkOpResponse[]} request - results of performed operations if array
      * @param {Object} request - contents of the received BulkResponse message if object
      * @param {SCIMMY.Messages.BulkResponse~BulkOpResponse[]} request.Operations - list of SCIM-compliant bulk operation results
+     * @property {typeof SCIMMY.Messages.BulkResponse.id[]} schemas - list exclusively containing the SCIM BulkResponse message schema ID
      * @property {SCIMMY.Messages.BulkResponse~BulkOpResponse[]} Operations - list of BulkResponse operation results
      */
     constructor(request = []) {
@@ -74,7 +79,7 @@ export class BulkResponse {
     resolve() {
         return new Map(this.Operations
             // Only target POST operations with valid bulkIds
-            .filter(o => o.method === "POST" && !!o.bulkId && typeof o.bulkId === "string")
+            .filter(o => String(o.method) === "POST" && !!o.bulkId && typeof o.bulkId === "string")
             .map(o => ([o.bulkId, (typeof o.location === "string" && !!o.location ? o.location.split("/").pop() : false)])));
     }
 }
\ No newline at end of file
diff --git a/src/lib/messages/error.js b/src/lib/messages/error.js
index ca2c9aa..57aa1b8 100644
--- a/src/lib/messages/error.js
+++ b/src/lib/messages/error.js
@@ -28,12 +28,16 @@ const validCodeTypes = {400: validScimTypes.slice(2), 409: ["uniqueness"], 413:
  * *   When used to parse service provider responses, throws a new instance of `SCIMMY.Types.Error` with details sourced from the message.
  */
 export class ErrorResponse extends Error {
+    /** @private */
+    static #id = "urn:ietf:params:scim:api:messages:2.0:Error";
+    
     /**
      * SCIM Error Message Schema ID
-     * @type {String}
-     * @private
+     * @type {"urn:ietf:params:scim:api:messages:2.0:Error"}
      */
-    static #id = "urn:ietf:params:scim:api:messages:2.0:Error";
+    static get id() {
+        return this.#id;
+    }
     
     /**
      * Details of the underlying cause of the error response
@@ -47,6 +51,7 @@ export class ErrorResponse extends Error {
     /**
      * Instantiate a new SCIM Error Message with relevant details
      * @param {typeof SCIMMY.Types.Error|SCIMMY.Messages.ErrorResponse~CauseDetails|Error} [ex={}] - the initiating exception to parse into a SCIM error message
+     * @property {typeof SCIMMY.Messages.ErrorResponse.id[]} schemas - list exclusively containing the SCIM Error message schema ID
      * @property {SCIMMY.Messages.ErrorResponse~ValidStatusCodes} status - stringified HTTP status code to be sent with the error
      * @property {SCIMMY.Messages.ErrorResponse~ValidScimTypes} [scimType] - the SCIM detail error keyword as per [RFC7644§3.12]{@link https://datatracker.ietf.org/doc/html/rfc7644#section-3.12}
      * @property {String} [detail] - a human-readable description of what caused the error to occur
diff --git a/src/lib/messages/listresponse.js b/src/lib/messages/listresponse.js
index f49939d..6c15805 100644
--- a/src/lib/messages/listresponse.js
+++ b/src/lib/messages/listresponse.js
@@ -37,12 +37,16 @@
  * const response = new SCIMMY.Messages.ListResponse(resources, {itemsPerPage: 2});
  */
 export class ListResponse {
+    /** @private */
+    static #id = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
+    
     /**
      * SCIM List Response Message Schema ID
-     * @type {String}
-     * @private
+     * @type {"urn:ietf:params:scim:api:messages:2.0:ListResponse"}
      */
-    static #id = "urn:ietf:params:scim:api:messages:2.0:ListResponse";
+    static get id() {
+        return this.#id;
+    }
     
     /**
      * ListResponse sort and pagination constraints
@@ -63,6 +67,7 @@ export class ListResponse {
      * @param {Number} [params.count=20] - alias property for itemsPerPage, used only if itemsPerPage is unset
      * @param {Number} [params.itemsPerPage=20] - maximum number of items returned in this list response
      * @param {Number} [params.totalResults] - the total number of resources matching a given request
+     * @property {typeof SCIMMY.Messages.ListResponse.id[]} schemas - list exclusively containing the SCIM ListResponse message schema ID
      * @property {Array<T>} Resources - resources included in the list response
      * @property {Number} totalResults - the total number of resources matching a given request
      * @property {Number} startIndex - index within total results that included resources start from
diff --git a/src/lib/messages/patchop.js b/src/lib/messages/patchop.js
index 5250cb4..9324d2f 100644
--- a/src/lib/messages/patchop.js
+++ b/src/lib/messages/patchop.js
@@ -58,7 +58,7 @@ export class PatchOp {
      * @type {"urn:ietf:params:scim:api:messages:2.0:PatchOp"}
      */
     static get id() {
-        return PatchOp.#id;
+        return this.#id;
     }
     
     /**
diff --git a/src/lib/messages/searchrequest.js b/src/lib/messages/searchrequest.js
index d333c09..42b8368 100644
--- a/src/lib/messages/searchrequest.js
+++ b/src/lib/messages/searchrequest.js
@@ -11,16 +11,21 @@ import Resources from "../resources.js";
  * *   Provides a method to perform the search request against the declared or specified resource types.
  */
 export class SearchRequest {
+    /** @private */
+    static #id = "urn:ietf:params:scim:api:messages:2.0:SearchRequest";
+    
     /**
      * SCIM SearchRequest Message Schema ID
-     * @type {String}
-     * @private
+     * @type {"urn:ietf:params:scim:api:messages:2.0:SearchRequest"}
      */
-    static #id = "urn:ietf:params:scim:api:messages:2.0:SearchRequest";
+    static get id() {
+        return this.#id;
+    }
     
     /**
      * Instantiate a new SCIM SearchRequest message from the supplied request
      * @param {Object} [request] - contents of the SearchRequest received by the service provider
+     * @param {typeof SCIMMY.Messages.SearchRequest.id[]} request.schemas - list exclusively containing the SCIM SearchRequest message schema ID
      * @param {String} [request.filter] - the filter to be applied on ingress/egress by implementing resource
      * @param {String[]} [request.excludedAttributes] - the string list of attributes or filters to exclude on egress
      * @param {String[]} [request.attributes] - the string list of attributes or filters to include on egress
@@ -28,6 +33,7 @@ export class SearchRequest {
      * @param {String} [request.sortOrder] - the direction retrieved resources should be sorted in
      * @param {Number} [request.startIndex] - offset index that retrieved resources should start from
      * @param {Number} [request.count] - maximum number of retrieved resources that should be returned in one operation
+     * @property {typeof SCIMMY.Messages.SearchRequest.id[]} schemas - list exclusively containing the SCIM SearchRequest message schema ID
      * @property {String} [filter] - the filter to be applied on ingress/egress by implementing resource
      * @property {String[]} [excludedAttributes] - the string list of attributes or filters to exclude on egress
      * @property {String[]} [attributes] - the string list of attributes or filters to include on egress


Test patch:
diff --git a/test/lib/messages/bulkrequest.js b/test/lib/messages/bulkrequest.js
index 0fa4eed..f1f26a3 100644
--- a/test/lib/messages/bulkrequest.js
+++ b/test/lib/messages/bulkrequest.js
@@ -135,6 +135,23 @@ describe("SCIMMY.Messages.BulkRequest", () => {
         });
     });
     
+    describe(".id", () => {
+        it("should be defined", () => {
+            assert.ok("id" in BulkRequest,
+                "Static member 'id' not defined");
+        });
+        
+        it("should be a string", () => {
+            assert.ok(typeof BulkRequest.id === "string",
+                "Static member 'id' was not a string");
+        });
+        
+        it("should match SCIM Bulk Request Message schema ID", async () => {
+            assert.strictEqual(BulkRequest.id, params.id,
+                "Static member 'id' did not match SCIM Bulk Request Message schema ID");
+        });
+    });
+    
     describe("#apply()", () => {
         it("should be implemented", () => {
             assert.ok(typeof (new BulkRequest({...template})).apply === "function",
diff --git a/test/lib/messages/bulkresponse.js b/test/lib/messages/bulkresponse.js
index 491befe..546e6b9 100644
--- a/test/lib/messages/bulkresponse.js
+++ b/test/lib/messages/bulkresponse.js
@@ -28,6 +28,23 @@ describe("SCIMMY.Messages.BulkResponse", () => {
         });
     });
     
+    describe(".id", () => {
+        it("should be defined", () => {
+            assert.ok("id" in BulkResponse,
+                "Static member 'id' not defined");
+        });
+        
+        it("should be a string", () => {
+            assert.ok(typeof BulkResponse.id === "string",
+                "Static member 'id' was not a string");
+        });
+        
+        it("should match SCIM Bulk Response Message schema ID", async () => {
+            assert.strictEqual(BulkResponse.id, params.id,
+                "Static member 'id' did not match SCIM Bulk Response Message schema ID");
+        });
+    });
+    
     describe("#resolve()", () => {
         it("should be implemented", () => {
             assert.ok(typeof (new BulkResponse()).resolve === "function",
diff --git a/test/lib/messages/error.js b/test/lib/messages/error.js
index 46b80f4..5fb8d99 100644
--- a/test/lib/messages/error.js
+++ b/test/lib/messages/error.js
@@ -63,4 +63,21 @@ describe("SCIMMY.Messages.Error", () => {
             }
         });
     });
+    
+    describe(".id", () => {
+        it("should be defined", () => {
+            assert.ok("id" in ErrorResponse,
+                "Static member 'id' not defined");
+        });
+        
+        it("should be a string", () => {
+            assert.ok(typeof ErrorResponse.id === "string",
+                "Static member 'id' was not a string");
+        });
+        
+        it("should match SCIM Error Message schema ID", async () => {
+            assert.strictEqual(ErrorResponse.id, params.id,
+                "Static member 'id' did not match SCIM Error Message schema ID");
+        });
+    });
 });
\ No newline at end of file
diff --git a/test/lib/messages/listresponse.js b/test/lib/messages/listresponse.js
index 5900ebf..a3d74ea 100644
--- a/test/lib/messages/listresponse.js
+++ b/test/lib/messages/listresponse.js
@@ -96,6 +96,23 @@ describe("SCIMMY.Messages.ListResponse", () => {
         });
     });
     
+    describe(".id", () => {
+        it("should be defined", () => {
+            assert.ok("id" in ListResponse,
+                "Static member 'id' not defined");
+        });
+        
+        it("should be a string", () => {
+            assert.ok(typeof ListResponse.id === "string",
+                "Static member 'id' was not a string");
+        });
+        
+        it("should match SCIM List Response Message schema ID", async () => {
+            assert.strictEqual(ListResponse.id, params.id,
+                "Static member 'id' did not match SCIM List Response Message schema ID");
+        });
+    });
+    
     describe("#Resources", () => {
         it("should be defined", () => {
             assert.ok("Resources" in new ListResponse(),
diff --git a/test/lib/messages/searchrequest.js b/test/lib/messages/searchrequest.js
index bd02b12..46f7665 100644
--- a/test/lib/messages/searchrequest.js
+++ b/test/lib/messages/searchrequest.js
@@ -142,6 +142,23 @@ describe("SCIMMY.Messages.SearchRequest", () => {
         });
     });
     
+    describe(".id", () => {
+        it("should be defined", () => {
+            assert.ok("id" in SearchRequest,
+                "Static member 'id' not defined");
+        });
+        
+        it("should be a string", () => {
+            assert.ok(typeof SearchRequest.id === "string",
+                "Static member 'id' was not a string");
+        });
+        
+        it("should match SCIM Search Request Message schema ID", async () => {
+            assert.strictEqual(SearchRequest.id, params.id,
+                "Static member 'id' did not match SCIM Search Request Message schema ID");
+        });
+    });
+    
     describe("#prepare()", () => {
         it("should be implemented", () => {
             assert.ok(typeof (new SearchRequest()).prepare === "function",