refactor(lib): remove keys and just get a slug from the label

This commit is contained in:
Lewis Wynne 2026-03-23 17:02:46 +00:00
parent 7dc4bd8a11
commit 456e7e8feb
5 changed files with 26 additions and 30 deletions

View file

@ -1,6 +1,7 @@
import { XMLParser } from 'fast-xml-parser'; import { XMLParser } from 'fast-xml-parser';
import type { SpeciesData, CitizenshipData, LanguageData } from './types'; import type { SpeciesData, CitizenshipData, LanguageData } from './types';
import type { Template, RecordDef, FieldDef, SelectOption } from '../types'; import type { Template, RecordDef, FieldDef, SelectOption } from '../types';
import { slugify } from '../utils/slugify';
const parser = new XMLParser({ const parser = new XMLParser({
ignoreAttributes: false, 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[] { function parseOptions(field: any): SelectOption[] {
if (!field.option) return []; if (!field.option) return [];
return field.option.map((o: any) => ({ return field.option.map((o: any) => ({
@ -65,7 +62,6 @@ function parseOptions(field: any): SelectOption[] {
function parseField(raw: any): FieldDef { function parseField(raw: any): FieldDef {
const base = { const base = {
key: slugify(raw['@_label']),
label: raw['@_label'] label: raw['@_label']
}; };
const type = raw['@_type']; const type = raw['@_type'];

View file

@ -15,7 +15,7 @@ function makeTemplate(fields: any[]): Template {
describe('buildCharacterSchema', () => { describe('buildCharacterSchema', () => {
it('validates text fields as optional strings', () => { it('validates text fields as optional strings', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'pronouns', label: 'Pronouns', type: 'text' }]) makeTemplate([{ label: 'Pronouns', type: 'text' }])
); );
expect(schema.parse({})).toEqual({}); expect(schema.parse({})).toEqual({});
expect(schema.parse({ pronouns: 'She/her' })).toEqual({ pronouns: 'She/her' }); expect(schema.parse({ pronouns: 'She/her' })).toEqual({ pronouns: 'She/her' });
@ -24,7 +24,7 @@ describe('buildCharacterSchema', () => {
it('validates textarea fields as optional strings', () => { it('validates textarea fields as optional strings', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ key: 'distinguishing-features', label: 'Distinguishing Features', type: 'textarea' } { label: 'Distinguishing Features', type: 'textarea' }
]) ])
); );
expect(schema.parse({ 'distinguishing-features': 'Scar across left eye' })).toEqual({ expect(schema.parse({ 'distinguishing-features': 'Scar across left eye' })).toEqual({
@ -36,7 +36,7 @@ describe('buildCharacterSchema', () => {
it('validates list fields as optional strings', () => { it('validates list fields as optional strings', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ key: 'employment-history', label: 'Employment History', type: 'list' } { label: 'Employment History', type: 'list' }
]) ])
); );
expect(schema.parse({ 'employment-history': 'NanoTrasen Intern\nShaft Miner' })).toEqual({ expect(schema.parse({ 'employment-history': 'NanoTrasen Intern\nShaft Miner' })).toEqual({
@ -48,7 +48,7 @@ describe('buildCharacterSchema', () => {
it('validates date fields as optional strings', () => { it('validates date fields as optional strings', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ 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({ expect(schema.parse({ 'date-of-birth': 'March 15th, 2438' })).toEqual({
@ -60,7 +60,6 @@ describe('buildCharacterSchema', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ {
key: 'citizenship',
label: 'Citizenship', label: 'Citizenship',
type: 'select', type: 'select',
options: [{ value: 'biesel', label: 'Republic of Biesel' }] options: [{ value: 'biesel', label: 'Republic of Biesel' }]
@ -72,7 +71,7 @@ describe('buildCharacterSchema', () => {
it('validates number fields as optional numbers', () => { it('validates number fields as optional numbers', () => {
const schema = buildCharacterSchema( 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({ age: 30 })).toEqual({ age: 30 });
expect(schema.parse({})).toEqual({}); expect(schema.parse({})).toEqual({});
@ -80,21 +79,21 @@ describe('buildCharacterSchema', () => {
it('validates height as optional number', () => { it('validates height as optional number', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'height', label: 'Height', type: 'height' }]) makeTemplate([{ label: 'Height', type: 'height' }])
); );
expect(schema.parse({ height: 180 })).toEqual({ height: 180 }); expect(schema.parse({ height: 180 })).toEqual({ height: 180 });
}); });
it('validates weight as optional number', () => { it('validates weight as optional number', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'weight', label: 'Weight', type: 'weight' }]) makeTemplate([{ label: 'Weight', type: 'weight' }])
); );
expect(schema.parse({ weight: 75 })).toEqual({ weight: 75 }); expect(schema.parse({ weight: 75 })).toEqual({ weight: 75 });
}); });
it('validates name as optional string', () => { it('validates name as optional string', () => {
const schema = buildCharacterSchema( 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({ expect(schema.parse({ name: 'Ka\'Akaix\'Lak Zo\'ra' })).toEqual({
name: 'Ka\'Akaix\'Lak Zo\'ra' name: 'Ka\'Akaix\'Lak Zo\'ra'
@ -106,7 +105,6 @@ describe('buildCharacterSchema', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ {
key: 'spoken-languages',
label: 'Spoken Languages', label: 'Spoken Languages',
type: 'multi-select', type: 'multi-select',
options: [ options: [
@ -126,7 +124,6 @@ describe('buildCharacterSchema', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ {
key: 'opt-outs',
label: 'Opt-Outs', label: 'Opt-Outs',
type: 'checkbox', type: 'checkbox',
options: [ options: [
@ -145,7 +142,7 @@ describe('buildCharacterSchema', () => {
it('validates languages as optional string array', () => { it('validates languages as optional string array', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ key: 'spoken-languages', label: 'Spoken Languages', type: 'languages' } { label: 'Spoken Languages', type: 'languages' }
]) ])
); );
expect( expect(
@ -157,14 +154,14 @@ describe('buildCharacterSchema', () => {
it('validates species as optional string', () => { it('validates species as optional string', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'species', label: 'Species', type: 'species' }]) makeTemplate([{ label: 'Species', type: 'species' }])
); );
expect(schema.parse({ species: 'tajara' })).toEqual({ species: 'tajara' }); expect(schema.parse({ species: 'tajara' })).toEqual({ species: 'tajara' });
}); });
it('validates subspecies as optional string', () => { it('validates subspecies as optional string', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'subspecies', label: 'Subspecies', type: 'subspecies' }]) makeTemplate([{ label: 'Subspecies', type: 'subspecies' }])
); );
expect(schema.parse({ subspecies: 'zhan-khazan' })).toEqual({ expect(schema.parse({ subspecies: 'zhan-khazan' })).toEqual({
subspecies: 'zhan-khazan' subspecies: 'zhan-khazan'
@ -173,7 +170,7 @@ describe('buildCharacterSchema', () => {
it('validates citizenship type as optional string', () => { it('validates citizenship type as optional string', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'citizenship', label: 'Citizenship', type: 'citizenship' }]) makeTemplate([{ label: 'Citizenship', type: 'citizenship' }])
); );
expect(schema.parse({ citizenship: 'sol-alliance' })).toEqual({ expect(schema.parse({ citizenship: 'sol-alliance' })).toEqual({
citizenship: 'sol-alliance' citizenship: 'sol-alliance'
@ -183,10 +180,10 @@ describe('buildCharacterSchema', () => {
it('allows all fields to be missing', () => { it('allows all fields to be missing', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([ makeTemplate([
{ key: 'name', label: 'Name', type: 'text' }, { label: 'Name', type: 'text' },
{ key: 'height', label: 'Height', type: 'height' }, { label: 'Height', type: 'height' },
{ key: 'spoken-languages', label: 'Spoken Languages', type: 'languages' }, { label: 'Spoken Languages', type: 'languages' },
{ key: 'skin-color', label: 'Skin Color', type: 'text' } { label: 'Skin Color', type: 'text' }
]) ])
); );
expect(schema.parse({})).toEqual({}); expect(schema.parse({})).toEqual({});
@ -194,7 +191,7 @@ describe('buildCharacterSchema', () => {
it('rejects wrong types', () => { it('rejects wrong types', () => {
const schema = buildCharacterSchema( const schema = buildCharacterSchema(
makeTemplate([{ key: 'height', label: 'Height', type: 'height' }]) makeTemplate([{ label: 'Height', type: 'height' }])
); );
expect(() => schema.parse({ height: 'tall' })).toThrow(); expect(() => schema.parse({ height: 'tall' })).toThrow();
}); });

View file

@ -1,5 +1,6 @@
import { z } from 'zod'; import { z } from 'zod';
import type { FieldDef, Template } from './types'; import type { FieldDef, Template } from './types';
import { slugify } from './utils/slugify';
function zodForField(field: FieldDef): z.ZodTypeAny { function zodForField(field: FieldDef): z.ZodTypeAny {
switch (field.type) { switch (field.type) {
@ -29,7 +30,7 @@ export function buildCharacterSchema(template: Template): z.ZodObject<Record<str
const shape: Record<string, z.ZodTypeAny> = {}; const shape: Record<string, z.ZodTypeAny> = {};
for (const record of template.records) { for (const record of template.records) {
for (const field of record.fields) { for (const field of record.fields) {
shape[field.key] = zodForField(field); shape[slugify(field.label)] = zodForField(field);
} }
} }
return z.object(shape).partial(); return z.object(shape).partial();

View file

@ -4,7 +4,6 @@ export interface SelectOption {
} }
export interface BaseFieldDef { export interface BaseFieldDef {
key: string;
label: string; label: string;
} }

3
src/lib/utils/slugify.ts Normal file
View file

@ -0,0 +1,3 @@
export function slugify(label: string): string {
return label.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
}