Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform: add OpenAPI.fromApi tests #4226

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open

Conversation

gcanti
Copy link
Contributor

@gcanti gcanti commented Jan 8, 2025

Fix 1

Add missing deprecated key to OpenApi.annotations API.

Fix 2

Add "application/x-www-form-urlencoded" to OpenApiSpecContentType type as it is generated by the system when using HttpApiSchema.withEncoding({ kind: "UrlParams" })

Example

import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  HttpApiSchema,
  OpenApi
} from "@effect/platform"
import { Schema } from "effect"

const api = HttpApi.make("api").add(
  HttpApiGroup.make("group").add(
    HttpApiEndpoint.post("post", "/")
      .addSuccess(Schema.String)
      .setPayload(
        Schema.Struct({ foo: Schema.String }).pipe(
          HttpApiSchema.withEncoding({ kind: "UrlParams" })
        )
      )
  )
)

const spec = OpenApi.fromApi(api)

console.log(JSON.stringify(spec.paths, null, 2))
/*
Output:
{
  "/": {
    "post": {
      "tags": [
        "group"
      ],
      "operationId": "group.post",
      "parameters": [],
      "security": [],
      "responses": {
        "200": {
          "description": "a string",
          "content": {
            "application/json": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "400": {
          "description": "The request did not match the expected schema",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HttpApiDecodeError"
              }
            }
          }
        }
      },
      "requestBody": {
        "content": {
          "application/x-www-form-urlencoded": {
            "schema": {
              "type": "object",
              "required": [
                "foo"
              ],
              "properties": {
                "foo": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            }
          }
        },
        "required": true
      }
    }
  }
}
*/

Fix 3

Fix: Prevent request body from being added to the OpenAPI spec for GET methods in OpenApi.fromApi.

When creating a GET endpoint with a request payload, the requestBody was incorrectly added to the OpenAPI specification, which is invalid for GET methods.

Before

import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  OpenApi
} from "@effect/platform"
import { Schema } from "effect"

const api = HttpApi.make("api").add(
  HttpApiGroup.make("group").add(
    HttpApiEndpoint.get("get", "/")
      .addSuccess(Schema.String)
      .setPayload(
        Schema.Struct({
          a: Schema.String
        })
      )
  )
)

const spec = OpenApi.fromApi(api)

console.log(JSON.stringify(spec.paths, null, 2))
/*
Output:
{
  "/": {
    "get": {
      "tags": [
        "group"
      ],
      "operationId": "group.get",
      "parameters": [
        {
          "name": "a",
          "in": "query",
          "schema": {
            "type": "string"
          },
          "required": true
        }
      ],
      "security": [],
      "responses": {
        "200": {
          "description": "a string",
          "content": {
            "application/json": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "400": {
          "description": "The request did not match the expected schema",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HttpApiDecodeError"
              }
            }
          }
        }
      },
      "requestBody": {
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "a"
              ],
              "properties": {
                "a": {
                  "type": "string"
                }
              },
              "additionalProperties": false
            }
          }
        },
        "required": true
      }
    }
  }
}
*/

After

import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  OpenApi
} from "@effect/platform"
import { Schema } from "effect"

const api = HttpApi.make("api").add(
  HttpApiGroup.make("group").add(
    HttpApiEndpoint.get("get", "/")
      .addSuccess(Schema.String)
      .setPayload(
        Schema.Struct({
          a: Schema.String
        })
      )
  )
)

const spec = OpenApi.fromApi(api)

console.log(JSON.stringify(spec.paths, null, 2))
/*
Output:
{
  "/": {
    "get": {
      "tags": [
        "group"
      ],
      "operationId": "group.get",
      "parameters": [
        {
          "name": "a",
          "in": "query",
          "schema": {
            "type": "string"
          },
          "required": true
        }
      ],
      "security": [],
      "responses": {
        "200": {
          "description": "a string",
          "content": {
            "application/json": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "400": {
          "description": "The request did not match the expected schema",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HttpApiDecodeError"
              }
            }
          }
        }
      }
    }
  }
}
*/

Fix 4

Ensure the encoding kind of success responses is respected in the OpenAPI spec for GET requests.

Before

When generating an OpenAPI spec for a GET request with a success schema of type `HttpApiSchema.Text()``, the response content type was incorrectly set to "application/json" instead of "text/plain".

import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  HttpApiSchema,
  OpenApi
} from "@effect/platform"

const api = HttpApi.make("api").add(
  HttpApiGroup.make("group").add(
    HttpApiEndpoint.get("get", "/").addSuccess(HttpApiSchema.Text())
  )
)

const spec = OpenApi.fromApi(api)

console.log(JSON.stringify(spec.paths, null, 2))
/*
Output:
{
  "/": {
    "get": {
      "tags": [
        "group"
      ],
      "operationId": "group.get",
      "parameters": [],
      "security": [],
      "responses": {
        "200": {
          "description": "a string",
          "content": {
            "application/json": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "400": {
          "description": "The request did not match the expected schema",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HttpApiDecodeError"
              }
            }
          }
        }
      }
    }
  }
}
*/

After

import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  HttpApiSchema,
  OpenApi
} from "@effect/platform"

const api = HttpApi.make("api").add(
  HttpApiGroup.make("group").add(
    HttpApiEndpoint.get("get", "/").addSuccess(HttpApiSchema.Text())
  )
)

const spec = OpenApi.fromApi(api)

console.log(JSON.stringify(spec.paths, null, 2))
/*
Output:
{
  "/": {
    "get": {
      "tags": [
        "group"
      ],
      "operationId": "group.get",
      "parameters": [],
      "security": [],
      "responses": {
        "200": {
          "description": "a string",
          "content": {
-            "application/json": {
+            "text/plain": {
              "schema": {
                "type": "string"
              }
            }
          }
        },
        "400": {
          "description": "The request did not match the expected schema",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HttpApiDecodeError"
              }
            }
          }
        }
      }
    }
  }
}
*/

Copy link

changeset-bot bot commented Jan 8, 2025

🦋 Changeset detected

Latest commit: c856cee

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 29 packages
Name Type
@effect/platform Patch
@effect/cli Patch
@effect/cluster-node Patch
@effect/cluster-workflow Patch
@effect/cluster Patch
@effect/experimental Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/rpc-http Patch
@effect/rpc Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-node Patch
@effect/sql Patch
@effect/ai Patch
@effect/ai-openai Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/cluster-browser Patch
@effect/sql-drizzle Patch
@effect/sql-kysely Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@gcanti gcanti added the bug Something isn't working label Jan 9, 2025
@gcanti gcanti marked this pull request as ready for review January 10, 2025 13:42
@gcanti gcanti requested a review from tim-smart as a code owner January 10, 2025 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Discussion Ongoing
Development

Successfully merging this pull request may close these issues.

1 participant