-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(manipulation): add method to getUpdatableProperties
- Loading branch information
1 parent
87ced46
commit 517ee23
Showing
4 changed files
with
153 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
26 changes: 26 additions & 0 deletions
26
src/manipulation/DomainEntityUpdatablePropertiesMustBeDefinedError.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
export class DomainEntityUpdatablePropertiesMustBeDefinedError extends Error { | ||
constructor({ | ||
entityName, | ||
nameOfFunctionNeededFor, | ||
}: { | ||
entityName: string; | ||
nameOfFunctionNeededFor: string; | ||
}) { | ||
const message = ` | ||
\`${entityName}.updatable\` must be defined, to be able to \`${nameOfFunctionNeededFor}\`. | ||
Example: | ||
\`\`\` | ||
interface RocketShip { | ||
serialNumber: string; | ||
fuelQuantity: number; | ||
passengers: number; | ||
} | ||
class RocketShip extends DomainEntity<RocketShip> implements RocketShip { | ||
public static updatable = ['fuelQuantity', 'passengers]; | ||
} | ||
\`\`\` | ||
`; | ||
super(message); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import { v4 as uuid } from 'uuid'; | ||
|
||
import { DomainObjectNotSafeToManipulateError } from '../constraints/assertDomainObjectIsSafeToManipulate'; | ||
import { DomainEntity } from '../instantiation/DomainEntity'; | ||
import { DomainEntityUniqueKeysMustBeDefinedError } from './DomainEntityUniqueKeysMustBeDefinedError'; | ||
import { DomainEntityUpdatablePropertiesMustBeDefinedError } from './DomainEntityUpdatablePropertiesMustBeDefinedError'; | ||
import { getUniqueIdentifier } from './getUniqueIdentifier'; | ||
import { getUpdatableProperties } from './getUpdatableProperties'; | ||
|
||
describe('getUpdatableProperties', () => { | ||
describe('entity', () => { | ||
it('should be able to get updatable properties accurately', () => { | ||
interface RocketShip { | ||
serialNumber: string; | ||
fuelQuantity: number; | ||
passengers: number; | ||
} | ||
class RocketShip extends DomainEntity<RocketShip> implements RocketShip { | ||
public static updatable = ['fuelQuantity', 'passengers']; | ||
} | ||
const ship = new RocketShip({ | ||
serialNumber: 'SN5', | ||
fuelQuantity: 9001, | ||
passengers: 21, | ||
}); | ||
const updatableProps = getUpdatableProperties(ship); | ||
expect(updatableProps).toEqual({ fuelQuantity: 9001, passengers: 21 }); | ||
}); | ||
it('should throw an error if .updatable is not defined on the entity', () => { | ||
interface RocketShip { | ||
serialNumber: string; | ||
fuelQuantity: number; | ||
passengers: number; | ||
} | ||
class RocketShip extends DomainEntity<RocketShip> implements RocketShip {} | ||
const ship = new RocketShip({ | ||
serialNumber: 'SN5', | ||
fuelQuantity: 9001, | ||
passengers: 21, | ||
}); | ||
try { | ||
getUpdatableProperties(ship); | ||
throw new Error('should not reach here'); | ||
} catch (error) { | ||
if (!(error instanceof Error)) throw error; | ||
expect(error.message).toContain( | ||
'`RocketShip.updatable` must be defined, to be able to `getUpdatableProperties`', | ||
); | ||
expect(error).toBeInstanceOf( | ||
DomainEntityUpdatablePropertiesMustBeDefinedError, | ||
); | ||
} | ||
}); | ||
}); | ||
describe('safety', () => { | ||
it('should throw an error if domain object is not safe to manipulate', () => { | ||
interface PlaneManufacturer { | ||
name: string; | ||
} | ||
interface Plane { | ||
uuid: string; | ||
manufacturer: PlaneManufacturer; | ||
passengerLimit: number; | ||
} | ||
class Plane extends DomainEntity<Plane> implements Plane { | ||
public static unique = ['uuid']; | ||
public static updatable = ['passengerLimit']; | ||
} | ||
const phone = new Plane({ | ||
uuid: uuid(), | ||
manufacturer: { name: 'boeing' }, | ||
passengerLimit: 821, | ||
}); | ||
try { | ||
getUpdatableProperties(phone); | ||
} catch (error) { | ||
expect(error).toBeInstanceOf(DomainObjectNotSafeToManipulateError); | ||
} | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import pick from 'lodash.pick'; | ||
|
||
import { assertDomainObjectIsSafeToManipulate } from '../constraints/assertDomainObjectIsSafeToManipulate'; | ||
import { DomainEntity } from '../instantiation/DomainEntity'; | ||
import { DomainObject } from '../instantiation/DomainObject'; | ||
import { DomainEntityUpdatablePropertiesMustBeDefinedError } from './DomainEntityUpdatablePropertiesMustBeDefinedError'; | ||
|
||
/** | ||
* Extracts an object that contains only the updatable properties of the domain object, for DomainEntity | ||
* | ||
* Uses the definition of a DomainEntity in order to extract the properties that are updatable for the domain object generically | ||
* | ||
* note | ||
* - this should not be called on DomainValueObject's or DomainEvent's, since by definition they can not have updatable properties | ||
*/ | ||
export const getUpdatableProperties = <T extends Record<string, any>>( | ||
dobj: DomainEntity<T>, | ||
): Partial<T> => { | ||
// make sure its an instance of DomainObject | ||
if (!(dobj instanceof DomainObject)) | ||
throw new Error( | ||
'getUpdatableProperties called on object that is not an instance of a DomainObject. Are you sure you instantiated the object? (Related: see `DomainObject.nested`)', | ||
); | ||
|
||
// make sure that its safe to manipulate | ||
assertDomainObjectIsSafeToManipulate(dobj); | ||
|
||
// handle DomainEntity | ||
if (dobj instanceof DomainEntity) { | ||
const className = (dobj.constructor as typeof DomainEntity).name; | ||
const updatableProps = (dobj.constructor as typeof DomainEntity).updatable; | ||
if (!updatableProps) | ||
throw new DomainEntityUpdatablePropertiesMustBeDefinedError({ | ||
entityName: className, | ||
nameOfFunctionNeededFor: 'getUpdatableProperties', | ||
}); | ||
return pick(dobj, updatableProps.flat()); | ||
} | ||
|
||
// throw error we get here, this is unexpected | ||
throw new Error('unexpected domain object type'); | ||
}; |