@effect/openapi-generator 4.0.0-beta.4 → 4.0.0-beta.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,15 @@
1
1
  import * as Layer from "effect/Layer"
2
2
  import * as Predicate from "effect/Predicate"
3
3
  import * as ServiceMap from "effect/ServiceMap"
4
- import type { OpenAPISpecMethodName } from "effect/unstable/httpapi/OpenApi"
5
- import type { ParsedOperation } from "./ParsedOperation.ts"
4
+ import type { ParsedOpenApi, ParsedOperation } from "./ParsedOperation.ts"
6
5
  import * as Utils from "./Utils.ts"
7
6
 
8
7
  export class OpenApiTransformer extends ServiceMap.Service<
9
8
  OpenApiTransformer,
10
9
  {
11
- readonly imports: (importName: string, operations: ReadonlyArray<ParsedOperation>) => string
12
- readonly toTypes: (importName: string, name: string, operations: ReadonlyArray<ParsedOperation>) => string
13
- readonly toImplementation: (importName: string, name: string, operations: ReadonlyArray<ParsedOperation>) => string
10
+ readonly imports: (importName: string, parsed: ParsedOpenApi) => string
11
+ readonly toTypes: (importName: string, name: string, parsed: ParsedOpenApi) => string
12
+ readonly toImplementation: (importName: string, name: string, parsed: ParsedOpenApi) => string
14
13
  }
15
14
  >()("OpenApiTransformer") {}
16
15
 
@@ -36,17 +35,6 @@ const computeImportRequirements = (operations: ReadonlyArray<ParsedOperation>):
36
35
  const requiresStreaming = (requirements: ImportRequirements): boolean =>
37
36
  requirements.eventStream || requirements.octetStream
38
37
 
39
- const httpClientMethodNames: Record<OpenAPISpecMethodName, string> = {
40
- get: "get",
41
- put: "put",
42
- post: "post",
43
- delete: "del",
44
- options: "options",
45
- head: "head",
46
- patch: "patch",
47
- trace: `make("TRACE")`
48
- }
49
-
50
38
  export const makeTransformerSchema = () => {
51
39
  const operationsToInterface = (
52
40
  _importName: string,
@@ -306,7 +294,7 @@ export const make = (
306
294
 
307
295
  return (
308
296
  `"${operation.id}": (${params}) => ` +
309
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
297
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
310
298
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
311
299
  )
312
300
  }
@@ -347,7 +335,7 @@ export const make = (
347
335
 
348
336
  return (
349
337
  `"${operation.id}Sse": (${params}) => ` +
350
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
338
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
351
339
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
352
340
  )
353
341
  }
@@ -388,13 +376,14 @@ export const make = (
388
376
 
389
377
  return (
390
378
  `"${operation.id}Stream": (${params}) => ` +
391
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
379
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
392
380
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
393
381
  )
394
382
  }
395
383
 
396
384
  return OpenApiTransformer.of({
397
- imports: (importName, operations) => {
385
+ imports: (importName, parsed) => {
386
+ const operations = parsed.operations
398
387
  const requirements = computeImportRequirements(operations)
399
388
  const imports = [
400
389
  `import * as Data from "effect/Data"`,
@@ -421,8 +410,8 @@ export const make = (
421
410
  )
422
411
  return imports.join("\n")
423
412
  },
424
- toTypes: operationsToInterface,
425
- toImplementation: operationsToImpl
413
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
414
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
426
415
  })
427
416
  }
428
417
 
@@ -701,7 +690,7 @@ export const make = (
701
690
 
702
691
  return (
703
692
  `"${operation.id}": (${params}) => ` +
704
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
693
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
705
694
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
706
695
  )
707
696
  }
@@ -742,7 +731,7 @@ export const make = (
742
731
 
743
732
  return (
744
733
  `"${operation.id}Sse": (${params}) => ` +
745
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
734
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
746
735
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
747
736
  )
748
737
  }
@@ -783,13 +772,14 @@ export const make = (
783
772
 
784
773
  return (
785
774
  `"${operation.id}Stream": (${params}) => ` +
786
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
775
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
787
776
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
788
777
  )
789
778
  }
790
779
 
791
780
  return OpenApiTransformer.of({
792
- imports: (_importName, operations) => {
781
+ imports: (_importName, parsed) => {
782
+ const operations = parsed.operations
793
783
  const requirements = computeImportRequirements(operations)
794
784
  const imports = [
795
785
  `import * as Data from "effect/Data"`,
@@ -806,8 +796,8 @@ export const make = (
806
796
  )
807
797
  return imports.join("\n")
808
798
  },
809
- toTypes: operationsToInterface,
810
- toImplementation: operationsToImpl
799
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
800
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
811
801
  })
812
802
  }
813
803
 
@@ -1,9 +1,104 @@
1
1
  import type * as Types from "effect/Types"
2
- import type { OpenAPISpecMethodName } from "effect/unstable/httpapi/OpenApi"
2
+ import type {
3
+ OpenAPISecurityRequirement,
4
+ OpenAPISpecExternalDocs,
5
+ OpenAPISpecLicense,
6
+ OpenAPISpecMethodName,
7
+ OpenAPISpecServer
8
+ } from "effect/unstable/httpapi/OpenApi"
9
+
10
+ export interface ParsedOpenApiMetadata {
11
+ readonly title: string
12
+ readonly version: string
13
+ readonly summary: string | undefined
14
+ readonly description: string | undefined
15
+ readonly license: OpenAPISpecLicense | undefined
16
+ readonly servers: ReadonlyArray<OpenAPISpecServer> | undefined
17
+ }
18
+
19
+ export interface ParsedOpenApiTag {
20
+ readonly name: string
21
+ readonly description: string | undefined
22
+ readonly externalDocs: OpenAPISpecExternalDocs | undefined
23
+ }
24
+
25
+ export interface ParsedOpenApiSecurityScheme {
26
+ readonly name: string
27
+ readonly type: "basic" | "bearer" | "apiKey"
28
+ readonly description: string | undefined
29
+ readonly bearerFormat: string | undefined
30
+ readonly key: string | undefined
31
+ readonly in: "header" | "query" | "cookie" | undefined
32
+ }
33
+
34
+ export interface ParsedOpenApi {
35
+ readonly metadata: ParsedOpenApiMetadata
36
+ readonly tags: ReadonlyArray<ParsedOpenApiTag>
37
+ readonly securitySchemes: ReadonlyArray<ParsedOpenApiSecurityScheme>
38
+ readonly operations: ReadonlyArray<ParsedOperation>
39
+ }
40
+
41
+ export interface ParsedOperationMetadata {
42
+ readonly summary: string | undefined
43
+ readonly description: string | undefined
44
+ readonly deprecated: boolean
45
+ readonly externalDocs: OpenAPISpecExternalDocs | undefined
46
+ }
47
+
48
+ export interface ParsedOperationParameter {
49
+ readonly name: string
50
+ readonly in: "path" | "query" | "header" | "cookie"
51
+ readonly required: boolean
52
+ readonly description: string | undefined
53
+ readonly schema: {}
54
+ }
55
+
56
+ export interface ParsedOperationRequestBody {
57
+ readonly required: boolean
58
+ readonly contentTypes: Array<string>
59
+ }
60
+
61
+ export type ParsedOperationMediaTypeEncoding =
62
+ | "json"
63
+ | "multipart"
64
+ | "form-url-encoded"
65
+ | "text"
66
+ | "binary"
67
+
68
+ export interface ParsedOperationMediaTypeSchema {
69
+ readonly contentType: string
70
+ readonly encoding: ParsedOperationMediaTypeEncoding
71
+ readonly schema: string
72
+ }
73
+
74
+ export interface ParsedOperationResponse {
75
+ readonly status: string
76
+ readonly description: string | undefined
77
+ readonly contentTypes: Array<string>
78
+ readonly hasHeaders: boolean
79
+ readonly isEmpty: boolean
80
+ readonly representable: ReadonlyArray<ParsedOperationMediaTypeSchema>
81
+ }
82
+
83
+ export type ParsedOperationSecurityRequirement = Readonly<OpenAPISecurityRequirement>
3
84
 
4
85
  export interface ParsedOperation {
5
86
  readonly id: string
87
+ readonly operationId: string | undefined
88
+ readonly path: string
6
89
  readonly method: OpenAPISpecMethodName
90
+ readonly tags: ReadonlyArray<string>
91
+ readonly metadata: ParsedOperationMetadata
92
+ readonly parameters: {
93
+ readonly path: ReadonlyArray<ParsedOperationParameter>
94
+ readonly query: ReadonlyArray<ParsedOperationParameter>
95
+ readonly header: ReadonlyArray<ParsedOperationParameter>
96
+ readonly cookie: ReadonlyArray<ParsedOperationParameter>
97
+ }
98
+ readonly requestBody: ParsedOperationRequestBody | undefined
99
+ readonly responses: ReadonlyArray<ParsedOperationResponse>
100
+ readonly defaultResponse: ParsedOperationResponse | undefined
101
+ readonly effectiveSecurity: ReadonlyArray<ParsedOperationSecurityRequirement>
7
102
  readonly description: string | undefined
8
103
  readonly params?: string
9
104
  readonly paramsOptional: boolean
@@ -12,6 +107,12 @@ export interface ParsedOperation {
12
107
  readonly cookies: ReadonlyArray<string>
13
108
  readonly payload?: string
14
109
  readonly payloadFormData: boolean
110
+ readonly pathSchema: string | undefined
111
+ readonly querySchema: string | undefined
112
+ readonly querySchemaOptional: boolean
113
+ readonly headersSchema: string | undefined
114
+ readonly headersSchemaOptional: boolean
115
+ readonly requestBodyRepresentable: ReadonlyArray<ParsedOperationMediaTypeSchema>
15
116
  readonly pathIds: ReadonlyArray<string>
16
117
  readonly pathTemplate: string
17
118
  readonly successSchemas: ReadonlyMap<string, string>
@@ -31,10 +132,35 @@ export const makeDeepMutable = (options: {
31
132
  readonly description: string | undefined
32
133
  }): Types.DeepMutable<ParsedOperation> => ({
33
134
  ...options,
135
+ operationId: undefined,
136
+ path: "",
137
+ tags: [],
138
+ metadata: {
139
+ summary: undefined,
140
+ description: options.description,
141
+ deprecated: false,
142
+ externalDocs: undefined
143
+ },
144
+ parameters: {
145
+ path: [],
146
+ query: [],
147
+ header: [],
148
+ cookie: []
149
+ },
150
+ requestBody: undefined,
151
+ responses: [],
152
+ defaultResponse: undefined,
153
+ effectiveSecurity: [],
34
154
  urlParams: [],
35
155
  headers: [],
36
156
  cookies: [],
37
157
  payloadFormData: false,
158
+ pathSchema: undefined,
159
+ querySchema: undefined,
160
+ querySchemaOptional: true,
161
+ headersSchema: undefined,
162
+ headersSchemaOptional: true,
163
+ requestBodyRepresentable: [],
38
164
  successSchemas: new Map(),
39
165
  errorSchemas: new Map(),
40
166
  voidSchemas: new Set(),
package/src/main.ts CHANGED
@@ -10,18 +10,21 @@ import * as OpenApiPatch from "./OpenApiPatch.ts"
10
10
 
11
11
  const spec = Flag.fileParse("spec").pipe(
12
12
  Flag.withAlias("s"),
13
- Flag.withDescription("The OpenAPI spec file to generate the client from")
13
+ Flag.withDescription("The OpenAPI spec file to generate output from")
14
14
  )
15
15
 
16
16
  const name = Flag.string("name").pipe(
17
17
  Flag.withAlias("n"),
18
- Flag.withDescription("The name of the generated client"),
18
+ Flag.withDescription("The name of the generated output"),
19
19
  Flag.withDefault("Client")
20
20
  )
21
21
 
22
- const typeOnly = Flag.boolean("type-only").pipe(
23
- Flag.withAlias("t"),
24
- Flag.withDescription("Generate a type-only client without schemas")
22
+ const format = Flag.choice("format", ["httpclient", "httpclient-type-only", "httpapi"] as const).pipe(
23
+ Flag.withAlias("f"),
24
+ Flag.withDescription(
25
+ "Output format to generate: httpclient | httpclient-type-only | httpapi (default: httpclient)"
26
+ ),
27
+ Flag.withDefault("httpclient")
25
28
  )
26
29
 
27
30
  const patch = Flag.string("patch").pipe(
@@ -34,8 +37,8 @@ const patch = Flag.string("patch").pipe(
34
37
  Flag.between(0, Infinity)
35
38
  )
36
39
 
37
- const root = Command.make("openapigen", { spec, typeOnly, name, patch }).pipe(
38
- Command.withHandler(Effect.fnUntraced(function*({ name, spec, typeOnly, patch }) {
40
+ const root = Command.make("openapigen", { spec, format, name, patch }).pipe(
41
+ Command.withHandler(Effect.fnUntraced(function*({ name, spec, format, patch }) {
39
42
  let patchedSpec: Schema.Json = spec as Schema.Json
40
43
 
41
44
  if (patch.length > 0) {
@@ -53,11 +56,23 @@ const root = Command.make("openapigen", { spec, typeOnly, name, patch }).pipe(
53
56
  }
54
57
 
55
58
  const generator = yield* OpenApiGenerator.OpenApiGenerator
56
- const source = yield* generator.generate(patchedSpec as unknown as OpenAPISpec, { name, typeOnly })
59
+ const warnings: Array<OpenApiGenerator.OpenApiGeneratorWarning> = []
60
+ const source = yield* generator.generate(patchedSpec as unknown as OpenAPISpec, {
61
+ name,
62
+ format,
63
+ onWarning: (warning) => {
64
+ warnings.push(warning)
65
+ }
66
+ })
67
+ yield* Effect.forEach(
68
+ warnings,
69
+ (warning) => Console.error(formatWarning(warning)),
70
+ { discard: true }
71
+ )
57
72
  return yield* Console.log(source)
58
73
  })),
59
- Command.provide(({ typeOnly }) =>
60
- typeOnly
74
+ Command.provide(({ format }) =>
75
+ format === "httpclient-type-only"
61
76
  ? OpenApiGenerator.layerTransformerTs
62
77
  : OpenApiGenerator.layerTransformerSchema
63
78
  )
@@ -66,3 +81,14 @@ const root = Command.make("openapigen", { spec, typeOnly, name, patch }).pipe(
66
81
  export const run: Effect.Effect<void, CliError.CliError, Command.Environment> = Command.run(root, {
67
82
  version: "0.0.0"
68
83
  })
84
+
85
+ const formatWarning = (warning: OpenApiGenerator.OpenApiGeneratorWarning): string => {
86
+ const context = [
87
+ warning.method?.toUpperCase(),
88
+ warning.path,
89
+ warning.operationId ? `(${warning.operationId})` : undefined
90
+ ].filter((value): value is string => value !== undefined)
91
+ return context.length > 0
92
+ ? `WARNING [${warning.code}] ${context.join(" ")}: ${warning.message}`
93
+ : `WARNING [${warning.code}] ${warning.message}`
94
+ }