@effect/openapi-generator 4.0.0-beta.40 → 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,15 +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 { ParsedOperation } from "./ParsedOperation.ts"
4
+ import type { ParsedOpenApi, ParsedOperation } from "./ParsedOperation.ts"
5
5
  import * as Utils from "./Utils.ts"
6
6
 
7
7
  export class OpenApiTransformer extends ServiceMap.Service<
8
8
  OpenApiTransformer,
9
9
  {
10
- readonly imports: (importName: string, operations: ReadonlyArray<ParsedOperation>) => string
11
- readonly toTypes: (importName: string, name: string, operations: ReadonlyArray<ParsedOperation>) => string
12
- 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
13
13
  }
14
14
  >()("OpenApiTransformer") {}
15
15
 
@@ -382,7 +382,8 @@ export const make = (
382
382
  }
383
383
 
384
384
  return OpenApiTransformer.of({
385
- imports: (importName, operations) => {
385
+ imports: (importName, parsed) => {
386
+ const operations = parsed.operations
386
387
  const requirements = computeImportRequirements(operations)
387
388
  const imports = [
388
389
  `import * as Data from "effect/Data"`,
@@ -409,8 +410,8 @@ export const make = (
409
410
  )
410
411
  return imports.join("\n")
411
412
  },
412
- toTypes: operationsToInterface,
413
- toImplementation: operationsToImpl
413
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
414
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
414
415
  })
415
416
  }
416
417
 
@@ -777,7 +778,8 @@ export const make = (
777
778
  }
778
779
 
779
780
  return OpenApiTransformer.of({
780
- imports: (_importName, operations) => {
781
+ imports: (_importName, parsed) => {
782
+ const operations = parsed.operations
781
783
  const requirements = computeImportRequirements(operations)
782
784
  const imports = [
783
785
  `import * as Data from "effect/Data"`,
@@ -794,8 +796,8 @@ export const make = (
794
796
  )
795
797
  return imports.join("\n")
796
798
  },
797
- toTypes: operationsToInterface,
798
- toImplementation: operationsToImpl
799
+ toTypes: (importName, name, parsed) => operationsToInterface(importName, name, parsed.operations),
800
+ toImplementation: (importName, name, parsed) => operationsToImpl(importName, name, parsed.operations)
799
801
  })
800
802
  }
801
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
+ }