diff --git a/fern/pages/changelogs/ts-express/2025-01-29.mdx b/fern/pages/changelogs/ts-express/2025-01-29.mdx deleted file mode 100644 index 1af9526044d..00000000000 --- a/fern/pages/changelogs/ts-express/2025-01-29.mdx +++ /dev/null @@ -1,3 +0,0 @@ -## 0.17.7 -**`(internal):`** Upgrade to IRv55. - diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/overrides-resolution.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/overrides-resolution.json new file mode 100644 index 00000000000..9a2c5243a18 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-docs/overrides-resolution.json @@ -0,0 +1,308 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "createAUser": { + "auth": false, + "display-name": "Create a user", + "docs": undefined, + "examples": [ + { + "request": {}, + "response": { + "body": { + "createdAt": "2024-01-15T09:30:00Z", + "email": "email", + "id": "id", + "name": "name", + "settings": { + "lastModified": "2024-01-15T09:30:00Z", + "notifications": true, + "theme": "theme", + }, + "stats": { + "accountStatus": "active", + "lastLoginTime": "2024-01-15T09:30:00Z", + "totalLogins": 1, + }, + }, + }, + }, + ], + "method": "POST", + "pagination": undefined, + "path": "/users", + "request": { + "body": { + "properties": { + "email": "optional", + "name": "optional", + "settings": "optional", + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "UserCreate", + "path-parameters": undefined, + "query-parameters": undefined, + }, + "response": { + "docs": "User created successfully", + "status-code": 200, + "type": "User", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "updateAUser": { + "auth": false, + "display-name": "Update a user", + "docs": undefined, + "examples": [ + { + "request": {}, + "response": { + "body": { + "createdAt": "2024-01-15T09:30:00Z", + "email": "email", + "id": "id", + "name": "name", + "settings": { + "lastModified": "2024-01-15T09:30:00Z", + "notifications": true, + "theme": "theme", + }, + "stats": { + "accountStatus": "active", + "lastLoginTime": "2024-01-15T09:30:00Z", + "totalLogins": 1, + }, + }, + }, + }, + ], + "method": "PUT", + "pagination": undefined, + "path": "/users", + "request": { + "body": { + "properties": { + "email": "optional", + "lastName": "optional", + "name": "optional", + "settings": "optional", + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "UserUpdate", + "path-parameters": undefined, + "query-parameters": undefined, + }, + "response": { + "docs": "User updated successfully", + "status-code": 200, + "type": "User", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "User": { + "docs": undefined, + "inline": undefined, + "properties": { + "createdAt": "optional", + "email": "optional", + "id": "optional", + "name": "optional", + "settings": "optional", + "stats": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserSettings": { + "docs": undefined, + "inline": undefined, + "properties": { + "lastModified": "optional", + "notifications": "optional", + "theme": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserStats": { + "docs": undefined, + "inline": undefined, + "properties": { + "accountStatus": "optional", + "lastLoginTime": "optional", + "totalLogins": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserStatsAccountStatus": { + "enum": [ + "active", + "suspended", + "deleted", + "inactive", + ], + "inline": true, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + createAUser: + path: /users + method: POST + auth: false + source: + openapi: ../openapi.yml + display-name: Create a user + request: + name: UserCreate + body: + properties: + name: optional + email: optional + settings: optional + content-type: application/json + response: + docs: User created successfully + type: User + status-code: 200 + examples: + - request: {} + response: + body: + id: id + name: name + email: email + createdAt: '2024-01-15T09:30:00Z' + settings: + theme: theme + notifications: true + lastModified: '2024-01-15T09:30:00Z' + stats: + totalLogins: 1 + lastLoginTime: '2024-01-15T09:30:00Z' + accountStatus: active + updateAUser: + path: /users + method: PUT + auth: false + source: + openapi: ../openapi.yml + display-name: Update a user + request: + name: UserUpdate + body: + properties: + name: optional + email: optional + settings: optional + lastName: optional + content-type: application/json + response: + docs: User updated successfully + type: User + status-code: 200 + examples: + - request: {} + response: + body: + id: id + name: name + email: email + createdAt: '2024-01-15T09:30:00Z' + settings: + theme: theme + notifications: true + lastModified: '2024-01-15T09:30:00Z' + stats: + totalLogins: 1 + lastLoginTime: '2024-01-15T09:30:00Z' + accountStatus: active + source: + openapi: ../openapi.yml +types: + UserSettings: + properties: + theme: optional + notifications: optional + lastModified: optional + source: + openapi: ../openapi.yml + UserStatsAccountStatus: + enum: + - active + - suspended + - deleted + - inactive + inline: true + source: + openapi: ../openapi.yml + UserStats: + properties: + totalLogins: optional + lastLoginTime: optional + accountStatus: optional + source: + openapi: ../openapi.yml + User: + properties: + id: optional + name: optional + email: optional + createdAt: optional + settings: optional + stats: optional + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "OpenAPI overrides resolution", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: OpenAPI overrides resolution +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/overrides-resolution.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/overrides-resolution.json new file mode 100644 index 00000000000..4477984b94a --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir-in-memory/overrides-resolution.json @@ -0,0 +1,85 @@ +{ + "type": "openapi", + "value": { + "openapi": "3.1.0", + "info": { + "title": "OpenAPI overrides resolution", + "version": "1.0.0" + }, + "paths": { + "/users": { + "post": { + "summary": "Create a user", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "common/common.yml#/components/schemas/UserCreate" + } + } + } + }, + "responses": { + "200": { + "description": "User created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "common/common.yml#/components/schemas/User" + } + } + } + } + } + }, + "put": { + "summary": "Update a user", + "requestBody": { + "required": true, + "nullable": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "User updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "common/common.yml#/components/schemas/User" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "UserUpdate": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string", + "nullable": true + }, + "settings": { + "$ref": "common/common.yml#/components/schemas/UserSettings", + "nullable": true + } + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/overrides-resolution.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/overrides-resolution.json new file mode 100644 index 00000000000..5561bf03b45 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi-ir/overrides-resolution.json @@ -0,0 +1,823 @@ +{ + "title": "OpenAPI overrides resolution", + "servers": [], + "tags": { + "tagsById": {} + }, + "hasEndpointsMarkedInternal": false, + "endpoints": [ + { + "summary": "Create a user", + "audiences": [], + "tags": [], + "pathParameters": [], + "queryParameters": [], + "headers": [], + "generatedRequestName": "PostUsersRequest", + "request": { + "schema": { + "generatedName": "PostUsersRequest", + "schema": "UserCreate", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "contentType": "application/json", + "fullExamples": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "response": { + "description": "User created successfully", + "schema": { + "generatedName": "PostUsersResponse", + "schema": "User", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "statusCode": 200, + "type": "json" + }, + "errors": {}, + "server": [], + "authed": false, + "method": "POST", + "path": "/users", + "examples": [ + { + "pathParameters": [], + "queryParameters": [], + "headers": [], + "request": { + "properties": {}, + "type": "object" + }, + "response": { + "value": { + "properties": { + "id": { + "value": { + "value": "id", + "type": "string" + }, + "type": "primitive" + }, + "name": { + "value": { + "value": "name", + "type": "string" + }, + "type": "primitive" + }, + "email": { + "value": { + "value": "email", + "type": "string" + }, + "type": "primitive" + }, + "createdAt": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + }, + "settings": { + "properties": { + "theme": { + "value": { + "value": "theme", + "type": "string" + }, + "type": "primitive" + }, + "notifications": { + "value": { + "value": true, + "type": "boolean" + }, + "type": "primitive" + }, + "lastModified": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + } + }, + "type": "object" + }, + "stats": { + "properties": { + "totalLogins": { + "value": { + "value": 1, + "type": "int" + }, + "type": "primitive" + }, + "lastLoginTime": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + }, + "accountStatus": { + "value": "active", + "type": "enum" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + }, + { + "summary": "Update a user", + "audiences": [], + "tags": [], + "pathParameters": [], + "queryParameters": [], + "headers": [], + "generatedRequestName": "PutUsersRequest", + "request": { + "schema": { + "generatedName": "PutUsersRequest", + "schema": "UserUpdate", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "contentType": "application/json", + "fullExamples": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "json" + }, + "response": { + "description": "User updated successfully", + "schema": { + "generatedName": "PutUsersResponse", + "schema": "User", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "fullExamples": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "statusCode": 200, + "type": "json" + }, + "errors": {}, + "server": [], + "authed": false, + "method": "PUT", + "path": "/users", + "examples": [ + { + "pathParameters": [], + "queryParameters": [], + "headers": [], + "request": { + "properties": {}, + "type": "object" + }, + "response": { + "value": { + "properties": { + "id": { + "value": { + "value": "id", + "type": "string" + }, + "type": "primitive" + }, + "name": { + "value": { + "value": "name", + "type": "string" + }, + "type": "primitive" + }, + "email": { + "value": { + "value": "email", + "type": "string" + }, + "type": "primitive" + }, + "createdAt": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + }, + "settings": { + "properties": { + "theme": { + "value": { + "value": "theme", + "type": "string" + }, + "type": "primitive" + }, + "notifications": { + "value": { + "value": true, + "type": "boolean" + }, + "type": "primitive" + }, + "lastModified": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + } + }, + "type": "object" + }, + "stats": { + "properties": { + "totalLogins": { + "value": { + "value": 1, + "type": "int" + }, + "type": "primitive" + }, + "lastLoginTime": { + "value": { + "value": "2024-01-15T09:30:00Z", + "type": "datetime" + }, + "type": "primitive" + }, + "accountStatus": { + "value": "active", + "type": "enum" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "type": "withoutStreaming" + }, + "codeSamples": [], + "type": "full" + } + ], + "source": { + "file": "../openapi.yml", + "type": "openapi" + } + } + ], + "webhooks": [], + "channel": [], + "groupedSchemas": { + "rootSchemas": { + "UserUpdate": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "userUpdateName", + "key": "name", + "schema": { + "generatedName": "userUpdateName", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserUpdateName", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userUpdateEmail", + "key": "email", + "schema": { + "generatedName": "userUpdateEmail", + "value": { + "generatedName": "UserUpdateEmail", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserUpdateEmail", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userUpdateSettings", + "key": "settings", + "schema": { + "generatedName": "userUpdateSettings", + "value": { + "generatedName": "UserUpdateSettings", + "schema": "UserSettings", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + }, + { + "conflict": {}, + "generatedName": "userUpdateLastName", + "key": "lastName", + "schema": { + "generatedName": "userUpdateLastName", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserUpdateLastName", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "UserUpdate", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "UserSettings": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "userSettingsTheme", + "key": "theme", + "schema": { + "generatedName": "userSettingsTheme", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserSettingsTheme", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userSettingsNotifications", + "key": "notifications", + "schema": { + "generatedName": "userSettingsNotifications", + "value": { + "schema": { + "type": "boolean" + }, + "generatedName": "UserSettingsNotifications", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userSettingsLastModified", + "key": "lastModified", + "schema": { + "generatedName": "userSettingsLastModified", + "value": { + "generatedName": "UserSettingsLastModified", + "value": { + "schema": { + "type": "datetime" + }, + "generatedName": "UserSettingsLastModified", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "UserSettings", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "UserStats": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "userStatsTotalLogins", + "key": "totalLogins", + "schema": { + "generatedName": "userStatsTotalLogins", + "value": { + "generatedName": "UserStatsTotalLogins", + "value": { + "schema": { + "type": "int" + }, + "generatedName": "UserStatsTotalLogins", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userStatsLastLoginTime", + "key": "lastLoginTime", + "schema": { + "generatedName": "userStatsLastLoginTime", + "value": { + "generatedName": "UserStatsLastLoginTime", + "value": { + "schema": { + "type": "datetime" + }, + "generatedName": "UserStatsLastLoginTime", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userStatsAccountStatus", + "key": "accountStatus", + "schema": { + "generatedName": "userStatsAccountStatus", + "value": { + "generatedName": "UserStatsAccountStatus", + "value": { + "generatedName": "UserStatsAccountStatus", + "values": [ + { + "generatedName": "active", + "value": "active", + "casing": {} + }, + { + "generatedName": "suspended", + "value": "suspended", + "casing": {} + }, + { + "generatedName": "deleted", + "value": "deleted", + "casing": {} + }, + { + "generatedName": "inactive", + "value": "inactive", + "casing": {} + } + ], + "groupName": [], + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "enum" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + } + ], + "allOfPropertyConflicts": [], + "generatedName": "UserStats", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "User": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "userId", + "key": "id", + "schema": { + "generatedName": "userId", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserId", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userName", + "key": "name", + "schema": { + "generatedName": "userName", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserName", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userEmail", + "key": "email", + "schema": { + "generatedName": "userEmail", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserEmail", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userCreatedAt", + "key": "createdAt", + "schema": { + "generatedName": "userCreatedAt", + "value": { + "generatedName": "UserCreatedAt", + "value": { + "schema": { + "type": "datetime" + }, + "generatedName": "UserCreatedAt", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "nullable" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userSettings", + "key": "settings", + "schema": { + "generatedName": "userSettings", + "value": { + "generatedName": "UserSettings", + "schema": "UserSettings", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + }, + { + "conflict": {}, + "generatedName": "userStats", + "key": "stats", + "schema": { + "generatedName": "userStats", + "value": { + "generatedName": "UserStats", + "schema": "UserStats", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + } + ], + "allOfPropertyConflicts": [], + "generatedName": "User", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + }, + "UserCreate": { + "allOf": [], + "properties": [ + { + "conflict": {}, + "generatedName": "userCreateName", + "key": "name", + "schema": { + "generatedName": "userCreateName", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserCreateName", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userCreateEmail", + "key": "email", + "schema": { + "generatedName": "userCreateEmail", + "value": { + "schema": { + "type": "string" + }, + "generatedName": "UserCreateEmail", + "groupName": [], + "type": "primitive" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [] + }, + { + "conflict": {}, + "generatedName": "userCreateSettings", + "key": "settings", + "schema": { + "generatedName": "userCreateSettings", + "value": { + "generatedName": "UserCreateSettings", + "schema": "UserSettings", + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "reference" + }, + "groupName": [], + "type": "optional" + }, + "audiences": [], + "readonly": false + } + ], + "allOfPropertyConflicts": [], + "generatedName": "UserCreate", + "groupName": [], + "additionalProperties": false, + "source": { + "file": "../openapi.yml", + "type": "openapi" + }, + "type": "object" + } + }, + "namespacedSchemas": {} + }, + "variables": {}, + "nonRequestReferencedSchemas": {}, + "securitySchemes": {}, + "globalHeaders": [], + "idempotencyHeaders": [], + "groups": {} +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/overrides-resolution.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/overrides-resolution.json new file mode 100644 index 00000000000..9a2c5243a18 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/__snapshots__/openapi/overrides-resolution.json @@ -0,0 +1,308 @@ +{ + "absoluteFilePath": "/DUMMY_PATH", + "importedDefinitions": {}, + "namedDefinitionFiles": { + "__package__.yml": { + "absoluteFilepath": "/DUMMY_PATH", + "contents": { + "service": { + "auth": false, + "base-path": "", + "endpoints": { + "createAUser": { + "auth": false, + "display-name": "Create a user", + "docs": undefined, + "examples": [ + { + "request": {}, + "response": { + "body": { + "createdAt": "2024-01-15T09:30:00Z", + "email": "email", + "id": "id", + "name": "name", + "settings": { + "lastModified": "2024-01-15T09:30:00Z", + "notifications": true, + "theme": "theme", + }, + "stats": { + "accountStatus": "active", + "lastLoginTime": "2024-01-15T09:30:00Z", + "totalLogins": 1, + }, + }, + }, + }, + ], + "method": "POST", + "pagination": undefined, + "path": "/users", + "request": { + "body": { + "properties": { + "email": "optional", + "name": "optional", + "settings": "optional", + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "UserCreate", + "path-parameters": undefined, + "query-parameters": undefined, + }, + "response": { + "docs": "User created successfully", + "status-code": 200, + "type": "User", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "updateAUser": { + "auth": false, + "display-name": "Update a user", + "docs": undefined, + "examples": [ + { + "request": {}, + "response": { + "body": { + "createdAt": "2024-01-15T09:30:00Z", + "email": "email", + "id": "id", + "name": "name", + "settings": { + "lastModified": "2024-01-15T09:30:00Z", + "notifications": true, + "theme": "theme", + }, + "stats": { + "accountStatus": "active", + "lastLoginTime": "2024-01-15T09:30:00Z", + "totalLogins": 1, + }, + }, + }, + }, + ], + "method": "PUT", + "pagination": undefined, + "path": "/users", + "request": { + "body": { + "properties": { + "email": "optional", + "lastName": "optional", + "name": "optional", + "settings": "optional", + }, + }, + "content-type": "application/json", + "headers": undefined, + "name": "UserUpdate", + "path-parameters": undefined, + "query-parameters": undefined, + }, + "response": { + "docs": "User updated successfully", + "status-code": 200, + "type": "User", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "types": { + "User": { + "docs": undefined, + "inline": undefined, + "properties": { + "createdAt": "optional", + "email": "optional", + "id": "optional", + "name": "optional", + "settings": "optional", + "stats": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserSettings": { + "docs": undefined, + "inline": undefined, + "properties": { + "lastModified": "optional", + "notifications": "optional", + "theme": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserStats": { + "docs": undefined, + "inline": undefined, + "properties": { + "accountStatus": "optional", + "lastLoginTime": "optional", + "totalLogins": "optional", + }, + "source": { + "openapi": "../openapi.yml", + }, + }, + "UserStatsAccountStatus": { + "enum": [ + "active", + "suspended", + "deleted", + "inactive", + ], + "inline": true, + "source": { + "openapi": "../openapi.yml", + }, + }, + }, + }, + "rawContents": "service: + auth: false + base-path: '' + endpoints: + createAUser: + path: /users + method: POST + auth: false + source: + openapi: ../openapi.yml + display-name: Create a user + request: + name: UserCreate + body: + properties: + name: optional + email: optional + settings: optional + content-type: application/json + response: + docs: User created successfully + type: User + status-code: 200 + examples: + - request: {} + response: + body: + id: id + name: name + email: email + createdAt: '2024-01-15T09:30:00Z' + settings: + theme: theme + notifications: true + lastModified: '2024-01-15T09:30:00Z' + stats: + totalLogins: 1 + lastLoginTime: '2024-01-15T09:30:00Z' + accountStatus: active + updateAUser: + path: /users + method: PUT + auth: false + source: + openapi: ../openapi.yml + display-name: Update a user + request: + name: UserUpdate + body: + properties: + name: optional + email: optional + settings: optional + lastName: optional + content-type: application/json + response: + docs: User updated successfully + type: User + status-code: 200 + examples: + - request: {} + response: + body: + id: id + name: name + email: email + createdAt: '2024-01-15T09:30:00Z' + settings: + theme: theme + notifications: true + lastModified: '2024-01-15T09:30:00Z' + stats: + totalLogins: 1 + lastLoginTime: '2024-01-15T09:30:00Z' + accountStatus: active + source: + openapi: ../openapi.yml +types: + UserSettings: + properties: + theme: optional + notifications: optional + lastModified: optional + source: + openapi: ../openapi.yml + UserStatsAccountStatus: + enum: + - active + - suspended + - deleted + - inactive + inline: true + source: + openapi: ../openapi.yml + UserStats: + properties: + totalLogins: optional + lastLoginTime: optional + accountStatus: optional + source: + openapi: ../openapi.yml + User: + properties: + id: optional + name: optional + email: optional + createdAt: optional + settings: optional + stats: optional + source: + openapi: ../openapi.yml +", + }, + }, + "packageMarkers": {}, + "rootApiFile": { + "contents": { + "display-name": "OpenAPI overrides resolution", + "error-discrimination": { + "strategy": "status-code", + }, + "name": "api", + }, + "defaultUrl": undefined, + "rawContents": "name: api +error-discrimination: + strategy: status-code +display-name: OpenAPI overrides resolution +", + }, +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/common/common.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/common/common.yml new file mode 100644 index 00000000000..994237ee5a8 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/common/common.yml @@ -0,0 +1,59 @@ +components: + schemas: + UserCreate: + type: object + properties: + name: + type: string + email: + type: string + settings: + $ref: '#/components/schemas/UserSettings' + nullable: true + + User: + type: object + properties: + id: + type: string + name: + type: string + email: + type: string + createdAt: + type: string + format: date-time + nullable: true + settings: + $ref: '#/components/schemas/UserSettings' + nullable: true + stats: + $ref: '#/components/schemas/UserStats' + nullable: true + UserSettings: + type: object + properties: + theme: + type: string + notifications: + type: boolean + lastModified: + type: string + format: date-time + nullable: true + + UserStats: + type: object + properties: + totalLogins: + type: integer + nullable: true + lastLoginTime: + type: string + format: date-time + nullable: true + accountStatus: + type: string + enum: [active, suspended, deleted] + nullable: true + diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/fern.config.json b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/fern.config.json new file mode 100644 index 00000000000..7980537f564 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "fern", + "version": "*" +} \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/generators.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/generators.yml new file mode 100644 index 00000000000..1d572729d35 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/fern/generators.yml @@ -0,0 +1,4 @@ +api: + specs: + - openapi: ../openapi.yml + overrides: ../overrides/overrides.yml diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/openapi.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/openapi.yml new file mode 100644 index 00000000000..ca490e50c97 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/openapi.yml @@ -0,0 +1,53 @@ +openapi: 3.1.0 +info: + title: OpenAPI overrides resolution + version: 1.0.0 + +paths: + /users: + post: + summary: Create a user + requestBody: + required: true + content: + application/json: + schema: + $ref: 'common/common.yml#/components/schemas/UserCreate' + responses: + '200': + description: User created successfully + content: + application/json: + schema: + $ref: 'common/common.yml#/components/schemas/User' + + put: + summary: Update a user + requestBody: + required: true + nullable: true + content: + application/json: + schema: + $ref: '#/components/schemas/UserUpdate' + responses: + '200': + description: User updated successfully + content: + application/json: + schema: + $ref: 'common/common.yml#/components/schemas/User' + +components: + schemas: + UserUpdate: + type: object + properties: + name: + type: string + email: + type: string + nullable: true + settings: + $ref: 'common/common.yml#/components/schemas/UserSettings' + nullable: true \ No newline at end of file diff --git a/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/overrides/overrides.yml b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/overrides/overrides.yml new file mode 100644 index 00000000000..9dc4770baf2 --- /dev/null +++ b/packages/cli/api-importers/openapi/openapi-ir-to-fern-tests/src/__test__/fixtures/overrides-resolution/overrides/overrides.yml @@ -0,0 +1,55 @@ +components: + schemas: + UserUpdate: + type: object + properties: + name: + type: string + lastName: + # This property is not in the original schema, so we should + # expect to see it in the final result. + type: string + email: + type: string + nullable: true + settings: + # We resolve the reference based on the _this_ filepath. + $ref: '../common/common.yml#/components/schemas/UserSettings' + nullable: true + + User: + type: object + properties: + id: + type: string + name: + type: string + email: + type: string + createdAt: + type: string + format: date-time + nullable: true + settings: + # We can still resolve the reference based on the OpenAPI source + # filepath, too. + $ref: 'common/common.yml#/components/schemas/UserSettings' + nullable: true + stats: + $ref: '#/components/schemas/UserStats' + nullable: true + + UserStats: + type: object + properties: + totalLogins: + type: integer + nullable: true + lastLoginTime: + type: string + format: date-time + nullable: true + accountStatus: + type: string + enum: [active, suspended, deleted, inactive] # Add inactive + nullable: true \ No newline at end of file diff --git a/packages/cli/cli/versions.yml b/packages/cli/cli/versions.yml index 3f2531c0a03..33db54bafea 100644 --- a/packages/cli/cli/versions.yml +++ b/packages/cli/cli/versions.yml @@ -1,3 +1,15 @@ +- changelogEntry: + - summary: | + OpenAPI overrides now support resolving file references from the location of the + OpenAPI overrides file itself. Previously, relative paths were only resolved from + the OpenAPI source file. + + Most users will experience no change, but this will enable a wider set of file + directory layouts depending on the user's preference. + type: fix + irVersion: 55 + version: 0.51.13 + - changelogEntry: - summary: | The Fern Definition now allows you to declare status codes for the response without having a type. @@ -17,7 +29,6 @@ irVersion: 55 version: 0.51.12 - - changelogEntry: - summary: | The OpenAPI parser generates response examples that are `{}` for 204 response types. diff --git a/packages/cli/workspace/lazy-fern-workspace/src/loaders/OpenAPIRefResolver.ts b/packages/cli/workspace/lazy-fern-workspace/src/loaders/OpenAPIRefResolver.ts new file mode 100644 index 00000000000..8172819c9e7 --- /dev/null +++ b/packages/cli/workspace/lazy-fern-workspace/src/loaders/OpenAPIRefResolver.ts @@ -0,0 +1,34 @@ +import { isAbsoluteUrl } from "@redocly/openapi-core"; +import { BaseResolver } from "@redocly/openapi-core"; +import path from "path"; + +import { doesPathExistSync } from "@fern-api/fs-utils"; +import { AbsoluteFilePath } from "@fern-api/fs-utils"; + +/* + * OpenAPIRefResolver is used to extend the behavior of the redocly OpenAPI parser. + */ +export class OpenAPIRefResolver extends BaseResolver { + private absolutePathToOpenAPIOverrides: AbsoluteFilePath | undefined; + + constructor(absolutePathToOpenAPIOverrides: AbsoluteFilePath | undefined) { + super(); + this.absolutePathToOpenAPIOverrides = absolutePathToOpenAPIOverrides; + } + + public resolveExternalRef(base: string | null, ref: string): string { + const result = super.resolveExternalRef(base, ref); + if (doesPathExistSync(AbsoluteFilePath.of(result))) { + return result; + } + // If we fail to resolve the ref, we try to resolve it relative to the + // absolutePathToOpenAPIOverrides as a best effort. + // + // This is especially useful for OpenAPI sources that require writing to + // temporary directories (e.g. Protobuf). + if (this.absolutePathToOpenAPIOverrides != null) { + return path.resolve(path.dirname(this.absolutePathToOpenAPIOverrides), ref); + } + return result; + } +} diff --git a/packages/cli/workspace/lazy-fern-workspace/src/loaders/index.ts b/packages/cli/workspace/lazy-fern-workspace/src/loaders/index.ts index 10263e952fd..ca53bf29150 100644 --- a/packages/cli/workspace/lazy-fern-workspace/src/loaders/index.ts +++ b/packages/cli/workspace/lazy-fern-workspace/src/loaders/index.ts @@ -1 +1,2 @@ export { OpenAPILoader } from "./OpenAPILoader"; +export { OpenAPIRefResolver } from "./OpenAPIRefResolver"; diff --git a/packages/cli/workspace/lazy-fern-workspace/src/utils/loadOpenAPI.ts b/packages/cli/workspace/lazy-fern-workspace/src/utils/loadOpenAPI.ts index f3e085b5155..708c456e1ba 100644 --- a/packages/cli/workspace/lazy-fern-workspace/src/utils/loadOpenAPI.ts +++ b/packages/cli/workspace/lazy-fern-workspace/src/utils/loadOpenAPI.ts @@ -45,6 +45,7 @@ export async function loadOpenAPI({ // references are resolved. return await parseOpenAPI({ absolutePathToOpenAPI, + absolutePathToOpenAPIOverrides, parsed: merged }); } diff --git a/packages/cli/workspace/lazy-fern-workspace/src/utils/parseOpenAPI.ts b/packages/cli/workspace/lazy-fern-workspace/src/utils/parseOpenAPI.ts index 2b4ebd467cf..5d2883fbf68 100644 --- a/packages/cli/workspace/lazy-fern-workspace/src/utils/parseOpenAPI.ts +++ b/packages/cli/workspace/lazy-fern-workspace/src/utils/parseOpenAPI.ts @@ -4,11 +4,15 @@ import { OpenAPI } from "openapi-types"; import { DEFAULT_OPENAPI_BUNDLE_OPTIONS } from "@fern-api/api-workspace-commons"; import { AbsoluteFilePath } from "@fern-api/fs-utils"; +import { OpenAPIRefResolver } from "../loaders/OpenAPIRefResolver"; + export async function parseOpenAPI({ absolutePathToOpenAPI, + absolutePathToOpenAPIOverrides, parsed }: { absolutePathToOpenAPI: AbsoluteFilePath; + absolutePathToOpenAPIOverrides?: AbsoluteFilePath; parsed?: OpenAPI.Document; }): Promise { const result = @@ -18,11 +22,13 @@ export async function parseOpenAPI({ doc: { source: new Source(absolutePathToOpenAPI, ""), parsed - } + }, + externalRefResolver: new OpenAPIRefResolver(absolutePathToOpenAPIOverrides) }) : await bundle({ ...DEFAULT_OPENAPI_BUNDLE_OPTIONS, - ref: absolutePathToOpenAPI + ref: absolutePathToOpenAPI, + externalRefResolver: new OpenAPIRefResolver(absolutePathToOpenAPIOverrides) }); return result.bundle.parsed; }