import {z} from 'zod' export type ReferenceDate = string; export type GetApiRuleSpecArgs = {service: string, law: string, referenceDate: ReferenceDate}; export type GetApiRuleSpecOptions = {baseUrl?: string | undefined, fetch?: typeof fetch | undefined}; export const reference = z.object({law: z.string(), article: z.string(), url: z.string()}); export const typeSpec = z.object({type: z.string(), unit: z.string().nullable().optional(), precision: z.number().int().nullable().optional(), min: z.number().nullable().optional(), max: z.number().nullable().optional()}); export const temporal = z.object({type: z.string(), period_type: z.string().nullable().optional(), reference: z.string().nullable().optional(), immutable_after: z.string().nullable().optional()}); export const parameterField = z.object({name: z.string(), description: z.string(), type: z.string(), type_spec: typeSpec.optional(), temporal: temporal.optional(), required: z.boolean().nullable().optional()}); export const actionValues = z.union([z.array(z.lazy(() => actionValue)), z.unknown()]); export const condition = z.object({test: z.lazy(() => action.optional()), then: z.lazy(() => actionValue.optional()), else: z.lazy(() => actionValue.optional())}); export const action: z.ZodType = z.object({output: z.string(), value: z.lazy(() => actionValue.optional()), operation: z.string().nullable().optional(), subject: z.string().nullable().optional(), unit: z.string().nullable().optional(), combine: z.string().nullable().optional(), values: actionValues.optional(), conditions: z.array(condition).optional()}); export const actionValue: z.ZodType = z.union([action, z.unknown()]); export const selectField = z.object({name: z.string(), description: z.string(), type: z.string(), value: actionValue}); export const sourceReference = z.object({source_type: z.string().optional(), table: z.string().optional(), field: z.string().nullable().optional(), fields: z.array(z.string()).nullable().optional(), select_on: z.array(selectField).optional(), wallet_attribute: z.string().optional()}); export const parameter = z.object({name: z.string(), reference: z.string()}); export const serviceReference = z.object({service: z.string(), field: z.string(), law: z.string(), parameters: z.array(parameter).optional()}); export const sourceField = z.object({name: z.string(), description: z.string(), type: z.string(), type_spec: typeSpec.optional(), temporal: temporal.optional(), required: z.boolean().nullable().optional(), source_reference: sourceReference.optional(), service_reference: serviceReference.optional()}); export const inputField = z.object({name: z.string(), description: z.string(), type: z.string(), type_spec: typeSpec.optional(), temporal: temporal.optional(), required: z.boolean().nullable().optional(), service_reference: serviceReference}); export const outputField = z.object({name: z.string(), description: z.string(), type: z.string(), type_spec: typeSpec.optional(), temporal: temporal.optional(), required: z.boolean().nullable().optional(), citizen_relevance: z.string()}); export const applyEvent = z.object({type: z.string(), filter: z.record(z.string(), z.unknown())}); export const update = z.object({method: z.string(), mapping: z.record(z.string(), z.string())}); export const apply = z.object({name: z.string(), aggregate: z.string(), events: z.array(applyEvent), update: z.array(update)}); export const properties = z.object({parameters: z.array(parameterField).optional(), sources: z.array(sourceField).optional(), input: z.array(inputField).optional(), output: z.array(outputField).optional(), definitions: z.record(z.string(), z.unknown()).optional(), applies: z.array(apply).optional()}); export const actionRequirement = z.union([z.lazy(() => requirement), action]); export const requirement: z.ZodType = z.object({all: z.array(actionRequirement).nullable().optional(), or: z.array(actionRequirement).nullable().optional()}); export const ruleSpec = z.object({uuid: z.string(), name: z.string(), law: z.string(), law_type: z.string().nullable().optional(), legal_character: z.string().nullable().optional(), decision_type: z.string().nullable().optional(), discoverable: z.string().nullable().optional(), valid_from: z.string(), service: z.string(), description: z.string(), references: z.array(reference).optional(), properties: properties, requirements: z.array(requirement).optional(), actions: z.array(action).optional()}); export const getApiRuleSpecResponse = z.object({data: ruleSpec}); 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 }; /** * Get rule spec * * GET /rule-spec */ export const getApiRuleSpec = async (args: GetApiRuleSpecArgs, options?: GetApiRuleSpecOptions) => { const {baseUrl = "https://rvig-engine.bzlg.apps.digilab.network/v0", fetch: fetchFn = fetch} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.service != null) query.append("service", String(args.service)) if (args.law != null) query.append("law", String(args.law)) if (args.referenceDate != null) query.append("referenceDate", String(args.referenceDate)) const url = `${baseUrl.replace(/\/+$/, '')}/rule-spec${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(getApiRuleSpecResponse, await res.json()) };