-
Notifications
You must be signed in to change notification settings - Fork 280
feat(#10036): add PersonQualifier
and related functions
#10043
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 10 commits
215d1b2
cbc4aaa
07a3e92
8e37179
740295c
9081f1f
577ed6e
7958908
b5b3551
3427c99
5fe209a
566a085
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import { isString, hasField, isRecord, Nullable, hasFields } from './libs/core'; | ||
import { isString, hasField, isRecord, Nullable, hasFields, NormalizedParent, isNormalizedParent } from './libs/core'; | ||
import { InvalidArgumentError } from './libs/error'; | ||
|
||
/** | ||
|
@@ -164,36 +164,51 @@ type ContactQualifier = Readonly<{ | |
* Valid formats are 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or <unix epoch>. | ||
*/ | ||
export const byContactQualifier = (data: unknown): ContactQualifier => { | ||
return byContactQualifierNonAssertive(data) as ContactQualifier; | ||
}; | ||
|
||
/** @internal*/ | ||
export const byContactQualifierNonAssertive = (data: unknown) : Record<string, unknown> => { | ||
if (!isRecord(data)){ | ||
throw new InvalidArgumentError('Invalid "data": expected an object.'); | ||
} | ||
const qualifier = {...data}; | ||
if ('reported_date' in qualifier && !isValidReportedDate(qualifier.reported_date)){ | ||
insertReportedDateIfMissing(qualifier); | ||
if (!isValidReportedDate(qualifier.reported_date)){ | ||
throw new InvalidArgumentError( | ||
// eslint-disable-next-line max-len | ||
`Invalid reported_date. Expected format to be 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or a Unix epoch.` | ||
); | ||
} | ||
if (!isContactQualifier(qualifier)){ | ||
if (!checkContactQualifierFields(qualifier)){ | ||
throw new InvalidArgumentError(`Missing or empty required fields [${JSON.stringify(data)}].`); | ||
} | ||
return qualifier; | ||
}; | ||
|
||
const insertReportedDateIfMissing = (qualifier: Record<string, unknown>) :void => { | ||
if (!('reported_date' in qualifier)){ | ||
qualifier.reported_date = new Date().toISOString(); | ||
} | ||
}; | ||
|
||
/** | ||
* Returns `true` if the given qualifier is a {@link ContactQualifier} otherwise `false`. | ||
* @param qualifier the qualifier to check | ||
* @returns `true` if the given type is a {@link ContactQualifier}, otherwise `false`. | ||
*/ | ||
export const isContactQualifier = (qualifier: unknown): qualifier is ContactQualifier => { | ||
if (isRecord(qualifier) && hasFields(qualifier, [{name: 'type', type: 'string', ensureTruthyValue: true}, | ||
{name: 'name', type: 'string', ensureTruthyValue: true}])){ | ||
if ('reported_date' in qualifier && !isValidReportedDate(qualifier.reported_date)){ | ||
return false; | ||
} | ||
return true; | ||
} | ||
return false; | ||
return checkContactQualifierFields(qualifier); | ||
}; | ||
|
||
/** @internal */ | ||
const checkContactQualifierFields = (data: unknown): data is Record<string, unknown> => { | ||
return isRecord(data) && | ||
hasFields(data, [ | ||
{name: 'type', type: 'string', ensureTruthyValue: true}, | ||
{name: 'name', type: 'string', ensureTruthyValue: true} | ||
]) && | ||
(!('reported_date' in data) || isValidReportedDate(data.reported_date)); | ||
}; | ||
|
||
/** | ||
|
@@ -222,8 +237,10 @@ export const byReportQualifier = (data: unknown): ReportQualifier => { | |
if (!isRecord(data)) { | ||
throw new InvalidArgumentError('Invalid "data": expected an object.'); | ||
} | ||
insertReportedDateIfMissing(data); | ||
|
||
const qualifier = {...data}; | ||
if ('reported_date' in qualifier && !isValidReportedDate(qualifier.reported_date)) { | ||
if (!isValidReportedDate(qualifier.reported_date)) { | ||
throw new InvalidArgumentError( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue: the addition of date should be done after line 242, because the whole point of deep copying the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, right. |
||
// eslint-disable-next-line max-len | ||
`Invalid reported_date. Expected format to be 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or a Unix epoch.` | ||
|
@@ -267,3 +284,92 @@ const isValidReportedDate = (value: unknown): boolean => { | |
|
||
return false; | ||
}; | ||
|
||
/** | ||
* A qualifier for a person | ||
*/ | ||
type PersonQualifier = ContactQualifier & Readonly<{ | ||
parent: NormalizedParent; | ||
date_of_birth?: Date; | ||
phone?: string; | ||
patient_id?: string; | ||
sex?: string; | ||
contact_type?: string | ||
}> | ||
|
||
/** | ||
* Builds a qualifier for creation and update of a person with | ||
* the given fields. | ||
* @param data object containing the fields for a person | ||
* @returns the person qualifier | ||
* @throws Error if data is not an object | ||
* @throws Error if type is not provided or is empty | ||
* @throws Error if name is not provided or is empty | ||
* @throws Error if parent is not provided or is empty | ||
* @throws Error if parent is not in a valid de-hydrated format. | ||
* @throws Error if reported_date is not in a valid format. | ||
* Valid formats are 'YYYY-MM-DDTHH:mm:ssZ', 'YYYY-MM-DDTHH:mm:ss.SSSZ', or <unix epoch>. | ||
*/ | ||
export const byPersonQualifier = (data: unknown): PersonQualifier => { | ||
const qualifier = byContactQualifierNonAssertive(data); | ||
|
||
if (!hasField(qualifier, { name: 'parent', type: 'object' })) { | ||
throw new InvalidArgumentError(`Missing or empty required fields [${JSON.stringify(qualifier)}].`); | ||
} | ||
|
||
if (!isNormalizedParent(qualifier.parent)) { | ||
throw new InvalidArgumentError(`Missing required fields in the parent hierarchy [${JSON.stringify(qualifier)}].`); | ||
} | ||
|
||
if (!hasValidContactType(qualifier) && !hasValidLegacyContactType(qualifier, 'person')) { | ||
throw new InvalidArgumentError('Invalid type for contacts.'); | ||
apoorvapendse marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (hasBloatedLineage(qualifier)) { | ||
throw new InvalidArgumentError(`Additional fields found in the parent lineage [${JSON.stringify(qualifier)}].`); | ||
} | ||
|
||
return qualifier as unknown as PersonQualifier; | ||
}; | ||
|
||
/** @internal */ | ||
export const isPersonQualifier = (data: unknown): data is PersonQualifier => { | ||
if (!checkContactQualifierFields(data)) { | ||
return false; | ||
} | ||
|
||
if (!hasField(data, { name: 'parent', type: 'object' }) || !isNormalizedParent(data.parent)) { | ||
return false; | ||
} | ||
|
||
if (hasBloatedLineage(data)) { | ||
return false; | ||
} | ||
|
||
return hasValidContactType(data) || hasValidLegacyContactType(data, 'person'); | ||
}; | ||
|
||
/** @internal */ | ||
const hasBloatedLineage = ( data: Record<string, unknown> ): boolean => { | ||
// Ensure parent lineage doesn't have any additional properties other than `_id` and `parent`. | ||
let parent = data.parent as NormalizedParent | undefined; | ||
while (parent) { | ||
if (Object.keys(parent).length > 2) { | ||
// This means that the parent certainly has extra fields and is not minfied/de-hydrated as per | ||
// our liking as `isNormalized` check ensures that it does have two keys `_id` and `parent`. | ||
return true; | ||
} | ||
parent = parent.parent; | ||
} | ||
return false; | ||
}; | ||
|
||
/** @internal */ | ||
const hasValidContactType = ( data: Record<string, unknown> ): boolean => { | ||
return data.type === 'contact' && hasField(data, { name: 'contact_type', type: 'string' }); | ||
}; | ||
|
||
/** @internal */ | ||
const hasValidLegacyContactType = ( data: Record<string, unknown>, type:string): boolean => { | ||
return data.type === type; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: if the
reported_date
does not exist, shouldn't it be added to it? Or are you planning on adding it elsewhere?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Makes sense, added in the latest commit.