Skip to content

Commit

Permalink
chore: add checks namespace for all if statements in TypeScript(exten…
Browse files Browse the repository at this point in the history
…ds x)
  • Loading branch information
stijnvanhulle committed Nov 26, 2023
1 parent 358de1f commit 68cd9c3
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 65 deletions.
9 changes: 5 additions & 4 deletions packages/swagger/src/infer/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { expectTypeOf } from 'expect-type'

import type { oas } from '../../mocks/oas.ts'
import type { Infer, MethodMap, Model, RequestParams, Response } from './index.ts'
import type { MethodMap, Model, Parse, PathMap, RequestParams, Response } from './index.ts'

describe('swagger infer', () => {
type Oas = Infer<typeof oas>
// type Paths = keyof PathMap<Oas>
describe('swagger parse', () => {
type Oas = Parse<typeof oas>
type Paths = keyof PathMap<Oas>
type Methods = keyof MethodMap<Oas, '/pet'>

type UserModel = Model<Oas, 'User'>
type UserRequestParams = RequestParams<Oas, '/pet', 'post'>
type UserResponse = Response<Oas, '/pet', 'post', '200'>
test('types', () => {
expectTypeOf<Paths>().not.toBeUndefined()
expectTypeOf<Methods>().toMatchTypeOf<'post' | 'put'>()
expectTypeOf<UserModel>().toMatchTypeOf<{ username?: string | undefined }>()
expectTypeOf<UserRequestParams['json']>().toMatchTypeOf<{ status?: 'available' | 'pending' | 'sold' | undefined }>()
Expand Down
2 changes: 1 addition & 1 deletion packages/swagger/src/infer/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// based on https://github.com/ardatan/feTS/tree/master

export type { Infer } from './infer.ts'
export type { MethodMap, PathMap, StatusMap } from './mappers.ts'
export type { Model } from './model.ts'
export type { Parse } from './parse.ts'
export type { RequestParams } from './requestParams.ts'
export type { Response } from './response.ts'
36 changes: 23 additions & 13 deletions packages/swagger/src/infer/mappers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/no-namespace */
import type { Fn, Pipe, Tuples } from 'hotscript'
import type {
FromSchema,
JSONSchema,
} from 'json-schema-to-ts'
import type { OASDocument } from 'oas/rmoas.types'

namespace Checks {
export type Required = { required: true }

export type Schemas = {
schema: JSONSchema
}
export type Enum = { type: JSONSchemaTypeName; enum?: any[] }
export type Parameters = { in: string; required?: boolean }[]
export type SingleParameter<TParamType> = [{ in: TParamType; required?: true }]
export type Responses = { responses: any }
}

export type PathMap<TOAS extends OASDocument> = TOAS['paths']

interface ParamPropMap {
Expand All @@ -26,31 +40,27 @@ type ParamObj<
TParameter extends {
name: string
},
> = TParameter extends { required: true } ? {
[TName in TParameter['name']]: TParameter extends {
schema: JSONSchema
} ? FromSchema<TParameter['schema']>
: TParameter extends { type: JSONSchemaTypeName; enum?: any[] } ? FromSchema<{
> = TParameter extends Checks.Required ? {
[TName in TParameter['name']]: TParameter extends Checks.Schemas ? FromSchema<TParameter['schema']>
: TParameter extends Checks.Enum ? FromSchema<{
type: TParameter['type']
enum: TParameter['enum']
}>
: unknown
}
: {
[TName in TParameter['name']]?: TParameter extends {
schema: JSONSchema
} ? FromSchema<TParameter['schema']>
: TParameter extends { type: JSONSchemaTypeName; enum?: any[] } ? FromSchema<{
[TName in TParameter['name']]?: TParameter extends Checks.Schemas ? FromSchema<TParameter['schema']>
: TParameter extends Checks.Enum ? FromSchema<{
type: TParameter['type']
enum: TParameter['enum']
}>
: unknown
}

interface ParamToRequestParam<TParameters extends { in: string; required?: boolean }[]> extends Fn {
interface ParamToRequestParam<TParameters extends Checks.Parameters> extends Fn {
return: this['arg0'] extends { name: string; in: infer TParamType }
// If there is any required parameter for this parameter type, make that parameter type required
? TParameters extends [{ in: TParamType; required?: true }] ? {
? TParameters extends Checks.SingleParameter<TParamType> ? {
[
TKey in TParamType extends keyof ParamPropMap ? ParamPropMap[TParamType]
: never
Expand All @@ -65,7 +75,7 @@ interface ParamToRequestParam<TParameters extends { in: string; required?: boole
: {}
}

export type ParamMap<TParameters extends { name: string; in: string }[]> = Pipe<
export type ParamMap<TParameters extends Checks.Parameters> = Pipe<
TParameters,
[Tuples.Map<ParamToRequestParam<TParameters>>, Tuples.ToIntersection]
>
Expand All @@ -79,5 +89,5 @@ export type StatusMap<
TOAS extends OASDocument,
TPath extends keyof PathMap<TOAS>,
TMethod extends keyof MethodMap<TOAS, TPath>,
> = MethodMap<TOAS, TPath>[TMethod] extends { responses: any } ? MethodMap<TOAS, TPath>[TMethod]['responses']
> = MethodMap<TOAS, TPath>[TMethod] extends Checks.Responses ? MethodMap<TOAS, TPath>[TMethod]['responses']
: never
38 changes: 23 additions & 15 deletions packages/swagger/src/infer/model.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,38 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type {
FromSchema,
JSONSchema,
} from 'json-schema-to-ts'
import type { OASDocument } from 'oas/rmoas.types'

export type Model<
TOAS extends OASDocument,
TName extends TOAS extends {
namespace Checks {
export type ModelWithSchemas = {
components: {
schemas: Record<string, JSONSchema>
}
} ? keyof TOAS['components']['schemas']
: TOAS extends {
definitions: Record<string, JSONSchema>
} ? keyof TOAS['definitions']
: never,
> = TOAS extends {
components: {
schemas: {
[TModelName in TName]: JSONSchema
}
export type ModelWithSchemasNamed<TName extends string | number | symbol> = {
components: {
schemas: {
[TModelName in TName]: JSONSchema
}
}
}
} ? FromSchema<TOAS['components']['schemas'][TName]>
: TOAS extends {
export type ModelWithDefinitions = {
definitions: Record<string, JSONSchema>
}
export type ModelWithDefinitionsNamed<TName extends string | number | symbol = never> = {
definitions: {
[TModelName in TName]: JSONSchema
}
} ? FromSchema<TOAS['definitions'][TName]>
}
}

export type Model<
TOAS extends OASDocument,
TName extends TOAS extends Checks.ModelWithSchemas ? keyof TOAS['components']['schemas']
: TOAS extends Checks.ModelWithDefinitions ? keyof TOAS['definitions']
: never,
> = TOAS extends Checks.ModelWithSchemasNamed<TName> ? FromSchema<TOAS['components']['schemas'][TName]>
: TOAS extends Checks.ModelWithDefinitionsNamed<TName> ? FromSchema<TOAS['definitions'][TName]>
: never
Original file line number Diff line number Diff line change
@@ -1,22 +1,30 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type { Booleans, Call, Objects, Strings, Tuples } from 'hotscript'
import type { Object } from 'ts-toolbelt'

type FixAdditionalPropertiesForAllOf<T> = T extends { allOf: any[] } ? Omit<T, 'allOf'> & {
namespace Checks {
export type AllOFf = { allOf: any[] }
export type Object = {
type: 'object'
properties: any
}
export type Properties = { properties: any }
export type PropertiesRequired = {
properties: Record<string, any>
required: string[]
}
}

type FixAdditionalPropertiesForAllOf<T> = T extends Checks.AllOFf ? Omit<T, 'allOf'> & {
allOf: Call<Tuples.Map<Objects.Omit<'additionalProperties'>>, T['allOf']>
}
: T

type FixMissingAdditionalProperties<T> = T extends {
type: 'object'
properties: any
} ? Omit<T, 'additionalProperties'> & { additionalProperties: false }
type FixMissingAdditionalProperties<T> = T extends Checks.Object ? Omit<T, 'additionalProperties'> & { additionalProperties: false }
: T
type FixMissingTypeObject<T> = T extends { properties: any } ? T & { type: 'object' } : T
type FixMissingTypeObject<T> = T extends Checks.Properties ? T & { type: 'object' } : T

type FixExtraRequiredFields<T> = T extends {
properties: Record<string, any>
required: string[]
} ? Omit<T, 'required'> & {
type FixExtraRequiredFields<T> = T extends Checks.PropertiesRequired ? Omit<T, 'required'> & {
required: Call<Tuples.Filter<Booleans.Extends<keyof T['properties']>>, T['required']>
}
: T
Expand Down Expand Up @@ -47,4 +55,4 @@ type ResolveRefsInObj<T, TBase = T> = {
[K in keyof T]: ResolveRefsInObj<ResolveRefInObj<T[K], TBase>, TBase>
}

export type Infer<TOAS> = Mutable<ResolveRefsInObj<TOAS>>
export type Parse<TOAS> = Mutable<ResolveRefsInObj<TOAS>>
56 changes: 36 additions & 20 deletions packages/swagger/src/infer/requestParams.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-namespace */
/* eslint-disable @typescript-eslint/ban-types */

import type { SplitByDelimiter, TupleToUnion } from '@kubb/types'
Expand All @@ -8,6 +9,31 @@ import type {
} from 'json-schema-to-ts'
import type { OASDocument } from 'oas/rmoas.types'
import type { MethodMap, ParamMap, PathMap } from './mappers.ts'
import type { SecurityParamsBySecurityRef } from './security.ts'

namespace Checks {
export type RequestBodyJson = {
requestBody: { content: { 'application/json': { schema: JSONSchema } } }
}
export type RequestBodyFormData = {
requestBody: {
content: { 'multipart/form-data': { schema: JSONSchema } }
}
}
export type RequestBodyFormEncoded = {
requestBody: {
content: {
'application/x-www-form-urlencoded': { schema: JSONSchema }
}
}
}
export type Parameters = {
parameters: { name: string; in: string }[]
}
export type PathBrackets = `${string}{${string}}${string}`
export type PathPattern = `${string}:${string}${string}`
export type Required = { required: true }
}

type ExtractPathParamsWithPattern<TPath extends string> = Pipe<
TPath,
Expand Down Expand Up @@ -40,9 +66,7 @@ export type RequestParams<
TPath extends keyof PathMap<TOAS>,
TMethod extends keyof MethodMap<TOAS, TPath>,
> =
& (MethodMap<TOAS, TPath>[TMethod] extends {
requestBody: { content: { 'application/json': { schema: JSONSchema } } }
} ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends { required: true } ? {
& (MethodMap<TOAS, TPath>[TMethod] extends Checks.RequestBodyJson ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends Checks.Required ? {
/**
* The request body in JSON is required for this request.
*
Expand All @@ -62,11 +86,7 @@ export type RequestParams<
MethodMap<TOAS, TPath>[TMethod]['requestBody']['content']['application/json']['schema']
>
}
: MethodMap<TOAS, TPath>[TMethod] extends {
requestBody: {
content: { 'multipart/form-data': { schema: JSONSchema } }
}
} ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends { required: true } ? {
: MethodMap<TOAS, TPath>[TMethod] extends Checks.RequestBodyFormData ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends Checks.Required ? {
/**
* The request body in multipart/form-data is required for this request.
*
Expand All @@ -92,13 +112,7 @@ export type RequestParams<
>[TMethod]['requestBody']['content']['multipart/form-data']['schema']
>
}
: MethodMap<TOAS, TPath>[TMethod] extends {
requestBody: {
content: {
'application/x-www-form-urlencoded': { schema: JSONSchema }
}
}
} ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends { required: true } ? {
: MethodMap<TOAS, TPath>[TMethod] extends Checks.RequestBodyFormEncoded ? MethodMap<TOAS, TPath>[TMethod]['requestBody'] extends Checks.Required ? {
/**
* The request body in application/x-www-form-urlencoded is required for this request.
*
Expand All @@ -125,12 +139,10 @@ export type RequestParams<
>
}
: {})
& (MethodMap<TOAS, TPath>[TMethod] extends {
parameters: { name: string; in: string }[]
} ? ParamMap<MethodMap<TOAS, TPath>[TMethod]['parameters']>
& (MethodMap<TOAS, TPath>[TMethod] extends Checks.Parameters ? ParamMap<MethodMap<TOAS, TPath>[TMethod]['parameters']>
: {})
& // If there is any parameters defined in path but not in the parameters array, we should add them to the params
(TPath extends `${string}{${string}}${string}` ? {
(TPath extends Checks.PathBrackets ? {
/**
* Parameters defined in the path are required for this request.
*
Expand All @@ -141,7 +153,7 @@ export type RequestParams<
params: Record<ExtractPathParamsWithBrackets<TPath>, string | number | bigint | boolean>
}
: {})
& (TPath extends `${string}:${string}${string}` ? {
& (TPath extends Checks.PathPattern ? {
/**
* Parameters defined in the path are required for this request.
*
Expand All @@ -152,3 +164,7 @@ export type RequestParams<
params: Record<ExtractPathParamsWithPattern<TPath>, string | number | bigint | boolean>
}
: {})
& // Respect security definitions in path object
SecurityParamsBySecurityRef<TOAS, MethodMap<TOAS, TPath>[TMethod]>
& // Respect global security definitions
SecurityParamsBySecurityRef<TOAS, TOAS>
7 changes: 6 additions & 1 deletion packages/swagger/src/infer/response.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
/* eslint-disable @typescript-eslint/no-namespace */
import type {
FromSchema,
} from 'json-schema-to-ts'
import type { OASDocument } from 'oas/rmoas.types'
import type { MethodMap, PathMap, StatusMap } from './mappers.ts'

namespace Checks {
export type Content = { content: any }
}

type ResponseSchemas<
TOAS extends OASDocument,
TPath extends keyof PathMap<TOAS>,
Expand All @@ -16,7 +21,7 @@ type JSONResponseSchema<
TPath extends keyof PathMap<TOAS>,
TMethod extends keyof MethodMap<TOAS, TPath>,
TStatus extends keyof StatusMap<TOAS, TPath, TMethod>,
> = StatusMap<TOAS, TPath, TMethod>[TStatus] extends { content: any } ? ResponseSchemas<TOAS, TPath, TMethod, TStatus>[
> = StatusMap<TOAS, TPath, TMethod>[TStatus] extends Checks.Content ? ResponseSchemas<TOAS, TPath, TMethod, TStatus>[
keyof ResponseSchemas<
TOAS,
TPath,
Expand Down
Loading

0 comments on commit 68cd9c3

Please sign in to comment.