Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support different content-type typed reply with TypeProvider #4360

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
311 changes: 311 additions & 0 deletions test/types/type-provider.test-d.ts
Expand Up @@ -231,6 +231,40 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
}
))

// -------------------------------------------------------------------
// TypeBox Reply Type (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: Type.String()
},
'application/json': {
schema: Type.Object({
msg: Type.String()
})
}
}
},
500: Type.Object({
error: Type.String()
})
}
}
},
async (_, res) => {
res.send('hello')
res.send({ msg: 'hello' })
res.send({ error: 'error' })
}
))

// -------------------------------------------------------------------
// TypeBox Reply Type: Non Assignable
// -------------------------------------------------------------------
Expand All @@ -253,6 +287,38 @@ expectError(server.withTypeProvider<TypeBoxProvider>().get(
}
))

// -------------------------------------------------------------------
// TypeBox Reply Type: Non Assignable (Different Content-types)
// -------------------------------------------------------------------

expectError(server.withTypeProvider<TypeBoxProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: Type.String()
},
'application/json': {
schema: Type.Object({
msg: Type.String()
})
}
}
},
500: Type.Object({
error: Type.String()
})
}
}
},
async (_, res) => {
res.send(false)
}
))

// -------------------------------------------------------------------
// TypeBox Reply Return Type
// -------------------------------------------------------------------
Expand Down Expand Up @@ -280,6 +346,43 @@ expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
}
))

// -------------------------------------------------------------------
// TypeBox Reply Return Type (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: Type.String()
},
'application/json': {
schema: Type.Object({
msg: Type.String()
})
}
}
},
500: Type.Object({
error: Type.String()
})
}
}
},
async (_, res) => {
const option = 1 as 1 | 2 | 3
switch (option) {
case 1: return 'hello'
case 2: return { msg: 'hello' }
case 3: return { error: 'error' }
}
}
))

// -------------------------------------------------------------------
// TypeBox Reply Return Type: Non Assignable
// -------------------------------------------------------------------
Expand All @@ -302,6 +405,38 @@ expectError(server.withTypeProvider<TypeBoxProvider>().get(
}
))

// -------------------------------------------------------------------
// TypeBox Reply Return Type: Non Assignable (Different Content-types)
// -------------------------------------------------------------------

expectError(server.withTypeProvider<TypeBoxProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: Type.String()
},
'application/json': {
schema: Type.Object({
msg: Type.String()
})
}
}
},
500: Type.Object({
error: Type.String()
})
}
}
},
async (_, res) => {
return false
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type
// -------------------------------------------------------------------
Expand All @@ -324,6 +459,36 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
(_, res) => {
res.send('hello')
res.send({ msg: 'hello' })
res.send({ error: 'error' })
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type: Non Assignable
// -------------------------------------------------------------------
Expand All @@ -344,6 +509,34 @@ expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type: Non Assignable (Different Content-types)
// -------------------------------------------------------------------

expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
async (_, res) => {
res.send(false)
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type Return
// -------------------------------------------------------------------
Expand All @@ -368,6 +561,40 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
}
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type Return (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
async (_, res) => {
const option = 1 as 1 | 2 | 3
switch (option) {
case 1: return 'hello'
case 2: return { msg: 'hello' }
case 3: return { error: 'error' }
}
}
))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type Return: Non Assignable
// -------------------------------------------------------------------
Expand Down Expand Up @@ -399,6 +626,34 @@ expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get('/', {
return { foo: 555 }
}))

// -------------------------------------------------------------------
// JsonSchemaToTs Reply Type Return: Non Assignable (Different Content-types)
// -------------------------------------------------------------------

expectError(server.withTypeProvider<JsonSchemaToTsProvider>().get(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
async (_, res) => {
return false
}
))

// -------------------------------------------------------------------
// Reply Type Override
// -------------------------------------------------------------------
Expand All @@ -419,6 +674,34 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: b
}
))

// -------------------------------------------------------------------
// Reply Type Override (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: boolean}>(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
async (_, res) => {
res.send(true)
}
))

// -------------------------------------------------------------------
// Reply Type Return Override
// -------------------------------------------------------------------
Expand All @@ -439,6 +722,34 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: b
}
))

// -------------------------------------------------------------------
// Reply Type Return Override (Different Content-types)
// -------------------------------------------------------------------

expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: boolean}>(
'/',
{
schema: {
response: {
200: {
content: {
'text/string': {
schema: { type: 'string' }
},
'application/json': {
schema: { type: 'object', properties: { msg: { type: 'string' } } }
}
}
},
500: { type: 'object', properties: { error: { type: 'string' } } }
} as const
}
},
async (_, res) => {
return true
}
))

// -------------------------------------------------------------------
// FastifyPlugin: Auxiliary
// -------------------------------------------------------------------
Expand Down
8 changes: 5 additions & 3 deletions types/type-provider.d.ts
Expand Up @@ -60,10 +60,12 @@ export interface ResolveFastifyRequestType<TypeProvider extends FastifyTypeProvi
// FastifyReplyType
// -----------------------------------------------------------------------------------------------

// Resolves the Reply type by taking a union of response status codes
// Resolves the Reply type by taking a union of response status codes and content-types
type ResolveReplyFromSchemaCompiler<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema> = {
[K in keyof SchemaCompiler['response']]: CallTypeProvider<TypeProvider, SchemaCompiler['response'][K]>
} extends infer Result ? Result[keyof Result] : unknown
[K1 in keyof SchemaCompiler['response']]: SchemaCompiler['response'][K1] extends { content: { [keyof: string]: { schema: unknown } } } ? ({
[K2 in keyof SchemaCompiler['response'][K1]['content']]: CallTypeProvider<TypeProvider, SchemaCompiler['response'][K1]['content'][K2]['schema']>
} extends infer Result ? Result[keyof Result] : unknown) : CallTypeProvider<TypeProvider, SchemaCompiler['response'][K1]>
} extends infer Result ? Result[keyof Result] : unknown;

// The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment
export type FastifyReplyType<Reply = unknown> = Reply
Expand Down