From 456e7e8febacd8a9f97af06b8e6fccb403cec45a Mon Sep 17 00:00:00 2001 From: lew Date: Mon, 23 Mar 2026 17:02:46 +0000 Subject: [PATCH] refactor(lib): remove keys and just get a slug from the label --- src/lib/data/parse.ts | 6 +----- src/lib/schema.test.ts | 43 +++++++++++++++++++--------------------- src/lib/schema.ts | 3 ++- src/lib/types.ts | 1 - src/lib/utils/slugify.ts | 3 +++ 5 files changed, 26 insertions(+), 30 deletions(-) create mode 100644 src/lib/utils/slugify.ts diff --git a/src/lib/data/parse.ts b/src/lib/data/parse.ts index eaac11d..8ef3ddc 100644 --- a/src/lib/data/parse.ts +++ b/src/lib/data/parse.ts @@ -1,6 +1,7 @@ import { XMLParser } from 'fast-xml-parser'; import type { SpeciesData, CitizenshipData, LanguageData } from './types'; import type { Template, RecordDef, FieldDef, SelectOption } from '../types'; +import { slugify } from '../utils/slugify'; const parser = new XMLParser({ ignoreAttributes: false, @@ -51,10 +52,6 @@ export function parseLanguages(xml: string): LanguageData[] { })); } -function slugify(label: string): string { - return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); -} - function parseOptions(field: any): SelectOption[] { if (!field.option) return []; return field.option.map((o: any) => ({ @@ -65,7 +62,6 @@ function parseOptions(field: any): SelectOption[] { function parseField(raw: any): FieldDef { const base = { - key: slugify(raw['@_label']), label: raw['@_label'] }; const type = raw['@_type']; diff --git a/src/lib/schema.test.ts b/src/lib/schema.test.ts index 1171314..ece1e6f 100644 --- a/src/lib/schema.test.ts +++ b/src/lib/schema.test.ts @@ -15,7 +15,7 @@ function makeTemplate(fields: any[]): Template { describe('buildCharacterSchema', () => { it('validates text fields as optional strings', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'pronouns', label: 'Pronouns', type: 'text' }]) + makeTemplate([{ label: 'Pronouns', type: 'text' }]) ); expect(schema.parse({})).toEqual({}); expect(schema.parse({ pronouns: 'She/her' })).toEqual({ pronouns: 'She/her' }); @@ -24,7 +24,7 @@ describe('buildCharacterSchema', () => { it('validates textarea fields as optional strings', () => { const schema = buildCharacterSchema( makeTemplate([ - { key: 'distinguishing-features', label: 'Distinguishing Features', type: 'textarea' } + { label: 'Distinguishing Features', type: 'textarea' } ]) ); expect(schema.parse({ 'distinguishing-features': 'Scar across left eye' })).toEqual({ @@ -36,7 +36,7 @@ describe('buildCharacterSchema', () => { it('validates list fields as optional strings', () => { const schema = buildCharacterSchema( makeTemplate([ - { key: 'employment-history', label: 'Employment History', type: 'list' } + { label: 'Employment History', type: 'list' } ]) ); expect(schema.parse({ 'employment-history': 'NanoTrasen Intern\nShaft Miner' })).toEqual({ @@ -48,7 +48,7 @@ describe('buildCharacterSchema', () => { it('validates date fields as optional strings', () => { const schema = buildCharacterSchema( makeTemplate([ - { key: 'date-of-birth', label: 'Date of Birth', type: 'date', placeholder: 'March 15th, 2438' } + { label: 'Date of Birth', type: 'date', placeholder: 'March 15th, 2438' } ]) ); expect(schema.parse({ 'date-of-birth': 'March 15th, 2438' })).toEqual({ @@ -60,8 +60,7 @@ describe('buildCharacterSchema', () => { const schema = buildCharacterSchema( makeTemplate([ { - key: 'citizenship', - label: 'Citizenship', + label: 'Citizenship', type: 'select', options: [{ value: 'biesel', label: 'Republic of Biesel' }] } @@ -72,7 +71,7 @@ describe('buildCharacterSchema', () => { it('validates number fields as optional numbers', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'age', label: 'Age', type: 'number', min: 0, max: 999 }]) + makeTemplate([{ label: 'Age', type: 'number', min: 0, max: 999 }]) ); expect(schema.parse({ age: 30 })).toEqual({ age: 30 }); expect(schema.parse({})).toEqual({}); @@ -80,21 +79,21 @@ describe('buildCharacterSchema', () => { it('validates height as optional number', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'height', label: 'Height', type: 'height' }]) + makeTemplate([{ label: 'Height', type: 'height' }]) ); expect(schema.parse({ height: 180 })).toEqual({ height: 180 }); }); it('validates weight as optional number', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'weight', label: 'Weight', type: 'weight' }]) + makeTemplate([{ label: 'Weight', type: 'weight' }]) ); expect(schema.parse({ weight: 75 })).toEqual({ weight: 75 }); }); it('validates name as optional string', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'name', label: 'Name', type: 'text' }]) + makeTemplate([{ label: 'Name', type: 'text' }]) ); expect(schema.parse({ name: 'Ka\'Akaix\'Lak Zo\'ra' })).toEqual({ name: 'Ka\'Akaix\'Lak Zo\'ra' @@ -106,8 +105,7 @@ describe('buildCharacterSchema', () => { const schema = buildCharacterSchema( makeTemplate([ { - key: 'spoken-languages', - label: 'Spoken Languages', + label: 'Spoken Languages', type: 'multi-select', options: [ { value: 'tau-ceti-basic', label: 'Tau Ceti Basic' }, @@ -126,8 +124,7 @@ describe('buildCharacterSchema', () => { const schema = buildCharacterSchema( makeTemplate([ { - key: 'opt-outs', - label: 'Opt-Outs', + label: 'Opt-Outs', type: 'checkbox', options: [ { value: 'no-borg', label: 'Do Not Borgify' }, @@ -145,7 +142,7 @@ describe('buildCharacterSchema', () => { it('validates languages as optional string array', () => { const schema = buildCharacterSchema( makeTemplate([ - { key: 'spoken-languages', label: 'Spoken Languages', type: 'languages' } + { label: 'Spoken Languages', type: 'languages' } ]) ); expect( @@ -157,14 +154,14 @@ describe('buildCharacterSchema', () => { it('validates species as optional string', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'species', label: 'Species', type: 'species' }]) + makeTemplate([{ label: 'Species', type: 'species' }]) ); expect(schema.parse({ species: 'tajara' })).toEqual({ species: 'tajara' }); }); it('validates subspecies as optional string', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'subspecies', label: 'Subspecies', type: 'subspecies' }]) + makeTemplate([{ label: 'Subspecies', type: 'subspecies' }]) ); expect(schema.parse({ subspecies: 'zhan-khazan' })).toEqual({ subspecies: 'zhan-khazan' @@ -173,7 +170,7 @@ describe('buildCharacterSchema', () => { it('validates citizenship type as optional string', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'citizenship', label: 'Citizenship', type: 'citizenship' }]) + makeTemplate([{ label: 'Citizenship', type: 'citizenship' }]) ); expect(schema.parse({ citizenship: 'sol-alliance' })).toEqual({ citizenship: 'sol-alliance' @@ -183,10 +180,10 @@ describe('buildCharacterSchema', () => { it('allows all fields to be missing', () => { const schema = buildCharacterSchema( makeTemplate([ - { key: 'name', label: 'Name', type: 'text' }, - { key: 'height', label: 'Height', type: 'height' }, - { key: 'spoken-languages', label: 'Spoken Languages', type: 'languages' }, - { key: 'skin-color', label: 'Skin Color', type: 'text' } + { label: 'Name', type: 'text' }, + { label: 'Height', type: 'height' }, + { label: 'Spoken Languages', type: 'languages' }, + { label: 'Skin Color', type: 'text' } ]) ); expect(schema.parse({})).toEqual({}); @@ -194,7 +191,7 @@ describe('buildCharacterSchema', () => { it('rejects wrong types', () => { const schema = buildCharacterSchema( - makeTemplate([{ key: 'height', label: 'Height', type: 'height' }]) + makeTemplate([{ label: 'Height', type: 'height' }]) ); expect(() => schema.parse({ height: 'tall' })).toThrow(); }); diff --git a/src/lib/schema.ts b/src/lib/schema.ts index 3dbba0e..3ed15db 100644 --- a/src/lib/schema.ts +++ b/src/lib/schema.ts @@ -1,5 +1,6 @@ import { z } from 'zod'; import type { FieldDef, Template } from './types'; +import { slugify } from './utils/slugify'; function zodForField(field: FieldDef): z.ZodTypeAny { switch (field.type) { @@ -29,7 +30,7 @@ export function buildCharacterSchema(template: Template): z.ZodObject = {}; for (const record of template.records) { for (const field of record.fields) { - shape[field.key] = zodForField(field); + shape[slugify(field.label)] = zodForField(field); } } return z.object(shape).partial(); diff --git a/src/lib/types.ts b/src/lib/types.ts index d8be5fa..b42891c 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -4,7 +4,6 @@ export interface SelectOption { } export interface BaseFieldDef { - key: string; label: string; } diff --git a/src/lib/utils/slugify.ts b/src/lib/utils/slugify.ts new file mode 100644 index 0000000..0a822e7 --- /dev/null +++ b/src/lib/utils/slugify.ts @@ -0,0 +1,3 @@ +export function slugify(label: string): string { + return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); +}