Skip to content

HttpApi: refactor OpenAPI related APIs#1133

Merged
gcanti merged 52 commits intomainfrom
httpapi-forEach
Feb 5, 2026
Merged

HttpApi: refactor OpenAPI related APIs#1133
gcanti merged 52 commits intomainfrom
httpapi-forEach

Conversation

@gcanti
Copy link
Contributor

@gcanti gcanti commented Jan 28, 2026

Request Context Renamings

To align with other frameworks, the proposal is to rename:

  • rename path to params (Express, Hono, Elysia)
  • rename urlParams to query (Express, Hono, Elysia)

I also propose requiring a record of schemas (instead of allowing a single schema like before) for:

  • path params
  • query params
  • headers
  • the request payload when the method has no body (for example, a GET)

This makes the API easier to read and avoids issues when generating an OpenAPI spec. OpenAPI needs a record of schemas and cannot represent other schema shapes (for example, a union).

Example (Declaring params, query, and headers)

import { Schema } from "effect"
import { HttpApiEndpoint } from "effect/unstable/httpapi"

HttpApiEndpoint.patch("updateUser", "/user/:id", {
  // Path parameters from the route pattern (e.g. /user/:id).
  // This is a record where each key is the parameter name.
  params: {
    // Schema for the "id" path parameter
    id: Schema.String
  },

  // (optional) Query string parameters (e.g. ?mode=merge).
  // This is a record where each key is the query parameter name.
  query: {
    // Schema for the "mode" query parameter
    mode: Schema.Literals(["merge", "replace"])
  },

  // (optional) Request headers.
  // Use the exact header name as the key.
  headers: {
    "x-api-key": Schema.String,
    "x-request-id": Schema.String
  }
})

Status Codes

I added an explicit HttpApiSchema.status API to set the response status code.

We can keep the httpApiStatus annotation, or remove it entirely.

Encodings

I propose removing explicit encoding annotations and using these APIs instead of HttpApiSchema.withEncoding:

  • HttpApiSchema.asJson
  • HttpApiSchema.asUrlParams
  • HttpApiSchema.asText
  • HttpApiSchema.asUint8Array
  • HttpApiSchema.asMultipart
  • HttpApiSchema.asMultipartStream

I use HttpApiSchema.asUrlParams here because it's like this now but I think it's a bit confusing, maybe asQuery would be better to align with query above, or asFormUrlEncoded

Multiple Encodings

Right now there is also a hidden assumption: encoding annotations only apply at the top level (nested unions are not handled). To make this behavior explicit, I propose using an array of schemas for payload, success, and error.

Example (Multiple payloads and responses with status and encoding)

import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiSchema } from "effect/unstable/httpapi"

const User = Schema.Struct({
  id: Schema.String,
  name: Schema.String
})

HttpApiEndpoint.patch("updateUser", "/user/:id", {
  params: {
    id: Schema.String
  },

  // The request payload can be a single schema or an array of schemas.
  // - Default encoding is JSON.
  // - Default status for success is 200.
  // For GET requests, the payload must be a record of schemas.
  payload: [
    // JSON payload (default encoding).
    Schema.Struct({
      name: Schema.String
    }),

    // text/plain payload.
    Schema.String.pipe(HttpApiSchema.asText())
  ],

  // Possible success responses.
  // Default is 200 OK with no content if omitted.
  success: [
    // JSON response (default encoding).
    User,

    // text/plain response with a custom status code.
    Schema.String.pipe(
      HttpApiSchema.status(206),
      HttpApiSchema.asText()
    )
  ],

  // Possible error responses.
  error: [
    // Default is 500 Internal Server Error with JSON encoding.
    Schema.Number,

    // text/plain error with a custom status code.
    Schema.String.pipe(
      HttpApiSchema.status(404),
      HttpApiSchema.asText()
    ),

    // Any schema that encodes to `Schema.Void` is treated as "no content".
    // Here it uses a custom status code.
    Schema.Void.pipe(HttpApiSchema.status(401))
  ]
})

No Content Handling

Schema.Void is still treated as "no content".

The general rule is unchanged: if a schema encodes to Schema.Void, the response is treated as having no content.

Previously, decoding a "no content" response required using HttpApiSchema.asEmpty or HttpApiSchema.EmptyErrorClass (which was a bit hacky).

The proposal is to replace both with a single API: HttpApiSchema.asNoContent.

Example (Decoding typed errors from no-content responses)

import { Schema } from "effect"
import { HttpApiEndpoint, HttpApiSchema } from "effect/unstable/httpapi"

const SimpleError = Schema.Struct({
  _tag: Schema.tag("SimpleError"),
  message: Schema.String
})

class GroupError extends Schema.ErrorClass<GroupError>("GroupError")({
  _tag: Schema.tag("GroupError")
}) {}

HttpApiEndpoint.get("findById", "/:id", {
  pathParams: {
    id: Schema.FiniteFromString
  },

  // Possible error responses.
  error: [
    // Struct-based error schema
    SimpleError.pipe(
      HttpApiSchema.asNoContent({
        decode: () => SimpleError.makeUnsafe({ message: "Simple error" })
      }),
      HttpApiSchema.status(400)
    ),

    // Class-based error schema
    GroupError.pipe(
      HttpApiSchema.asNoContent({
        decode: () => new GroupError({})
      }),
      HttpApiSchema.status(418)
    )
  ]
})

@github-actions
Copy link
Contributor

github-actions bot commented Jan 28, 2026

📊 JSDoc Documentation Analysis

📈 Current Analysis Results
Analyzing 124 TypeScript files in packages/effect/src/ (including schema and config subdirectories)...

============================================================
         EFFECT JSDOC ANALYSIS REPORT
============================================================

📊 SUMMARY STATISTICS
------------------------------
Total files analyzed: 124
Total exported members: 4013
Missing @example: 1582 (39.4%)
Missing @category: 511 (12.7%)

🎯 TOP FILES NEEDING ATTENTION
----------------------------------------
1. Schema.ts
   📝 456 missing examples, 🏷️  262 missing categories
   📦 456 total exports
2. SchemaRepresentation.ts
   📝 92 missing examples, 🏷️  91 missing categories
   📦 92 total exports
3. SchemaAST.ts
   📝 74 missing examples, 🏷️  18 missing categories
   📦 74 total exports
4. Channel.ts
   📝 76 missing examples, 🏷️  0 missing categories
   📦 147 total exports
5. Sink.ts
   📝 64 missing examples, 🏷️  2 missing categories
   📦 81 total exports
6. Predicate.ts
   📝 57 missing examples, 🏷️  0 missing categories
   📦 57 total exports
7. SchemaGetter.ts
   📝 49 missing examples, 🏷️  0 missing categories
   📦 49 total exports
8. SchemaTransformation.ts
   📝 29 missing examples, 🏷️  18 missing categories
   📦 29 total exports
9. Config.ts
   📝 33 missing examples, 🏷️  5 missing categories
   📦 33 total exports
10. Cause.ts
   📝 33 missing examples, 🏷️  2 missing categories
   📦 75 total exports
11. JsonSchema.ts
   📝 17 missing examples, 🏷️  17 missing categories
   📦 17 total exports
12. Order.ts
   📝 25 missing examples, 🏷️  0 missing categories
   📦 25 total exports
13. Filter.ts
   📝 24 missing examples, 🏷️  0 missing categories
   📦 35 total exports
14. SchemaIssue.ts
   📝 22 missing examples, 🏷️  2 missing categories
   📦 22 total exports
15. SchemaParser.ts
   📝 24 missing examples, 🏷️  0 missing categories
   📦 24 total exports

✅ PERFECTLY DOCUMENTED FILES
-----------------------------------
   Chunk.ts (87 exports)
   Clock.ts (5 exports)
   FiberHandle.ts (15 exports)
   FiberMap.ts (19 exports)
   FiberSet.ts (14 exports)
   HKT.ts (4 exports)
   HashMap.ts (44 exports)
   HashSet.ts (21 exports)
   Match.ts (57 exports)
   MutableHashSet.ts (9 exports)
   MutableRef.ts (17 exports)
   NonEmptyIterable.ts (3 exports)
   Random.ts (7 exports)
   Redacted.ts (9 exports)
   RegExp.ts (3 exports)
   Runtime.ts (3 exports)
   Symbol.ts (1 exports)
   Trie.ts (29 exports)
   TxChunk.ts (22 exports)
   TxHashMap.ts (41 exports)
   TxHashSet.ts (24 exports)
   TxRef.ts (7 exports)
   TxSemaphore.ts (14 exports)
   Unify.ts (8 exports)
   index.ts (0 exports)

🔍 SAMPLE MISSING ITEMS FROM Schema.ts
-----------------------------------
   Optionality (type, line 60): missing example, category
   Mutability (type, line 67): missing example, category
   ConstructorDefault (type, line 74): missing example, category
   MakeOptions (interface, line 82): missing example, category
   Bottom (interface, line 106): missing example, category
   declareConstructor (interface, line 158): missing example, category
   declareConstructor (function, line 182): missing example
   declare (interface, line 206): missing example
   declare (function, line 215): missing example, category
   revealBottom (function, line 235): missing example, category

📋 BREAKDOWN BY EXPORT TYPE
-----------------------------------
const: 694 missing examples, 155 missing categories
function: 333 missing examples, 97 missing categories
type: 182 missing examples, 86 missing categories
interface: 277 missing examples, 152 missing categories
class: 57 missing examples, 1 missing categories
namespace: 39 missing examples, 20 missing categories

📈 DOCUMENTATION PROGRESS
------------------------------
Examples: 2431/4013 (60.6% complete)
Categories: 3502/4013 (87.3% complete)

============================================================
Analysis complete! 2093 items need attention.
============================================================

📄 Detailed results saved to: jsdoc-analysis-results.json

This comment is automatically updated on each push. View the analysis script for details.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 28, 2026

Bundle Size Analysis

File Name Current Size Previous Size Difference
basic.ts 6.31 KB 6.31 KB 0.00 KB (0.00%)
batching.ts 8.56 KB 8.56 KB 0.00 KB (0.00%)
brand.ts 6.22 KB 6.22 KB 0.00 KB (0.00%)
cache.ts 9.66 KB 9.66 KB 0.00 KB (0.00%)
config.ts 16.18 KB 16.18 KB 0.00 KB (0.00%)
differ.ts 14.39 KB 14.39 KB 0.00 KB (0.00%)
http-client.ts 19.10 KB 19.10 KB 0.00 KB (0.00%)
logger.ts 8.88 KB 8.88 KB 0.00 KB (0.00%)
metric.ts 8.81 KB 8.81 KB 0.00 KB (0.00%)
optic.ts 7.47 KB 7.47 KB 0.00 KB (0.00%)
pubsub.ts 13.19 KB 13.19 KB 0.00 KB (0.00%)
queue.ts 11.05 KB 11.05 KB 0.00 KB (0.00%)
schedule.ts 9.77 KB 9.77 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 23.92 KB 23.92 KB 0.00 KB (0.00%)
schema-string-transformation.ts 11.34 KB 11.34 KB 0.00 KB (0.00%)
schema-string.ts 9.66 KB 9.66 KB 0.00 KB (0.00%)
schema-template-literal.ts 12.17 KB 12.17 KB 0.00 KB (0.00%)
schema-toArbitraryLazy.ts 16.41 KB 16.41 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 15.25 KB 15.25 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 15.54 KB 15.54 KB 0.00 KB (0.00%)
schema-toFormatter.ts 15.39 KB 15.39 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 17.99 KB 17.99 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 16.18 KB 16.18 KB 0.00 KB (0.00%)
schema.ts 15.03 KB 15.03 KB 0.00 KB (0.00%)
stm.ts 12.05 KB 12.05 KB 0.00 KB (0.00%)
stream.ts 8.51 KB 8.51 KB 0.00 KB (0.00%)

@gcanti gcanti force-pushed the httpapi-forEach branch 8 times, most recently from e8802d6 to 950fedd Compare January 31, 2026 06:13
@gcanti gcanti changed the title HttpApi: centralize forEach behavior HttpApi: refactor OpenAPI related APIs Jan 31, 2026
@gcanti gcanti force-pushed the httpapi-forEach branch 5 times, most recently from 18c4ed1 to 75e4198 Compare February 1, 2026 19:06
@gcanti gcanti mentioned this pull request Feb 3, 2026
@gcanti gcanti marked this pull request as ready for review February 5, 2026 09:51
@gcanti gcanti merged commit 20a140b into main Feb 5, 2026
13 checks passed
@gcanti gcanti deleted the httpapi-forEach branch February 5, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant