typespec-http-zod 0.19.1 → 0.21.0

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.
package/dist/src/api.d.ts CHANGED
@@ -222,6 +222,22 @@ export interface EmittedRoute {
222
222
  * {@link rawBodyProperty}, whose case is the same statement about a different body.
223
223
  */
224
224
  readonly bodyProperty: string | undefined;
225
+ /**
226
+ * Whether the document publishes `requestBody.required: false`, so a request carrying NO body at
227
+ * all is one the contract permits.
228
+ *
229
+ * **A body that may be absent cannot be flattened into the input, which is why this is published
230
+ * rather than kept internal.** Merged, there is no way to say "these properties are here only
231
+ * sometimes" without making every one of them optional, which would be a different and weaker
232
+ * claim about the body that IS sent. So an optional body is named, exactly as an indexed one is,
233
+ * and a wrapping emitter spells the property optional from this.
234
+ *
235
+ * Measured before it existed: a bodyless `POST` naming `content-type: application/json` answered
236
+ * **400 `Malformed JSON in request body`** as `text/plain`, produced before the app's `invalid`
237
+ * hook and so outside its error envelope entirely - a service refusing a request its own contract
238
+ * permits, in a shape that contract forbids.
239
+ */
240
+ readonly optionalBody: boolean;
225
241
  /** Declared failures, each with the schema for its OWN body, in OpenAPI's precedence order. */
226
242
  readonly errorArms: readonly {
227
243
  readonly status: StatusKey;
package/dist/src/api.js CHANGED
@@ -1054,7 +1054,12 @@ export function collectRoutes(program, registry, service) {
1054
1054
  * resolved model's own indexer rather than from the emitted schema text, which would be a
1055
1055
  * decision made by pattern-matching generated output.
1056
1056
  */
1057
- const bodyProperty = requestType?.kind === "Model" && requestType.indexer !== undefined
1057
+ /**
1058
+ * **An OPTIONAL body is named for the same reason an indexed one is**: the merge cannot
1059
+ * express "absent". See {@link EmittedRoute.optionalBody}.
1060
+ */
1061
+ const optionalBody = bodyParameter?.property?.optional === true;
1062
+ const bodyProperty = requestType?.kind === "Model" && (requestType.indexer !== undefined || optionalBody)
1058
1063
  ? (bodyParameter?.property?.name ?? "body")
1059
1064
  : undefined;
1060
1065
  routes.push({
@@ -1135,6 +1140,7 @@ export function collectRoutes(program, registry, service) {
1135
1140
  : registry.expressionFor(responseType)),
1136
1141
  rawBodyProperty,
1137
1142
  bodyProperty,
1143
+ optionalBody,
1138
1144
  // Responses are read at Visibility.Read, exactly as the success body above is. Resolving
1139
1145
  // them under the ambient request visibility declared six components under the wrong
1140
1146
  // suffix - measured: type/model/visibility gained six divergences.
@@ -1653,8 +1659,21 @@ function renderRequestTypes(entries, responses, registry) {
1653
1659
  const lookup = entries
1654
1660
  .map((entry) => `\t${entry.operationId}: ${upperFirst(entry.operationId)}Input;`)
1655
1661
  .join("\n");
1656
- return `${generatedBanner()}
1657
- /**
1662
+ /**
1663
+ * **Emitted only where an operation has something to flatten.**
1664
+ *
1665
+ * A generated file has to pass the lint of whatever project it lands in, and `noUnusedLocals`
1666
+ * makes an unused type declaration an error. A service whose operations take no input at all -
1667
+ * two parameterless `GET`s, which is where a health check starts and therefore where every new
1668
+ * consumer starts - got this declaration and no use of it, so `requests.gen.ts` did not compile.
1669
+ * `TS6196: 'Simplify' is declared but never used.` Third of its kind, after the unconditional
1670
+ * `zValidator` and `z` imports.
1671
+ *
1672
+ * Decided from the aliases actually rendered rather than by searching the output for the name.
1673
+ */
1674
+ const simplifyHelper = inputs.length === 0
1675
+ ? ""
1676
+ : `/**
1658
1677
  * Flattens an intersection into a single object type.
1659
1678
  *
1660
1679
  * Without it, \`Body & { filingId: string }\` and the equivalent flat object are structurally the
@@ -1662,7 +1681,8 @@ function renderRequestTypes(entries, responses, registry) {
1662
1681
  * this file with the emitted Zod would fail on correct code.
1663
1682
  */
1664
1683
  type Simplify<T> = { [K in keyof T]: T[K] } & {};
1665
-
1684
+ `;
1685
+ return `${generatedBanner()}${simplifyHelper}
1666
1686
  /**
1667
1687
  * The same shape, tolerant of \`readonly\`.
1668
1688
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-http-zod",
3
- "version": "0.19.1",
3
+ "version": "0.21.0",
4
4
  "description": "TypeSpec emitter and library: turn an HTTP service definition into Zod validators and framework-free contract types that agree, keyword for keyword, with the OpenAPI document @typespec/openapi3 publishes from the same source.",
5
5
  "keywords": [
6
6
  "codegen",