sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.65k stars 150 forks source link

Metadata missing in custom types #831

Closed spinlud closed 3 months ago

spinlud commented 3 months ago

Hi, we are currently migrating from version 0.24 to 0.32. We have a custom type StringEnumArray defined as follows:

//typebox-custom.ts

type CustomTypeBuilderMeta = { type: 'decimal128' } | { type: 'stringEnumArray'; values: Set<string> };

const Meta = Symbol('Meta');

class CustomTypeBuilder extends TypeBuilder {

    public readonly Meta = Meta;

    constructor() {
        super();
    }

    public getMeta(item: any): CustomTypeBuilderMeta {
        return item[this.Meta];
    }

    public StringEnumArray<T extends string[]>(values: [...T], opts: { examples?: any; description?: string } = {}) {
        return super.Unsafe<Array<T[number]>>({
            type: 'string',
            examples: [...opts.examples?? [], ...values],
            description: opts.description,
            [this.Meta]: { type: 'stringEnumArray', values: new Set(values) } as CustomTypeBuilderMeta,
        });
    }
}

export const Type = new CustomTypeBuilder();

This allows custom validation logic in Fastify pre handler plugin, inspecting the Meta property:

import {Type} from './typebox-custom';
// [...]

export const preHandlerPlugin = fp(
    (fastify, opts, done) => {
        fastify.addHook('preHandler', async (request, reply) => {
            // [...]

            const meta = Type.getMeta(spec);

            if (meta?.type === 'stringEnumArray') {
                const values = meta.values;
                const parts = (o as string).split(',');
                // [...]
            }
        }
    }
);

Migrating to version 0.32 we are noticing that the metadata is not longer included, also the TypeBox.Kind symbol is not longer Unsafe but Intersect:

Before: Screenshot 2024-04-16 at 09 09 30

After: Screenshot 2024-04-16 at 09 13 07

We have tried to replace TypeBuilder with both JsonTypeBuilder and JavaScriptTypeBuilder, but the issue is the same. Any suggestion on how to properly migrate this custom type to latest Typebox version?

sinclairzx81 commented 3 months ago

@spinlud Hi,

There's been a few updates to the TypeBuilder over the past minor revisions of the library, however this functionality shouldn't have changed too much. You will want to extend either the JsonTypeBuilder or JavaScriptTypeBuilder (the recommendation would be the JsonTypeBuilder if limiting to Fastify compatible types)

I've run the following and everything seems to be returning correctly.

import { JsonTypeBuilder } from '@sinclair/typebox'

type CustomTypeBuilderMeta = { type: 'decimal128' } | { type: 'stringEnumArray'; values: Set<string> };

const Meta = Symbol('Meta');

export class CustomTypeBuilder extends JsonTypeBuilder {
  public readonly Meta = Meta;
  constructor() {
    super();
  }
  public getMeta(item: any): CustomTypeBuilderMeta {
    return item[this.Meta];
  }
  public StringEnumArray<T extends string[]>(values: [...T], opts: { examples?: any; description?: string } = {}) {
    return super.Unsafe<Array<T[number]>>({
      type: 'string',
      examples: [...opts.examples ?? [], ...values],
      description: opts.description,
      [this.Meta]: { type: 'stringEnumArray', values: new Set(values) } as CustomTypeBuilderMeta,
    });
  }
}

export const Type = new CustomTypeBuilder();

const A = Type.StringEnumArray(['A', 'B'])

console.log(A)
// {
//   type: 'string',
//   examples: [ 'A', 'B' ],
//   description: undefined,
//   [Symbol(Meta)]: { type: 'stringEnumArray', values: Set(2) { 'A', 'B' } },
//   [Symbol(TypeBox.Kind)]: 'Unsafe'
// }

I see the Kind and Meta symbols in the returned schematics (which looks correct).

Intersect and Composite

Looking at your screenshots, it looks like you may be composing this type with Intersect somewhere (as noted by the allOf schematic). Just note that between 0.25.0 and 0.32.0, the schematics for Intersect have changed to the allOf representation, but there was a fallback type Composite added which retained the 0.25.0 schematics. Both of these types should validate the same, but the schematics are different. You can try replace instances of Intersect with Composite if you wish to retain previous version schematics.

Let me know if this helps Cheers S

spinlud commented 3 months ago

Thank you very much @sinclairzx81, it helped! Closing