orval
orval copied to clipboard
Support replacing types with custom ones when transforming the input
(Not using the standard issue template as this is a feature request)
I'm migrating from openapi-typescript to orval. In my current setup I've added what could be called "nominal types" - so for instance instead of type userId = number it would use type userId = UserId with the following implementation:
const UserIdSymbol = Symbol('UserId');
type UserId = NominalType<typeof UserIdSymbol>;
Then I have a transformer on the API TS generation that modifies all instances of a userId to use this custom type, and injects an import {UserId} from './types'
Such that we end up with something like:
// Generated types from openapi-typescript
import { UserId } from './custom-types';
// ...
export type UserModel = {
id: UserId;
// ...
};
Arguments about the use-cases of this approach and such aside, would this be possible with orval?
I'm trying something like this in the orval config:
input: {
override: {
transformer: (api) => {
// Just testing as an example
const schema = api.components?.schemas?.ExampleSchema;
if (schema && 'properties' in schema && schema.properties?.version && 'type' in schema.properties.version) {
schema.properties.userId.type = UserIdSymbol as any;
}
return api;
},
},
},
But this just generates the type userId: unknown - so ideally there would be a supported way to do this.
When using openapi-typescript I just transform the type and basically set it to "UserId", so that it then appears as the string content. And I inject the import statement at the top of the file manually (they have a way to inject anything at the top of the file)
I marked as question. I feel like what you want to do is possible with Orval but I have not done it myself. Hopefully someone who has done this can chime in like @soartec-lab ?
no, I haven't tried it too.
I have done a similar thing as OP with openapi-generator. Orval seems much more lightweight, but sadly the lack of this feature is a deal breaker for truly type-safe APIs.
@aurbano @Papooch
I inspected this.
If you change the type in the transformer, only primitive types are supported.
In orval, if the user input type is not a primitive type and not an object, it returns unknown as an unintended type. Becouse, orval cannot determine whether it is really an unknown type or whether the user intended to enter the type.
https://github.com/anymaniax/orval/blob/master/packages/core/src/getters/object.ts#L207
If there is a better way to determine the user-defined one, I will fix this issue.
I think the easiest way to solve this would be to create a new configuration option - something like customTypeMapping, which would take a map of openapi type to typescript type (or possibly the format field - which is how it works in openapi-generator).
Some other inspiration could be found in a similar feature for schema-first GraphQL for NestJS (see customScalarTypeMapping, and also notice the additionalHeader setting)
With that in mind, based on OP's example, I would suggest the following API:
Say the OpenAPI schema looks like this:
type: object
properties:
id:
type: string
format: 'x-user-id' # for example
name:
type: string
I would lean towards using the format field as the source for custom types, following the example of openapi-generator
The configuration could then look as follows:
customFormatMapping: {
'x-user-id': {
typeName: 'UserId', // use the type name as a string
importFrom: './custom-types' // optionally add an import (although a custom static header could be enough)
}
}