• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RobinTail / express-zod-api / 6394154516

03 Oct 2023 02:10PM CUT coverage: 100.0%. Remained the same
6394154516

Pull #1202

github

web-flow
Merge c5edb5c00 into f2934e530
Pull Request #1202: Bump @typescript-eslint/eslint-plugin from 6.7.3 to 6.7.4

561 of 591 branches covered (0.0%)

1194 of 1194 relevant lines covered (100.0%)

257.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/documentation.ts
1
import {
2
  OpenApiBuilder,
3
  OperationObject,
4
  ReferenceObject,
5
  SchemaObject,
6
  SecuritySchemeObject,
7
  SecuritySchemeType,
8
} from "openapi3-ts/oas30";
84✔
9
import { z } from "zod";
10
import { DocumentationError } from "./errors";
84✔
11
import {
12
  defaultInputSources,
13
  defaultSerializer,
14
  makeCleanId,
15
} from "./common-helpers";
84✔
16
import { CommonConfig } from "./config-type";
17
import { mapLogicalContainer } from "./logical-container";
84✔
18
import { Method } from "./method";
19
import {
20
  depictRequest,
21
  depictRequestParams,
22
  depictResponse,
23
  depictSecurity,
24
  depictSecurityRefs,
25
  depictTags,
26
  ensureShortDescription,
27
  reformatParamsInPath,
28
} from "./documentation-helpers";
84✔
29
import { Routing } from "./routing";
30
import { RoutingWalkerParams, walkRouting } from "./routing-walker";
84✔
31

32
interface DocumentationParams {
33
  title: string;
34
  version: string;
35
  serverUrl: string | string[];
36
  routing: Routing;
37
  config: CommonConfig;
38
  /** @default Successful response */
39
  successfulResponseDescription?: string;
40
  /** @default Error response */
41
  errorResponseDescription?: string;
42
  /** @default true */
43
  hasSummaryFromDescription?: boolean;
44
  /** @default inline */
45
  composition?: "inline" | "components";
46
  /**
47
   * @desc Used for comparing schemas wrapped into z.lazy() to limit the recursion
48
   * @default JSON.stringify() + SHA1 hash as a hex digest
49
   * */
50
  serializer?: (schema: z.ZodTypeAny) => string;
51
}
52

53
export class Documentation extends OpenApiBuilder {
176✔
54
  protected lastSecuritySchemaIds: Partial<Record<SecuritySchemeType, number>> =
55
    {};
172✔
56
  protected lastOperationIdSuffixes: Record<string, number> = {};
172✔
57

58
  protected makeRef(
59
    name: string,
60
    schema: SchemaObject | ReferenceObject,
61
  ): ReferenceObject {
62
    this.addSchema(name, schema);
108✔
63
    return this.getRef(name)!;
108✔
64
  }
65

66
  protected getRef(name: string): ReferenceObject | undefined {
67
    return name in (this.rootDoc.components?.schemas || {})
132!
68
      ? { $ref: `#/components/schemas/${name}` }
69
      : undefined;
70
  }
71

72
  protected ensureUniqOperationId(
73
    path: string,
74
    method: Method,
75
    userDefinedOperationId?: string,
76
  ) {
77
    if (userDefinedOperationId) {
236✔
78
      if (userDefinedOperationId in this.lastOperationIdSuffixes) {
20✔
79
        throw new DocumentationError({
4✔
80
          message: `Duplicated operationId: "${userDefinedOperationId}"`,
81
          method,
82
          isResponse: false,
83
          path,
84
        });
85
      }
86
      this.lastOperationIdSuffixes[userDefinedOperationId] = 1;
16✔
87
      return userDefinedOperationId;
16✔
88
    }
89
    const operationId = makeCleanId(path, method);
216✔
90
    if (operationId in this.lastOperationIdSuffixes) {
216✔
91
      this.lastOperationIdSuffixes[operationId]++;
4✔
92
      return `${operationId}${this.lastOperationIdSuffixes[operationId]}`;
4✔
93
    }
94
    this.lastOperationIdSuffixes[operationId] = 1;
212✔
95
    return operationId;
212✔
96
  }
97

98
  protected ensureUniqSecuritySchemaName(subject: SecuritySchemeObject) {
99
    const serializedSubject = JSON.stringify(subject);
36✔
100
    for (const name in this.rootDoc.components?.securitySchemes || {}) {
36!
101
      if (
40✔
102
        serializedSubject ===
103
        JSON.stringify(this.rootDoc.components?.securitySchemes?.[name])
104
      ) {
105
        return name;
4✔
106
      }
107
    }
108
    this.lastSecuritySchemaIds[subject.type] =
32✔
109
      (this.lastSecuritySchemaIds?.[subject.type] || 0) + 1;
52✔
110
    return `${subject.type.toUpperCase()}_${
32✔
111
      this.lastSecuritySchemaIds[subject.type]
112
    }`;
113
  }
114

115
  public constructor({
116
    routing,
117
    config,
118
    title,
119
    version,
120
    serverUrl,
121
    successfulResponseDescription = "Successful response",
172✔
122
    errorResponseDescription = "Error response",
172✔
123
    hasSummaryFromDescription = true,
172✔
124
    composition = "inline",
160✔
125
    serializer = defaultSerializer,
172✔
126
  }: DocumentationParams) {
127
    super();
172✔
128
    this.addInfo({ title, version });
172✔
129
    for (const url of typeof serverUrl === "string" ? [serverUrl] : serverUrl) {
172!
130
      this.addServer({ url });
172✔
131
    }
132
    const onEndpoint: RoutingWalkerParams["onEndpoint"] = (
172✔
133
      endpoint,
134
      path,
135
      _method,
136
    ) => {
137
      const method = _method as Method;
236✔
138
      const commonParams = {
236✔
139
        path,
140
        method,
141
        endpoint,
142
        composition,
143
        serializer,
144
        getRef: this.getRef.bind(this),
145
        makeRef: this.makeRef.bind(this),
146
      };
147
      const [shortDesc, longDesc] = (["short", "long"] as const).map(
236✔
148
        endpoint.getDescription.bind(endpoint),
149
      );
150
      const inputSources =
151
        config.inputSources?.[method] || defaultInputSources[method];
236✔
152
      const depictedParams = depictRequestParams({
236✔
153
        ...commonParams,
154
        inputSources,
155
      });
156
      const operationId = this.ensureUniqOperationId(
236✔
157
        path,
158
        method,
159
        endpoint.getOperationId(method),
160
      );
161
      const operation: OperationObject = {
232✔
162
        operationId,
163
        responses: {
164
          [endpoint.getStatusCode("positive")]: depictResponse({
165
            ...commonParams,
166
            clue: successfulResponseDescription,
167
            isPositive: true,
168
          }),
169
          [endpoint.getStatusCode("negative")]: depictResponse({
170
            ...commonParams,
171
            clue: errorResponseDescription,
172
            isPositive: false,
173
          }),
174
        },
175
      };
176
      if (longDesc) {
232✔
177
        operation.description = longDesc;
48✔
178
        if (hasSummaryFromDescription && shortDesc === undefined) {
48✔
179
          operation.summary = ensureShortDescription(longDesc);
40✔
180
        }
181
      }
182
      if (shortDesc) {
232✔
183
        operation.summary = ensureShortDescription(shortDesc);
24✔
184
      }
185
      if (endpoint.getTags().length > 0) {
232✔
186
        operation.tags = endpoint.getTags();
48✔
187
      }
188
      if (depictedParams.length > 0) {
232✔
189
        operation.parameters = depictedParams;
84✔
190
      }
191
      if (inputSources.includes("body")) {
232✔
192
        operation.requestBody = depictRequest(commonParams);
124✔
193
      }
194
      const securityRefs = depictSecurityRefs(
204✔
195
        mapLogicalContainer(
196
          depictSecurity(endpoint.getSecurity()),
197
          (securitySchema) => {
198
            const name = this.ensureUniqSecuritySchemaName(securitySchema);
36✔
199
            const scopes = ["oauth2", "openIdConnect"].includes(
36✔
200
              securitySchema.type,
201
            )
202
              ? endpoint.getScopes()
203
              : [];
204
            this.addSecurityScheme(name, securitySchema);
36✔
205
            return { name, scopes };
36✔
206
          },
207
        ),
208
      );
209
      if (securityRefs.length > 0) {
204✔
210
        operation.security = securityRefs;
20✔
211
      }
212
      const swaggerCompatiblePath = reformatParamsInPath(path);
204✔
213
      this.addPath(swaggerCompatiblePath, {
204✔
214
        [method]: operation,
215
      });
216
    };
217
    walkRouting({ routing, onEndpoint });
172✔
218
    this.rootDoc.tags = config.tags ? depictTags(config.tags) : [];
140✔
219
  }
220
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc