-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e80c95e
commit ec8e77c
Showing
7 changed files
with
324 additions
and
37 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// eslint-disable-next-line eslint-comments/disable-enable-pair | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import * as t from 'io-ts'; | ||
|
||
/** | ||
* Creates a default value for an io-ts codec. | ||
* | ||
* @param codec - the codec whose default we want to create | ||
* @returns an object honoring the io-ts codec | ||
*/ | ||
export const createDefaultCodec = <C extends t.Mixed>( | ||
codec: C, | ||
): t.TypeOf<C> => { | ||
if (codec instanceof t.UnionType) { | ||
// First, look for object types in the union | ||
const objectType = codec.types.find( | ||
(type: any) => | ||
type instanceof t.InterfaceType || | ||
type instanceof t.PartialType || | ||
type instanceof t.IntersectionType, | ||
); | ||
if (objectType) { | ||
return createDefaultCodec(objectType); | ||
} | ||
|
||
// For unions, null has higher preference as default. Otherwise,first type's default | ||
// If null is one of the union types, it should be the default | ||
const hasNull = codec.types.some( | ||
(type: any) => type instanceof t.NullType || type.name === 'null', | ||
); | ||
if (hasNull) { | ||
return null as t.TypeOf<C>; | ||
} | ||
|
||
// If no null type found, default to first type | ||
return createDefaultCodec(codec.types[0]); | ||
} | ||
|
||
if (codec instanceof t.InterfaceType || codec instanceof t.PartialType) { | ||
const defaults: Record<string, any> = {}; | ||
Object.entries(codec.props).forEach(([key, type]) => { | ||
defaults[key] = createDefaultCodec(type as any); | ||
}); | ||
return defaults as t.TypeOf<C>; | ||
} | ||
|
||
if (codec instanceof t.IntersectionType) { | ||
// Merge defaults of all types in the intersection | ||
return codec.types.reduce( | ||
(acc: t.TypeOf<C>, type: any) => ({ | ||
...acc, | ||
...createDefaultCodec(type), | ||
}), | ||
{}, | ||
); | ||
} | ||
|
||
if (codec instanceof t.ArrayType) { | ||
// Check if the array element type is an object type | ||
const elementType = codec.type; | ||
const isObjectType = | ||
elementType instanceof t.InterfaceType || | ||
elementType instanceof t.PartialType || | ||
elementType instanceof t.IntersectionType; | ||
|
||
return ( | ||
isObjectType ? [createDefaultCodec(elementType)] : [] | ||
) as t.TypeOf<C>; | ||
} | ||
|
||
// Handle primitive and common types | ||
switch (codec.name) { | ||
case 'string': | ||
return '' as t.TypeOf<C>; | ||
case 'number': | ||
return null as t.TypeOf<C>; | ||
case 'boolean': | ||
return null as t.TypeOf<C>; | ||
case 'null': | ||
return null as t.TypeOf<C>; | ||
case 'undefined': | ||
return undefined as t.TypeOf<C>; | ||
default: | ||
return null as t.TypeOf<C>; | ||
} | ||
}; |
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,68 @@ | ||
import * as t from 'io-ts'; | ||
import { | ||
OneTrustAssessmentNestedQuestionCodec, | ||
OneTrustAssessmentQuestionOptionCodec, | ||
OneTrustAssessmentQuestionResponseCodec, | ||
OneTrustAssessmentQuestionResponsesCodec, | ||
OneTrustAssessmentQuestionRiskCodec, | ||
OneTrustAssessmentQuestionRisksCodec, | ||
OneTrustAssessmentSectionCodec, | ||
OneTrustAssessmentSectionSubmittedByCodec, | ||
OneTrustPrimaryEntityDetailsCodec, | ||
} from '../oneTrust/codecs'; | ||
import { createDefaultCodec } from './createDefaultCodec'; | ||
|
||
// TODO: test the shit out of this | ||
const enrichQuestionWithDefault = ({ | ||
options, | ||
...rest | ||
}: OneTrustAssessmentNestedQuestionCodec): OneTrustAssessmentNestedQuestionCodec => ({ | ||
options: | ||
options === null || options.length === 0 | ||
? createDefaultCodec(t.array(OneTrustAssessmentQuestionOptionCodec)) | ||
: options, | ||
...rest, | ||
}); | ||
|
||
// TODO: test the shit out of this | ||
const enrichQuestionResponsesWithDefault = ( | ||
questionResponses: OneTrustAssessmentQuestionResponsesCodec, | ||
): OneTrustAssessmentQuestionResponsesCodec => | ||
questionResponses.length === 0 | ||
? createDefaultCodec(t.array(OneTrustAssessmentQuestionResponseCodec)) | ||
: questionResponses; | ||
|
||
// TODO: test the shit out of this | ||
const enrichRisksWithDefault = ( | ||
risks: OneTrustAssessmentQuestionRisksCodec, | ||
): OneTrustAssessmentQuestionRisksCodec => | ||
risks === null || risks.length === 0 | ||
? createDefaultCodec(t.array(OneTrustAssessmentQuestionRiskCodec)) | ||
: risks; | ||
|
||
// TODO: test the shit out of this | ||
export const enrichSectionsWithDefault = ( | ||
sections: OneTrustAssessmentSectionCodec[], | ||
): OneTrustAssessmentSectionCodec[] => | ||
sections.map((s) => ({ | ||
...s, | ||
questions: s.questions.map((q) => ({ | ||
...q, | ||
question: enrichQuestionWithDefault(q.question), | ||
questionResponses: enrichQuestionResponsesWithDefault( | ||
q.questionResponses, | ||
), | ||
risks: enrichRisksWithDefault(q.risks), | ||
})), | ||
submittedBy: | ||
s.submittedBy === null | ||
? createDefaultCodec(OneTrustAssessmentSectionSubmittedByCodec) | ||
: s.submittedBy, | ||
})); | ||
|
||
export const enrichPrimaryEntityDetailsWithDefault = ( | ||
primaryEntityDetails: OneTrustPrimaryEntityDetailsCodec, | ||
): OneTrustPrimaryEntityDetailsCodec => | ||
primaryEntityDetails.length === 0 | ||
? createDefaultCodec(OneTrustPrimaryEntityDetailsCodec) | ||
: primaryEntityDetails; |
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
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,100 @@ | ||
import * as t from 'io-ts'; | ||
import chai, { expect } from 'chai'; | ||
import deepEqualInAnyOrder from 'deep-equal-in-any-order'; | ||
|
||
import { createDefaultCodec } from '../createDefaultCodec'; | ||
|
||
chai.use(deepEqualInAnyOrder); | ||
|
||
describe('buildDefaultCodec', () => { | ||
it('should correctly build a default codec for null', () => { | ||
const result = createDefaultCodec(t.null); | ||
expect(result).to.equal(null); | ||
}); | ||
|
||
it('should correctly build a default codec for number', () => { | ||
const result = createDefaultCodec(t.number); | ||
expect(result).to.equal(null); | ||
}); | ||
|
||
it('should correctly build a default codec for boolean', () => { | ||
const result = createDefaultCodec(t.boolean); | ||
expect(result).to.equal(null); | ||
}); | ||
|
||
it('should correctly build a default codec for undefined', () => { | ||
const result = createDefaultCodec(t.undefined); | ||
expect(result).to.equal(undefined); | ||
}); | ||
|
||
it('should correctly build a default codec for string', () => { | ||
const result = createDefaultCodec(t.string); | ||
expect(result).to.equal(''); | ||
}); | ||
|
||
it('should correctly build a default codec for a union with null', () => { | ||
const result = createDefaultCodec(t.union([t.string, t.null])); | ||
// should default to null if the union contains null | ||
expect(result).to.equal(null); | ||
}); | ||
|
||
it('should correctly build a default codec for a union with type', () => { | ||
const result = createDefaultCodec( | ||
t.union([t.string, t.null, t.type({ name: t.string })]), | ||
); | ||
// should default to the type if the union contains a type | ||
expect(result).to.deep.equal({ name: '' }); | ||
}); | ||
|
||
it('should correctly build a default codec for a union without null', () => { | ||
const result = createDefaultCodec(t.union([t.string, t.number])); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.equal(''); | ||
}); | ||
|
||
it('should correctly build a default codec for an array of object types', () => { | ||
const result = createDefaultCodec( | ||
t.array(t.type({ name: t.string, age: t.number })), | ||
); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.deep.equalInAnyOrder([{ name: '', age: null }]); | ||
}); | ||
|
||
it('should correctly build a default codec for an array of object partials', () => { | ||
const result = createDefaultCodec( | ||
t.array(t.partial({ name: t.string, age: t.number })), | ||
); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.deep.equalInAnyOrder([{ name: '', age: null }]); | ||
}); | ||
|
||
it('should correctly build a default codec for an array of object intersections', () => { | ||
const result = createDefaultCodec( | ||
t.array( | ||
t.intersection([ | ||
t.partial({ name: t.string, age: t.number }), | ||
t.type({ city: t.string }), | ||
]), | ||
), | ||
); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.deep.equalInAnyOrder([{ name: '', age: null, city: '' }]); | ||
}); | ||
|
||
it('should correctly build a default codec for an array of strings', () => { | ||
const result = createDefaultCodec(t.array(t.string)); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.deep.equal([]); | ||
}); | ||
|
||
it('should correctly build a default codec for an intersection', () => { | ||
const result = createDefaultCodec( | ||
t.intersection([ | ||
t.type({ id: t.string, name: t.string }), | ||
t.partial({ age: t.number }), | ||
]), | ||
); | ||
// should default to the first value if the union does not contains null | ||
expect(result).to.deep.equalInAnyOrder({ id: '', name: '', age: null }); | ||
}); | ||
}); |
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
Oops, something went wrong.