@effect/openapi-generator 4.0.0-beta.6 → 4.0.0-beta.60

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.
@@ -47,7 +47,7 @@ import * as Yaml from "yaml"
47
47
  * @since 1.0.0
48
48
  * @category errors
49
49
  */
50
- export class JsonPatchParseError extends Schema.ErrorClass("JsonPatchParseError")({
50
+ export class JsonPatchParseError extends Schema.ErrorClass<JsonPatchParseError>("JsonPatchParseError")({
51
51
  _tag: Schema.tag("JsonPatchParseError"),
52
52
  source: Schema.String,
53
53
  reason: Schema.String
@@ -82,7 +82,7 @@ export class JsonPatchParseError extends Schema.ErrorClass("JsonPatchParseError"
82
82
  * @since 1.0.0
83
83
  * @category errors
84
84
  */
85
- export class JsonPatchValidationError extends Schema.ErrorClass("JsonPatchValidationError")({
85
+ export class JsonPatchValidationError extends Schema.ErrorClass<JsonPatchValidationError>("JsonPatchValidationError")({
86
86
  _tag: Schema.tag("JsonPatchValidationError"),
87
87
  source: Schema.String,
88
88
  reason: Schema.String
@@ -119,14 +119,16 @@ export class JsonPatchValidationError extends Schema.ErrorClass("JsonPatchValida
119
119
  * @since 1.0.0
120
120
  * @category errors
121
121
  */
122
- export class JsonPatchApplicationError extends Schema.ErrorClass("JsonPatchApplicationError")({
123
- _tag: Schema.tag("JsonPatchApplicationError"),
124
- source: Schema.String,
125
- operationIndex: Schema.Number,
126
- operation: Schema.String,
127
- path: Schema.String,
128
- reason: Schema.String
129
- }) {
122
+ export class JsonPatchApplicationError
123
+ extends Schema.ErrorClass<JsonPatchApplicationError>("JsonPatchApplicationError")({
124
+ _tag: Schema.tag("JsonPatchApplicationError"),
125
+ source: Schema.String,
126
+ operationIndex: Schema.Number,
127
+ operation: Schema.String,
128
+ path: Schema.String,
129
+ reason: Schema.String
130
+ })
131
+ {
130
132
  override get message() {
131
133
  return `Failed to apply patch from ${this.source}: operation ${this.operationIndex} ` +
132
134
  `(${this.operation} at ${this.path}): ${this.reason}`
@@ -169,7 +171,7 @@ export class JsonPatchApplicationError extends Schema.ErrorClass("JsonPatchAppli
169
171
  * @since 1.0.0
170
172
  * @category errors
171
173
  */
172
- export class JsonPatchAggregateError extends Schema.ErrorClass("JsonPatchAggregateError")({
174
+ export class JsonPatchAggregateError extends Schema.ErrorClass<JsonPatchAggregateError>("JsonPatchAggregateError")({
173
175
  _tag: Schema.tag("JsonPatchAggregateError"),
174
176
  errors: Schema.Array(Schema.Unknown)
175
177
  }) {
@@ -1,16 +1,15 @@
1
+ import * as Context from "effect/Context"
1
2
  import * as Layer from "effect/Layer"
2
3
  import * as Predicate from "effect/Predicate"
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
- export class OpenApiTransformer extends ServiceMap.Service<
7
+ export class OpenApiTransformer extends Context.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,
@@ -281,6 +269,8 @@ export const make = (
281
269
  const payloadVarName = "options.payload"
282
270
  if (operation.payloadFormData) {
283
271
  pipeline.push(`HttpClientRequest.bodyFormData(${payloadVarName} as any)`)
272
+ } else if (operation.payloadFormUrlEncoded) {
273
+ pipeline.push(`HttpClientRequest.bodyUrlParams(${payloadVarName} as any)`)
284
274
  } else if (operation.payload) {
285
275
  pipeline.push(`HttpClientRequest.bodyJsonUnsafe(${payloadVarName})`)
286
276
  }
@@ -306,7 +296,7 @@ export const make = (
306
296
 
307
297
  return (
308
298
  `"${operation.id}": (${params}) => ` +
309
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
299
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
310
300
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
311
301
  )
312
302
  }
@@ -347,7 +337,7 @@ export const make = (
347
337
 
348
338
  return (
349
339
  `"${operation.id}Sse": (${params}) => ` +
350
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
340
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
351
341
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
352
342
  )
353
343
  }
@@ -388,13 +378,14 @@ export const make = (
388
378
 
389
379
  return (
390
380
  `"${operation.id}Stream": (${params}) => ` +
391
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
381
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
392
382
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
393
383
  )
394
384
  }
395
385
 
396
386
  return OpenApiTransformer.of({
397
- imports: (importName, operations) => {
387
+ imports: (importName, parsed) => {
388
+ const operations = parsed.operations
398
389
  const requirements = computeImportRequirements(operations)
399
390
  const imports = [
400
391
  `import * as Data from "effect/Data"`,
@@ -421,8 +412,8 @@ export const make = (
421
412
  )
422
413
  return imports.join("\n")
423
414
  },
424
- toTypes: operationsToInterface,
425
- toImplementation: operationsToImpl
415
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
416
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
426
417
  })
427
418
  }
428
419
 
@@ -701,7 +692,7 @@ export const make = (
701
692
 
702
693
  return (
703
694
  `"${operation.id}": (${params}) => ` +
704
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
695
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
705
696
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
706
697
  )
707
698
  }
@@ -742,7 +733,7 @@ export const make = (
742
733
 
743
734
  return (
744
735
  `"${operation.id}Sse": (${params}) => ` +
745
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
736
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
746
737
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
747
738
  )
748
739
  }
@@ -783,13 +774,14 @@ export const make = (
783
774
 
784
775
  return (
785
776
  `"${operation.id}Stream": (${params}) => ` +
786
- `HttpClientRequest.${httpClientMethodNames[operation.method]}(${operation.pathTemplate})` +
777
+ `HttpClientRequest.${operation.method}(${operation.pathTemplate})` +
787
778
  `.pipe(\n ${pipeline.join(",\n ")}\n )`
788
779
  )
789
780
  }
790
781
 
791
782
  return OpenApiTransformer.of({
792
- imports: (_importName, operations) => {
783
+ imports: (_importName, parsed) => {
784
+ const operations = parsed.operations
793
785
  const requirements = computeImportRequirements(operations)
794
786
  const imports = [
795
787
  `import * as Data from "effect/Data"`,
@@ -806,8 +798,8 @@ export const make = (
806
798
  )
807
799
  return imports.join("\n")
808
800
  },
809
- toTypes: operationsToInterface,
810
- toImplementation: operationsToImpl
801
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
802
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
811
803
  })
812
804
  }
813
805
 
@@ -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,13 @@ export interface ParsedOperation {
12
107
  readonly cookies: ReadonlyArray<string>
13
108
  readonly payload?: string
14
109
  readonly payloadFormData: boolean
110
+ readonly payloadFormUrlEncoded: boolean
111
+ readonly pathSchema: string | undefined
112
+ readonly querySchema: string | undefined
113
+ readonly querySchemaOptional: boolean
114
+ readonly headersSchema: string | undefined
115
+ readonly headersSchemaOptional: boolean
116
+ readonly requestBodyRepresentable: ReadonlyArray<ParsedOperationMediaTypeSchema>
15
117
  readonly pathIds: ReadonlyArray<string>
16
118
  readonly pathTemplate: string
17
119
  readonly successSchemas: ReadonlyMap<string, string>
@@ -31,10 +133,36 @@ export const makeDeepMutable = (options: {
31
133
  readonly description: string | undefined
32
134
  }): Types.DeepMutable<ParsedOperation> => ({
33
135
  ...options,
136
+ operationId: undefined,
137
+ path: "",
138
+ tags: [],
139
+ metadata: {
140
+ summary: undefined,
141
+ description: options.description,
142
+ deprecated: false,
143
+ externalDocs: undefined
144
+ },
145
+ parameters: {
146
+ path: [],
147
+ query: [],
148
+ header: [],
149
+ cookie: []
150
+ },
151
+ requestBody: undefined,
152
+ responses: [],
153
+ defaultResponse: undefined,
154
+ effectiveSecurity: [],
34
155
  urlParams: [],
35
156
  headers: [],
36
157
  cookies: [],
37
158
  payloadFormData: false,
159
+ payloadFormUrlEncoded: false,
160
+ pathSchema: undefined,
161
+ querySchema: undefined,
162
+ querySchemaOptional: true,
163
+ headersSchema: undefined,
164
+ headersSchemaOptional: true,
165
+ requestBodyRepresentable: [],
38
166
  successSchemas: new Map(),
39
167
  errorSchemas: new Map(),
40
168
  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
+ }