diff --git a/__tests__/serializers/serializeResourceLinkage.test.ts b/__tests__/serializers/serializeResourceLinkage.test.ts index e86ecec..efe6f47 100644 --- a/__tests__/serializers/serializeResourceLinkage.test.ts +++ b/__tests__/serializers/serializeResourceLinkage.test.ts @@ -18,6 +18,19 @@ describe('`serializeResourceLinkage`', () => { chance = new Chance(); }); + describe('when given a resource linkage', () => { + it('should return the resource linkage as is', () => { + const resourceLinkage = { + type: chance.string(), + id: chance.string(), + }; + + expect(serializeResourceLinkage(resourceLinkage)).toEqual( + resourceLinkage, + ); + }); + }); + describe('when given an array of class instances', () => { it('should throw an error if some element in the array is not an object', () => { const classInstances = [1]; diff --git a/__tests__/serializers/utils/isResourceIdentifierObject.test.ts b/__tests__/serializers/utils/isResourceIdentifierObject.test.ts new file mode 100644 index 0000000..abb6743 --- /dev/null +++ b/__tests__/serializers/utils/isResourceIdentifierObject.test.ts @@ -0,0 +1,80 @@ +import { isResourceIdentifierObject } from '../../../src/serializers/utils/isResourceIdentifierObject'; + +describe('`isResourceIdentifierObject`', () => { + it('it should return `false` if `resourceIdentifierObjectCandidate` is null', () => { + const resourceIdentifierObjectCandidate = null; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate` is not an object', () => { + const resourceIdentifierObjectCandidate = 'not an object'; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate` is an array', () => { + const resourceIdentifierObjectCandidate: unknown[] = []; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate` does not have a `type` property', () => { + const resourceIdentifierObjectCandidate = { + id: '1', + }; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate.type` is not a string', () => { + const resourceIdentifierObjectCandidate = { + type: 1, + id: '1', + }; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate` does not have an `id` property', () => { + const resourceIdentifierObjectCandidate = { + type: 'test', + }; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `false` if `resourceIdentifierObjectCandidate.id` is not a string', () => { + const resourceIdentifierObjectCandidate = { + type: 'test', + id: 1, + }; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + false, + ); + }); + + it('it should return `true` if `resourceIdentifierObjectCandidate` is a valid JSON:API Resource Identifier Object', () => { + const resourceIdentifierObjectCandidate = { + type: 'test', + id: '1', + }; + + expect(isResourceIdentifierObject(resourceIdentifierObjectCandidate)).toBe( + true, + ); + }); +}); diff --git a/__tests__/serializers/utils/isResourceLinkage.test.ts b/__tests__/serializers/utils/isResourceLinkage.test.ts new file mode 100644 index 0000000..2da21b7 --- /dev/null +++ b/__tests__/serializers/utils/isResourceLinkage.test.ts @@ -0,0 +1,33 @@ +import { isResourceLinkage } from '../../../src/serializers/utils/isResourceLinkage'; + +import { isResourceIdentifierObject } from '../../../src/serializers/utils/isResourceIdentifierObject'; + +jest.mock('../../../src/serializers/utils/isResourceIdentifierObject'); + +const isResourceIdentifierObjectMocked = jest.mocked( + isResourceIdentifierObject, +); + +describe('`isResourceLinkage`', () => { + it('should return true if `resourceLinkageCandidate` is `null`', () => { + const resourceLinkageCandidate = null; + + expect(isResourceLinkage(resourceLinkageCandidate)).toBe(true); + }); + + it('should return true if `resourceLinkageCandidate` is an array of `JSONAPIResourceIdentifierObject`', () => { + isResourceIdentifierObjectMocked.mockReturnValue(true); + + const resourceLinkageCandidate = ['foo', 'bar']; + + expect(isResourceLinkage(resourceLinkageCandidate)).toBe(true); + }); + + it('should return true if `resourceLinkageCandidate` is a `JSONAPIResourceIdentifierObject`', () => { + isResourceIdentifierObjectMocked.mockReturnValue(true); + + const resourceLinkageCandidate = 'foo'; + + expect(isResourceLinkage(resourceLinkageCandidate)).toBe(true); + }); +}); diff --git a/src/serializers/serializeResourceLinkage.ts b/src/serializers/serializeResourceLinkage.ts index 4d6cd3b..9776233 100644 --- a/src/serializers/serializeResourceLinkage.ts +++ b/src/serializers/serializeResourceLinkage.ts @@ -3,12 +3,17 @@ import { resourceSymbol } from '../decorators/resource'; import { collect } from './utils/collect'; import { getMetadataBySymbol } from './utils/getMetadataBySymbol'; import { isObject } from './utils/isObject'; +import { isResourceLinkage } from './utils/isResourceLinkage'; import type { JSONAPIResourceLinkage } from '../types/resourceLinkage'; export const serializeResourceLinkage = ( - classInstance_s: I, -): Exclude => { + classInstance_s: I | JSONAPIResourceLinkage, +): JSONAPIResourceLinkage => { + if (isResourceLinkage(classInstance_s)) { + return classInstance_s; + } + if (Array.isArray(classInstance_s)) { if (!classInstance_s.every(isObject)) { throw new Error( diff --git a/src/serializers/utils/isResourceIdentifierObject.ts b/src/serializers/utils/isResourceIdentifierObject.ts new file mode 100644 index 0000000..868f824 --- /dev/null +++ b/src/serializers/utils/isResourceIdentifierObject.ts @@ -0,0 +1,34 @@ +import type { JSONAPIResourceIdentifierObject } from '../../types/resourceIdentifierObject'; + +export const isResourceIdentifierObject = ( + resourceIdentifierObjectCandidate: unknown, +): resourceIdentifierObjectCandidate is JSONAPIResourceIdentifierObject => { + if (resourceIdentifierObjectCandidate === null) { + return false; + } + + if ( + typeof resourceIdentifierObjectCandidate !== 'object' || + Array.isArray(resourceIdentifierObjectCandidate) + ) { + return false; + } + + if ('type' in resourceIdentifierObjectCandidate === false) { + return false; + } + + if (typeof resourceIdentifierObjectCandidate.type !== 'string') { + return false; + } + + if ('id' in resourceIdentifierObjectCandidate === false) { + return false; + } + + if (typeof resourceIdentifierObjectCandidate.id !== 'string') { + return false; + } + + return true; +}; diff --git a/src/serializers/utils/isResourceLinkage.ts b/src/serializers/utils/isResourceLinkage.ts new file mode 100644 index 0000000..4e8e861 --- /dev/null +++ b/src/serializers/utils/isResourceLinkage.ts @@ -0,0 +1,17 @@ +import { isResourceIdentifierObject } from './isResourceIdentifierObject'; + +import type { JSONAPIResourceLinkage } from '../../types/resourceLinkage'; + +export const isResourceLinkage = ( + resourceLinkageCandidate: unknown, +): resourceLinkageCandidate is JSONAPIResourceLinkage => { + if (resourceLinkageCandidate === null) { + return true; + } + + if (Array.isArray(resourceLinkageCandidate)) { + return resourceLinkageCandidate.every(isResourceIdentifierObject); + } + + return isResourceIdentifierObject(resourceLinkageCandidate); +};