feat(fp): WP-05 — parse-don't-validate closure + MDX

Close the three remaining unvalidated `as <DomainType>` casts at the wire
boundary (intake-policy, big-register aantekening type, brief passage scope),
each replaced by a Result-returning parser with a rejection-case spec, plus
the Foundations/Parse, don't validate curriculum page.
This commit is contained in:
eho
2026-07-03 21:02:15 +02:00
parent 5d6a78d4ec
commit 34d34512b3
11 changed files with 2323 additions and 1994 deletions
@@ -0,0 +1,20 @@
import { describe, it, expect } from 'vitest';
import { parseAantekening } from './big-register.adapter';
describe('big-register.adapter parse boundary', () => {
it('parses known aantekening types', () => {
expect(parseAantekening({ type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' })).toEqual({
ok: true,
value: { type: 'Specialisme', omschrijving: 'x', datum: '2026-01-01' },
});
expect(parseAantekening({ type: 'Aantekening' })).toEqual({
ok: true,
value: { type: 'Aantekening', omschrijving: '', datum: '' },
});
});
it('rejects an unknown type', () => {
expect(parseAantekening({ type: 'Bogus' }).ok).toBe(false);
expect(parseAantekening({}).ok).toBe(false);
});
});
@@ -1,4 +1,5 @@
import { Injectable, inject, resource } from '@angular/core';
import { Result, ok, err } from '@shared/kernel/fp';
import { Aantekening, AantekeningType } from '../domain/registration';
import { ApiClient, AantekeningDto } from '@shared/infrastructure/api-client';
@@ -16,15 +17,30 @@ export class BigRegisterAdapter {
private client = inject(ApiClient);
aantekeningenResource() {
return resource({ loader: () => this.client.notes().then((ns) => ns.map(toAantekening)) });
return resource({
loader: () =>
this.client.notes().then((ns) => {
const out: Aantekening[] = [];
for (const n of ns) {
const parsed = parseAantekening(n);
if (!parsed.ok) throw new Error(parsed.error);
out.push(parsed.value);
}
return out;
}),
});
}
}
/** Map the wire DTO (all fields optional) onto our domain type. */
function toAantekening(n: AantekeningDto): Aantekening {
return {
const AANTEKENING_TYPES: readonly AantekeningType[] = ['Specialisme', 'Aantekening'];
/** Trust-boundary parse: an unrecognized type is an explicit Failure, never a silent cast. */
export function parseAantekening(n: AantekeningDto): Result<string, Aantekening> {
if (!n.type || !AANTEKENING_TYPES.includes(n.type as AantekeningType))
return err(`aantekening: unknown type ${n.type}`);
return ok({
type: n.type as AantekeningType,
omschrijving: n.omschrijving ?? '',
datum: n.datum ?? '',
};
});
}