import {z} from 'zod' export type DrivingLicenceNumber = string; export type IncludeCPC = boolean; export type IncludeTacho = boolean; export type AcceptPartialResponse = 'true' | 'false' | 'onConditionCPCAvailable' | 'onConditionTachoAvailable'; export type DrivingLicenceRequest = {drivingLicenceNumber: DrivingLicenceNumber, includeCPC?: IncludeCPC | undefined, includeTacho?: IncludeTacho | undefined, acceptPartialResponse?: AcceptPartialResponse | undefined}; export type CreateApiV1DrivingLicencesRetrieveArgs = {body: DrivingLicenceRequest}; export type CreateApiV1DrivingLicencesRetrieveOptions = {baseUrl?: string | undefined, jwtAuth?: string | undefined, fetch?: typeof fetch | undefined}; export const drivingLicenceNumber = z.string().min(5).max(16); export const firstNames = z.string(); export const lastName = z.string(); export const gender = z.enum(["Male", "Female"]); export const date = z.string(); export const unstructuredAddress = z.object({line1: z.string().min(1).max(45).optional(), line2: z.string().max(45).optional(), line3: z.string().max(45).optional(), line4: z.string().max(45).optional(), line5: z.string().max(45).optional(), postcode: z.string().optional()}); export const address = z.object({unstructuredAddress: unstructuredAddress.optional()}); export const driver = z.object({drivingLicenceNumber: drivingLicenceNumber, firstNames: firstNames.optional(), lastName: lastName.optional(), gender: gender.optional(), dateOfBirth: date.optional(), address: address.optional(), disqualifiedUntil: date.optional(), disqualifiedForLife: z.boolean().optional(), disqualifiedPendingSentence: z.boolean().optional()}); export const licenceType = z.enum(["Provisional", "Full"]); export const licenceStatus = z.enum(["Valid", "Disqualified", "Revoked", "Surrendered", "Expired", "Exchanged", "Refused"]); export const statusQualifier = z.enum(["For re-assessment only", "Photo licence invitation sent", "Short period disqualification", "Until test passed", "Until extended test passed", "Pending sentence", "For life"]); export const licence = z.object({type: licenceType, status: licenceStatus, statusQualifier: statusQualifier.optional()}); export const categoryCode = z.string(); export const categoryLegalLiteral = z.string(); export const categoryType = z.enum(["Provisional", "Full"]); export const restrictionCode = z.string(); export const entitlementRecord = z.object({categoryCode: categoryCode.optional(), categoryLegalLiteral: categoryLegalLiteral.optional(), categoryType: categoryType.optional(), fromDate: date.optional(), expiryDate: date.optional(), restrictions: z.array(z.object({restrictionCode: restrictionCode, restrictionLiteral: z.string().optional()})).optional()}); export const disqualificationType = z.enum(["Disqualified until Test Pass", "Disqualified until Extended Test Pass", "Revoked until Test Pass", "Revoked test passed", "Extended test passed", "Test passed", "Appeal against Revocation until Test Pass", "Appeal against Disqualification until Extended Test Pass", "Appeal against Disqualification until Test Pass"]); export const years = z.number().int(); export const months = z.number().int(); export const days = z.number().int(); export const disqualification = z.object({type: disqualificationType.optional(), forLife: z.boolean().optional(), years: years.optional(), months: months.optional(), days: days.optional(), startDate: date.optional(), disqualifiedPendingSentence: z.boolean().optional()}); export const offenceCode = z.string(); export const endorsements = z.object({disqualification: disqualification.optional(), offenceCode: offenceCode.optional(), offenceLegalLiteral: z.string().optional(), offenceDate: date.optional(), convictionDate: date.optional(), disqualificationRemovalDate: date.optional(), disqualificationReimposedDate: date.optional(), disqualificationSuspendedPendingAppealDate: date.optional(), sentenceDate: date.optional(), disqualifiedPendingSentence: z.boolean().optional(), penaltyPoints: z.number().int().optional(), penaltyPointsExpiryDate: date.optional()}); export const testPass = z.object({categoryCode: z.string().optional(), categoryLegalLiteral: z.string().optional(), testDate: date.optional(), status: z.string().optional(), withAutomaticTransmission: z.boolean().optional()}); export const token = z.object({validFromDate: date.optional(), validToDate: date.optional(), issueNumber: z.string().optional()}); export const national = z.boolean(); export const cpc = z.object({lgvValidTo: date.optional(), pcvValidTo: date.optional(), national: national.optional()}); export const cpcResponse = z.object({cpcs: z.array(cpc).optional()}); export const tachoCard = z.object({cardNumber: z.string().optional(), cardStatus: z.string().optional(), cardExpiryDate: date.optional(), cardStartOfValidityDate: date.optional()}); export const holder = z.object({tachoCards: z.array(tachoCard).optional()}); export const error = z.object({status: z.string().optional(), title: z.string().optional(), code: z.string().optional(), detail: z.string().optional()}); export const errors = z.array(error); export const viewDriverResponse = z.object({driver: driver.optional(), licence: licence.optional(), entitlement: z.array(entitlementRecord).optional(), endorsements: z.array(endorsements).optional(), testPass: z.array(testPass).optional(), token: token.optional(), cpc: cpcResponse.optional(), holder: holder.optional(), errors: errors.optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Returns driver data for the supplied driving licence number, including licence details. Optionally returns CPC and tachograph data * * POST /v1/driving-licences/retrieve * * Also accepts auth: apiKeyAuth */ export const createApiV1DrivingLicencesRetrieve = async (args: CreateApiV1DrivingLicencesRetrieveArgs, options?: CreateApiV1DrivingLicencesRetrieveOptions) => { const {baseUrl = "https://driver-vehicle-licensing.api.gov.uk/full-driver-enquiry", fetch: fetchFn = fetch, jwtAuth} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (jwtAuth !== undefined) headers.set("Authorization", jwtAuth) const url = `${baseUrl.replace(/\/+$/, '')}/v1/driving-licences/retrieve${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(viewDriverResponse, await res.json()) };